@@ -41,6 +41,7 @@ import (
4141
4242	infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1" 
4343	vmwarev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1" 
44+ 	"sigs.k8s.io/cluster-api-provider-vsphere/feature" 
4445	capvcontext "sigs.k8s.io/cluster-api-provider-vsphere/pkg/context" 
4546	"sigs.k8s.io/cluster-api-provider-vsphere/pkg/context/vmware" 
4647	infrautilv1 "sigs.k8s.io/cluster-api-provider-vsphere/pkg/util" 
@@ -163,6 +164,15 @@ func (v *VmopMachineService) SyncFailureReason(_ context.Context, machineCtx cap
163164	return  supervisorMachineCtx .VSphereMachine .Status .FailureReason  !=  nil  ||  supervisorMachineCtx .VSphereMachine .Status .FailureMessage  !=  nil , nil 
164165}
165166
167+ type  affinityInfo  struct  {
168+ 	affinitySpec   * vmoprv1.AffinitySpec 
169+ 	vmGroupName    string 
170+ 	failureDomain  * string 
171+ 
172+ 	// TODO: is this needed for the single zone case? 
173+ 	// zones []topologyv1.Zone 
174+ }
175+ 
166176// ReconcileNormal reconciles create and update events for VM Operator VMs. 
167177func  (v  * VmopMachineService ) ReconcileNormal (ctx  context.Context , machineCtx  capvcontext.MachineContext ) (bool , error ) {
168178	log  :=  ctrl .LoggerFrom (ctx )
@@ -171,10 +181,6 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
171181		return  false , errors .New ("received unexpected SupervisorMachineContext type" )
172182	}
173183
174- 	if  supervisorMachineCtx .Machine .Spec .FailureDomain  !=  ""  {
175- 		supervisorMachineCtx .VSphereMachine .Spec .FailureDomain  =  ptr .To (supervisorMachineCtx .Machine .Spec .FailureDomain )
176- 	}
177- 
178184	// If debug logging is enabled, report the number of vms in the cluster before and after the reconcile 
179185	if  log .V (5 ).Enabled () {
180186		vms , err  :=  v .getVirtualMachinesInCluster (ctx , supervisorMachineCtx )
@@ -188,6 +194,112 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
188194	// Set the VM state. Will get reset throughout the reconcile 
189195	supervisorMachineCtx .VSphereMachine .Status .VMStatus  =  vmwarev1 .VirtualMachineStatePending 
190196
197+ 	var  affInfo  affinityInfo 
198+ 	if  feature .Gates .Enabled (feature .NodeAutoPlacement ) && 
199+ 		! infrautilv1 .IsControlPlaneMachine (machineCtx .GetVSphereMachine ()) {
200+ 		// Check for the presence of a VirtualMachineGroup with the name and namespace same as the name of the Cluster 
201+ 		vmOperatorVMGroup  :=  & vmoprv1.VirtualMachineGroup {}
202+ 		key  :=  client.ObjectKey {
203+ 			Namespace : supervisorMachineCtx .Cluster .Namespace ,
204+ 			Name :      supervisorMachineCtx .Cluster .Name ,
205+ 		}
206+ 		err  :=  v .Client .Get (ctx , key , vmOperatorVMGroup )
207+ 		if  err  !=  nil  {
208+ 			if  ! apierrors .IsNotFound (err ) {
209+ 				return  false , err 
210+ 			}
211+ 			if  apierrors .IsNotFound (err ) {
212+ 				log .V (4 ).Info ("VirtualMachineGroup not found, requeueing" )
213+ 				return  true , nil 
214+ 			}
215+ 		}
216+ 
217+ 		// Check if the current machine is a member of the boot order 
218+ 		// in the VirtualMachineGroup. 
219+ 		if  ! v .checkVirtualMachineGroupMembership (vmOperatorVMGroup , supervisorMachineCtx ) {
220+ 			log .V (4 ).Info ("Waiting for VirtualMachineGroup membership, requeueing" )
221+ 			return  true , nil 
222+ 		}
223+ 
224+ 		// Initialize the affinityInfo for the VM 
225+ 		affInfo  =  affinityInfo {
226+ 			vmGroupName : vmOperatorVMGroup .Name ,
227+ 		}
228+ 
229+ 		// Check the presence of the node-pool label on the VirtualMachineGroup object 
230+ 		nodePool  :=  supervisorMachineCtx .Machine .Labels [clusterv1 .MachineDeploymentNameLabel ]
231+ 		if  zone , ok  :=  vmOperatorVMGroup .Labels [fmt .Sprintf ("zone.cluster.x-k8s.io/%s" , nodePool )]; ok  &&  zone  !=  ""  {
232+ 			affInfo .failureDomain  =  ptr .To (zone )
233+ 		}
234+ 
235+ 		// Fetch machine deployments without explicit failureDomain specified 
236+ 		// to use when setting the anti-affinity rules 
237+ 		machineDeployments  :=  & clusterv1.MachineDeploymentList {}
238+ 		if  err  :=  v .Client .List (ctx , machineDeployments ,
239+ 			client .InNamespace (supervisorMachineCtx .Cluster .Namespace ),
240+ 			client.MatchingLabels {clusterv1 .ClusterNameLabel : supervisorMachineCtx .Cluster .Name }); err  !=  nil  {
241+ 			return  false , err 
242+ 		}
243+ 		mdNames  :=  []string {}
244+ 		for  _ , machineDeployment  :=  range  machineDeployments .Items  {
245+ 			// Not adding node pool with explicit failureDomain specified to propose anti-affinity behavior 
246+ 			// among node pools with automatic placement only. 
247+ 			if  machineDeployment .Spec .Template .Spec .FailureDomain  ==  ""  &&  machineDeployment .Name  !=  nodePool  {
248+ 				mdNames  =  append (mdNames , machineDeployment .Name )
249+ 			}
250+ 		}
251+ 		// turn to v4 log 
252+ 		log .V (2 ).Info ("Gathered anti-affine MDs" , "mdNames" , mdNames )
253+ 
254+ 		affInfo .affinitySpec  =  & vmoprv1.AffinitySpec {
255+ 			VMAffinity : & vmoprv1.VMAffinitySpec {
256+ 				RequiredDuringSchedulingPreferredDuringExecution : []vmoprv1.VMAffinityTerm {
257+ 					{
258+ 						LabelSelector : & metav1.LabelSelector {
259+ 							MatchLabels : map [string ]string {
260+ 								clusterv1 .MachineDeploymentNameLabel : nodePool ,
261+ 								clusterv1 .ClusterNameLabel :           supervisorMachineCtx .Cluster .Name ,
262+ 							},
263+ 						},
264+ 						TopologyKey : corev1 .LabelTopologyZone ,
265+ 					},
266+ 				},
267+ 			},
268+ 			VMAntiAffinity : & vmoprv1.VMAntiAffinitySpec {
269+ 				PreferredDuringSchedulingPreferredDuringExecution : []vmoprv1.VMAffinityTerm {
270+ 					{
271+ 						LabelSelector : & metav1.LabelSelector {
272+ 							MatchLabels : map [string ]string {
273+ 								clusterv1 .MachineDeploymentNameLabel : nodePool ,
274+ 								clusterv1 .ClusterNameLabel :           supervisorMachineCtx .Cluster .Name ,
275+ 							},
276+ 						},
277+ 						TopologyKey : corev1 .LabelHostname ,
278+ 					},
279+ 					{
280+ 						LabelSelector : & metav1.LabelSelector {
281+ 							MatchLabels : map [string ]string {
282+ 								clusterv1 .ClusterNameLabel : supervisorMachineCtx .Cluster .Name ,
283+ 							},
284+ 							MatchExpressions : []metav1.LabelSelectorRequirement {
285+ 								{
286+ 									Key :      clusterv1 .MachineDeploymentNameLabel ,
287+ 									Operator : metav1 .LabelSelectorOpIn ,
288+ 									Values :   mdNames ,
289+ 								},
290+ 							},
291+ 						},
292+ 						TopologyKey : corev1 .LabelTopologyZone ,
293+ 					},
294+ 				},
295+ 			},
296+ 		}
297+ 	}
298+ 
299+ 	if  supervisorMachineCtx .Machine .Spec .FailureDomain  !=  ""  {
300+ 		supervisorMachineCtx .VSphereMachine .Spec .FailureDomain  =  ptr .To (supervisorMachineCtx .Machine .Spec .FailureDomain )
301+ 	}
302+ 
191303	// Check for the presence of an existing object 
192304	vmOperatorVM  :=  & vmoprv1.VirtualMachine {}
193305	key , err  :=  virtualMachineObjectKey (supervisorMachineCtx .Machine .Name , supervisorMachineCtx .Machine .Namespace , supervisorMachineCtx .VSphereMachine .Spec .NamingStrategy )
@@ -208,7 +320,7 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
208320	}
209321
210322	// Reconcile the VM Operator VirtualMachine. 
211- 	if  err  :=  v .reconcileVMOperatorVM (ctx , supervisorMachineCtx , vmOperatorVM ); err  !=  nil  {
323+ 	if  err  :=  v .reconcileVMOperatorVM (ctx , supervisorMachineCtx , vmOperatorVM ,  & affInfo ); err  !=  nil  {
212324		v1beta1conditions .MarkFalse (supervisorMachineCtx .VSphereMachine , infrav1 .VMProvisionedCondition , vmwarev1 .VMCreationFailedReason , clusterv1beta1 .ConditionSeverityWarning ,
213325			"failed to create or update VirtualMachine: %v" , err )
214326		v1beta2conditions .Set (supervisorMachineCtx .VSphereMachine , metav1.Condition {
@@ -378,7 +490,7 @@ func (v *VmopMachineService) GetHostInfo(ctx context.Context, machineCtx capvcon
378490	return  vmOperatorVM .Status .Host , nil 
379491}
380492
381- func  (v  * VmopMachineService ) reconcileVMOperatorVM (ctx  context.Context , supervisorMachineCtx  * vmware.SupervisorMachineContext , vmOperatorVM  * vmoprv1.VirtualMachine ) error  {
493+ func  (v  * VmopMachineService ) reconcileVMOperatorVM (ctx  context.Context , supervisorMachineCtx  * vmware.SupervisorMachineContext , vmOperatorVM  * vmoprv1.VirtualMachine ,  affinityInfo   * affinityInfo ) error  {
382494	// All Machine resources should define the version of Kubernetes to use. 
383495	if  supervisorMachineCtx .Machine .Spec .Version  ==  ""  {
384496		return  errors .Errorf (
@@ -472,7 +584,7 @@ func (v *VmopMachineService) reconcileVMOperatorVM(ctx context.Context, supervis
472584		}
473585
474586		// Assign the VM's labels. 
475- 		vmOperatorVM .Labels  =  getVMLabels (supervisorMachineCtx , vmOperatorVM .Labels )
587+ 		vmOperatorVM .Labels  =  getVMLabels (supervisorMachineCtx , vmOperatorVM .Labels ,  affinityInfo )
476588
477589		addResourcePolicyAnnotations (supervisorMachineCtx , vmOperatorVM )
478590
@@ -494,6 +606,15 @@ func (v *VmopMachineService) reconcileVMOperatorVM(ctx context.Context, supervis
494606			vmOperatorVM  =  typedModified 
495607		}
496608
609+ 		if  affinityInfo  !=  nil  &&  affinityInfo .affinitySpec  !=  nil  {
610+ 			if  vmOperatorVM .Spec .Affinity  ==  nil  {
611+ 				vmOperatorVM .Spec .Affinity  =  affinityInfo .affinitySpec 
612+ 			}
613+ 			if  vmOperatorVM .Spec .GroupName  ==  ""  {
614+ 				vmOperatorVM .Spec .GroupName  =  affinityInfo .vmGroupName 
615+ 			}
616+ 		}
617+ 
497618		// Make sure the VSphereMachine owns the VM Operator VirtualMachine. 
498619		if  err  :=  ctrlutil .SetControllerReference (supervisorMachineCtx .VSphereMachine , vmOperatorVM , v .Client .Scheme ()); err  !=  nil  {
499620			return  errors .Wrapf (err , "failed to mark %s %s/%s as owner of %s %s/%s" ,
@@ -735,7 +856,7 @@ func (v *VmopMachineService) addVolumes(ctx context.Context, supervisorMachineCt
735856
736857		if  zone  :=  supervisorMachineCtx .VSphereMachine .Spec .FailureDomain ; zonal  &&  zone  !=  nil  {
737858			topology  :=  []map [string ]string {
738- 				{kubeTopologyZoneLabelKey : * zone },
859+ 				{corev1 . LabelTopologyZone : * zone },
739860			}
740861			b , err  :=  json .Marshal (topology )
741862			if  err  !=  nil  {
@@ -777,7 +898,7 @@ func (v *VmopMachineService) addVolumes(ctx context.Context, supervisorMachineCt
777898}
778899
779900// getVMLabels returns the labels applied to a VirtualMachine. 
780- func  getVMLabels (supervisorMachineCtx  * vmware.SupervisorMachineContext , vmLabels  map [string ]string ) map [string ]string  {
901+ func  getVMLabels (supervisorMachineCtx  * vmware.SupervisorMachineContext , vmLabels  map [string ]string ,  affinityInfo   * affinityInfo ) map [string ]string  {
781902	if  vmLabels  ==  nil  {
782903		vmLabels  =  map [string ]string {}
783904	}
@@ -791,7 +912,11 @@ func getVMLabels(supervisorMachineCtx *vmware.SupervisorMachineContext, vmLabels
791912
792913	// Get the labels that determine the VM's placement inside of a stretched 
793914	// cluster. 
794- 	topologyLabels  :=  getTopologyLabels (supervisorMachineCtx )
915+ 	var  failureDomain  * string 
916+ 	if  affinityInfo  !=  nil  &&  affinityInfo .failureDomain  !=  nil  {
917+ 		failureDomain  =  affinityInfo .failureDomain 
918+ 	}
919+ 	topologyLabels  :=  getTopologyLabels (supervisorMachineCtx , failureDomain )
795920	for  k , v  :=  range  topologyLabels  {
796921		vmLabels [k ] =  v 
797922	}
@@ -800,6 +925,9 @@ func getVMLabels(supervisorMachineCtx *vmware.SupervisorMachineContext, vmLabels
800925	// resources associated with the target cluster. 
801926	vmLabels [clusterv1 .ClusterNameLabel ] =  supervisorMachineCtx .GetClusterContext ().Cluster .Name 
802927
928+ 	// Ensure the VM has the machine deployment name label 
929+ 	vmLabels [clusterv1 .MachineDeploymentNameLabel ] =  supervisorMachineCtx .Machine .Labels [clusterv1 .MachineDeploymentNameLabel ]
930+ 
803931	return  vmLabels 
804932}
805933
@@ -809,10 +937,16 @@ func getVMLabels(supervisorMachineCtx *vmware.SupervisorMachineContext, vmLabels
809937// 
810938//	and thus the code is optimized as such. However, in the future 
811939//	this function may return a more diverse topology. 
812- func  getTopologyLabels (supervisorMachineCtx  * vmware.SupervisorMachineContext ) map [string ]string  {
940+ func  getTopologyLabels (supervisorMachineCtx  * vmware.SupervisorMachineContext , failureDomain  * string ) map [string ]string  {
941+ 	// TODO: Make it so that we always set the zone label, might require enquiring the zones present (when unset) 
813942	if  fd  :=  supervisorMachineCtx .VSphereMachine .Spec .FailureDomain ; fd  !=  nil  &&  * fd  !=  ""  {
814943		return  map [string ]string {
815- 			kubeTopologyZoneLabelKey : * fd ,
944+ 			corev1 .LabelTopologyZone : * fd ,
945+ 		}
946+ 	}
947+ 	if  failureDomain  !=  nil  &&  * failureDomain  !=  ""  {
948+ 		return  map [string ]string {
949+ 			corev1 .LabelTopologyZone : * failureDomain ,
816950		}
817951	}
818952	return  nil 
@@ -823,3 +957,16 @@ func getTopologyLabels(supervisorMachineCtx *vmware.SupervisorMachineContext) ma
823957func  getMachineDeploymentNameForCluster (cluster  * clusterv1.Cluster ) string  {
824958	return  fmt .Sprintf ("%s-workers-0" , cluster .Name )
825959}
960+ 
961+ // checkVirtualMachineGroupMembership checks if the machine is in the first boot order group 
962+ // and performs logic if a match is found. 
963+ func  (v  * VmopMachineService ) checkVirtualMachineGroupMembership (vmOperatorVMGroup  * vmoprv1.VirtualMachineGroup , supervisorMachineCtx  * vmware.SupervisorMachineContext ) bool  {
964+ 	if  len (vmOperatorVMGroup .Spec .BootOrder ) >  0  {
965+ 		for  _ , member  :=  range  vmOperatorVMGroup .Spec .BootOrder [0 ].Members  {
966+ 			if  member .Name  ==  supervisorMachineCtx .Machine .Name  {
967+ 				return  true 
968+ 			}
969+ 		}
970+ 	}
971+ 	return  false 
972+ }
0 commit comments