@@ -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