Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RELEASE_DIR := _build/release
DEV_DIR := _build/dev
REPO_ROOT := $(shell git rev-parse --show-toplevel)
FIPS_ENABLE ?= ""
BUILDER_GOLANG_VERSION ?= 1.24.11
BUILDER_GOLANG_VERSION ?= 1.24.13
BUILD_ARGS = --build-arg CRYPTO_LIB=${FIPS_ENABLE} --build-arg BUILDER_GOLANG_VERSION=${BUILDER_GOLANG_VERSION}
ARCH ?= amd64
ALL_ARCH = amd64 arm64
Expand All @@ -26,7 +26,7 @@ endif
# Image URL to use all building/pushing image targets
IMAGE_NAME := cluster-api-provider-maas-controller
REGISTRY ?= "us-east1-docker.pkg.dev/spectro-images/dev/${USER}/cluster-api"
SPECTRO_VERSION ?= 4.8.3-dev-12112025
SPECTRO_VERSION ?= 4.8.3-dev-tmo-19022026
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The date format in this version string appears to be "19022026" which could be interpreted as February 19, 2026 (ddmmyyyy format). According to the system information, the current date is February 20, 2026, so this date would be yesterday. However, if this is intended to be a date in the past or a specific versioning convention, please ensure it's intentional. If it's meant to be today's date, it should be updated to "20022026".

Copilot uses AI. Check for mistakes.
IMG_TAG ?= v0.6.1-spectro-${SPECTRO_VERSION}
CONTROLLER_IMG ?= ${REGISTRY}/${IMAGE_NAME}

Expand Down
20 changes: 11 additions & 9 deletions controllers/maasmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,12 +439,14 @@ func (r *MaasMachineReconciler) reconcileNormal(ctx context.Context, machineScop
// TODO(saamalik) confirm that we'll never "recreate" a m; e.g: findMachine should always return err
// if there used to be a m
if m == nil || !(m.State == infrav1beta1.MachineStateDeployed || m.State == infrav1beta1.MachineStateDeploying) {
// If machine is in Ready state, verify network interfaces before deploying
// This ensures correct subnet assignment before deployment starts
if m != nil && m.State == infrav1beta1.MachineStateReady && machineScope.GetDynamicLXD() {
machineScope.Info("Machine is in Ready state, verifying network interfaces before deployment", "machineID", m.ID)
// If machine is in Ready or Allocated state, verify network interfaces before deploying.
// This ensures correct subnet assignment before deployment starts (avoids wrong eth0 subnet
// on 2nd VM when MAAS compose links eth0 to a different subnet than requested).
if m != nil && machineScope.GetDynamicLXD() &&
(m.State == infrav1beta1.MachineStateReady || m.State == infrav1beta1.MachineStateAllocated) {
machineScope.Info("Verifying VM network interfaces before deployment", "machineID", m.ID, "state", m.State)
if err := machineSvc.VerifyVMNetworkInterfaces(ctx, m.ID); err != nil {
machineScope.Error(err, "Failed to verify VM network interfaces in Ready state, requeuing", "machineID", m.ID)
machineScope.Error(err, "Failed to verify VM network interfaces before deploy, requeuing", "machineID", m.ID)
conditions.MarkFalse(machineScope.MaasMachine, infrav1beta1.MachineDeployedCondition, infrav1beta1.MachineDeployingReason, clusterv1.ConditionSeverityWarning, "verifying network interfaces")
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
Expand Down Expand Up @@ -502,14 +504,14 @@ func (r *MaasMachineReconciler) reconcileNormal(ctx context.Context, machineScop
}

switch s := m.State; {
case s == infrav1beta1.MachineStateReady:
case s == infrav1beta1.MachineStateReady, s == infrav1beta1.MachineStateAllocated:
machineScope.SetNotReady()
conditions.MarkFalse(machineScope.MaasMachine, infrav1beta1.MachineDeployedCondition, infrav1beta1.MachineDeployingReason, clusterv1.ConditionSeverityWarning, "")
// Note: Network interface verification happens before deployment is triggered (see above).
// This is a safety check in case the machine reached Ready state through a different path.
// This is a safety check in case the machine reached Ready/Allocated through a different path.
if machineScope.GetDynamicLXD() {
if err := machineSvc.VerifyVMNetworkInterfaces(ctx, m.ID); err != nil {
machineScope.Error(err, "Failed to verify VM network interfaces in Ready state (safety check)", "machineID", m.ID)
machineScope.Error(err, "Failed to verify VM network interfaces (safety check)", "machineID", m.ID, "state", s)
// Requeue to retry verification - deployment should not proceed until interfaces are correct
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
Expand All @@ -534,7 +536,7 @@ func (r *MaasMachineReconciler) reconcileNormal(ctx context.Context, machineScop
machineScope.SetNotReady()
machineScope.Info("Machine is powered off!")
conditions.MarkFalse(machineScope.MaasMachine, infrav1beta1.MachineDeployedCondition, infrav1beta1.MachinePoweredOffReason, clusterv1.ConditionSeverityWarning, "")
case s == infrav1beta1.MachineStateDeploying, s == infrav1beta1.MachineStateAllocated:
case s == infrav1beta1.MachineStateDeploying:
machineScope.SetNotReady()
conditions.MarkFalse(machineScope.MaasMachine, infrav1beta1.MachineDeployedCondition, infrav1beta1.MachineDeployingReason, clusterv1.ConditionSeverityWarning, "")
case s == infrav1beta1.MachineStateDeployed:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/spectrocloud/cluster-api-provider-maas

go 1.24.2

toolchain go1.24.11
toolchain go1.24.13

require (
github.com/go-logr/logr v1.4.2
Expand Down
27 changes: 25 additions & 2 deletions pkg/maas/machine/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ func (s *Service) createVMViaMAAS(ctx context.Context, userDataB64 string) (*inf
if err != nil {
return nil, errors.Wrap(err, "failed to get existing VM by system-id")
}
// Verify and fix network interfaces (eth0/eth1 subnets) before deploy so that even if MAAS
// selected the wrong subnet for eth0 after commissioning/acquire, we correct it before deploy.
if err := s.VerifyVMNetworkInterfaces(ctx, m.SystemID()); err != nil {
return nil, errors.Wrap(err, "failed to verify VM network interfaces before deploy")
}
// Best-effort: set hostname and static IP before deploy
machineName := s.scope.Machine.Name
vmName := fmt.Sprintf("vm-%s", machineName)
Expand Down Expand Up @@ -667,9 +672,27 @@ func (s *Service) VerifyVMNetworkInterfaces(ctx context.Context, systemID string
}
}

// subnetsMatch returns true if actual and expected represent the same subnet (CIDR or name).
subnetsMatch := func(actual, expected string) bool {
if actual == "" || expected == "" {
return false
}
if strings.EqualFold(actual, expected) {
return true
}
// If both parse as CIDR, compare by network equality (robust to formatting)
_, actualNet, err1 := net.ParseCIDR(actual)
_, expectedNet, err2 := net.ParseCIDR(expected)
if err1 == nil && err2 == nil && actualNet != nil && expectedNet != nil {
return actualNet.IP.Equal(expectedNet.IP) && actualNet.Mask != nil && expectedNet.Mask != nil &&
string(actualNet.Mask) == string(expectedNet.Mask)
Comment on lines +687 to +688
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing network masks using string conversion is problematic because net.IPMask is a byte slice and converting it to a string may not correctly compare the actual byte values. Use bytes.Equal() instead for proper byte-wise comparison. Change line 687-688 to: bytes.Equal(actualNet.Mask, expectedNet.Mask). You'll need to import the "bytes" package.

Copilot uses AI. Check for mistakes.
}
return false
}

var aggErr error
if eth0Iface != nil {
if eth0Subnet == "" || !strings.EqualFold(eth0Subnet, expected0) {
if eth0Subnet == "" || !subnetsMatch(eth0Subnet, expected0) {
s.scope.Info("Fixing eth0 subnet mismatch", "system-id", systemID, "expected", expected0, "actual", eth0Subnet)
if err := s.fixInterfaceSubnet(ctx, systemID, eth0Iface, expected0, "eth0"); err != nil {
s.scope.Error(err, "Failed to fix eth0 subnet", "system-id", systemID, "expected", expected0, "actual", eth0Subnet)
Expand All @@ -685,7 +708,7 @@ func (s *Service) VerifyVMNetworkInterfaces(ctx context.Context, systemID string
}

if eth1Iface != nil {
if eth1Subnet == "" || !strings.EqualFold(eth1Subnet, expected1) {
if eth1Subnet == "" || !subnetsMatch(eth1Subnet, expected1) {
s.scope.Info("Fixing eth1 subnet mismatch", "system-id", systemID, "expected", expected1, "actual", eth1Subnet)
if err := s.fixInterfaceSubnet(ctx, systemID, eth1Iface, expected1, "eth1"); err != nil {
s.scope.Error(err, "Failed to fix eth1 subnet", "system-id", systemID, "expected", expected1, "actual", eth1Subnet)
Expand Down