@@ -311,6 +311,45 @@ func (r *CloudStackMachineReconciliationRunner) AddToLBIfNeeded() (retRes ctrl.R
311311 return ctrl.Result {}, nil
312312}
313313
314+ // RemoveFromLBIfNeeded removes the instance from the API server load balancer rule if it is a
315+ // control plane machine on a non-routed isolated network. Called from ReconcileDelete so the VM
316+ // is detached before being destroyed.
317+ //
318+ // The control-plane check uses the MachineControlPlaneLabel on the CloudStackMachine because the
319+ // CAPI Machine is not loaded on the delete path (see common stages in Reconcile).
320+ func (r * CloudStackMachineReconciliationRunner ) RemoveFromLBIfNeeded () (retRes ctrl.Result , reterr error ) {
321+ if _ , ok := r .ReconciliationSubject .Labels [clusterv1 .MachineControlPlaneLabel ]; ! ok {
322+ return ctrl.Result {}, nil
323+ }
324+ if r .FailureDomain .Spec .Zone .Network .Type != cloud .NetworkTypeIsolated {
325+ return ctrl.Result {}, nil
326+ }
327+ if r .ReconciliationSubject .Spec .InstanceID == nil {
328+ return ctrl.Result {}, nil
329+ }
330+
331+ // IsoNet is not pre-loaded on the delete path; fetch it by the same meta name AddToLBIfNeeded relies on.
332+ if res , err := r .GetObjectByName (
333+ r .IsoNetMetaName (r .FailureDomain .Spec .Zone .Network .Name ), r .IsoNet ,
334+ )(); r .ShouldReturn (res , err ) {
335+ return res , err
336+ }
337+ if r .IsoNet .Spec .Name == "" {
338+ // Isolated network object already gone -- nothing to detach from.
339+ return ctrl.Result {}, nil
340+ }
341+ if r .IsoNet .Status .RoutingMode != "" {
342+ // Routed isolated networks do not use a load balancer.
343+ return ctrl.Result {}, nil
344+ }
345+
346+ r .Log .Info ("Removing VM from load balancer rule." , "instance-id" , * r .ReconciliationSubject .Spec .InstanceID )
347+ if err := r .CSUser .RemoveVMFromLoadBalancerRule (r .IsoNet , * r .ReconciliationSubject .Spec .InstanceID ); err != nil {
348+ return ctrl.Result {}, err
349+ }
350+ return ctrl.Result {}, nil
351+ }
352+
314353// GetOrCreateMachineStateChecker creates or gets CloudStackMachineStateChecker object.
315354func (r * CloudStackMachineReconciliationRunner ) GetOrCreateMachineStateChecker () (retRes ctrl.Result , reterr error ) {
316355 checkerName := r .ReconciliationSubject .Spec .InstanceID
@@ -344,6 +383,14 @@ func (r *CloudStackMachineReconciliationRunner) ReconcileDelete() (retRes ctrl.R
344383 return ctrl.Result {}, err
345384 }
346385 }
386+
387+ // For control plane machines on a non-routed isolated network, remove the VM from the
388+ // API server load balancer rule before destroying the VM, so CloudStack does not keep
389+ // routing API traffic to a VM that's about to be expunged.
390+ if res , err := r .RemoveFromLBIfNeeded (); r .ShouldReturn (res , err ) {
391+ return res , err
392+ }
393+
347394 r .Recorder .Eventf (r .ReconciliationSubject , "Normal" , "Deleting" , CSMachineDeletionMessage , r .ReconciliationSubject .Name )
348395 r .Log .Info ("Deleting instance" , "instance-id" , r .ReconciliationSubject .Spec .InstanceID )
349396 // Use CSClient instead of CSUser here to expunge as admin.
0 commit comments