@@ -199,15 +199,31 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
199199 // Set the VM state. Will get reset throughout the reconcile
200200 supervisorMachineCtx .VSphereMachine .Status .VMStatus = vmwarev1 .VirtualMachineStatePending
201201
202+ // Get the VirtualMachine object Key
203+ vmOperatorVM := & vmoprv1.VirtualMachine {}
204+ key , err := virtualMachineObjectKey (supervisorMachineCtx .Machine .Name , supervisorMachineCtx .Machine .Namespace , supervisorMachineCtx .VSphereMachine .Spec .NamingStrategy )
205+ if err != nil {
206+ return false , err
207+ }
208+
209+ // When creating a new cluster and the user doesn't provide info about placement of VMs in a specific failure domain,
210+ // CAPV will define affinity rules to ensure proper placement of the machine.
211+ //
212+ // - All the machines belonging to the same MachineDeployment should be placed in the same failure domain - required.
213+ // - All the machines belonging to the same MachineDeployment should be spread across esxi hosts in the same failure domain - best-efforts.
214+ // - Different MachineDeployments and corresponding VMs should be spread across failure domains - best-efforts.
215+ //
216+ // Note: Control plane VM placement doesn't follow the above rules, and the assumption
217+ // is that failureDomain is always set for control plane VMs.
202218 var affInfo * affinityInfo
203219 if feature .Gates .Enabled (feature .NodeAutoPlacement ) &&
204220 ! infrautilv1 .IsControlPlaneMachine (machineCtx .GetVSphereMachine ()) {
205- vmOperatorVMGroup := & vmoprv1.VirtualMachineGroup {}
221+ vmGroup := & vmoprv1.VirtualMachineGroup {}
206222 key := client.ObjectKey {
207223 Namespace : supervisorMachineCtx .Cluster .Namespace ,
208224 Name : supervisorMachineCtx .Cluster .Name ,
209225 }
210- err := v .Client .Get (ctx , key , vmOperatorVMGroup )
226+ err := v .Client .Get (ctx , key , vmGroup )
211227 if err != nil {
212228 if ! apierrors .IsNotFound (err ) {
213229 return false , err
@@ -222,11 +238,8 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
222238 return true , nil
223239 }
224240
225- // Proceed only if the machine is a member of the VirtualMachineGroup.
226- isMember , err := v .checkVirtualMachineGroupMembership (vmOperatorVMGroup , supervisorMachineCtx )
227- if err != nil {
228- return true , errors .Wrapf (err , "%s" , fmt .Sprintf ("failed to check if VirtualMachine %s is a member of VirtualMachineGroup %s" , supervisorMachineCtx .VSphereMachine .Name , klog .KObj (vmOperatorVMGroup )))
229- }
241+ // Proceed only if the VSphereMachine is a member of the VirtualMachineGroup.
242+ isMember := v .checkVirtualMachineGroupMembership (vmGroup , key .Name )
230243 if ! isMember {
231244 v1beta2conditions .Set (supervisorMachineCtx .VSphereMachine , metav1.Condition {
232245 Type : infrav1 .VSphereMachineVirtualMachineProvisionedV1Beta2Condition ,
@@ -238,18 +251,19 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
238251 }
239252
240253 affInfo = & affinityInfo {
241- vmGroupName : vmOperatorVMGroup .Name ,
254+ vmGroupName : vmGroup .Name ,
242255 }
243256
244257 // Set the zone label using the annotation of the per-md zone mapping from VirtualMachineGroup.
245258 // This is for new VMs created during day-2 operations when Node Auto Placement is enabled.
246259 mdName := supervisorMachineCtx .Machine .Labels [clusterv1 .MachineDeploymentNameLabel ]
247- if fd , ok := vmOperatorVMGroup .Annotations [fmt .Sprintf ("%s/%s" , ZoneAnnotationPrefix , mdName )]; ok && fd != "" {
260+ if fd , ok := vmGroup .Annotations [fmt .Sprintf ("%s/%s" , ZoneAnnotationPrefix , mdName )]; ok && fd != "" {
248261 affInfo .failureDomain = fd
249262 }
250263
251- // Fetch machine deployments without explicit failureDomain specified
252- // to use when setting the anti-affinity rules.
264+ // VM in a MachineDeployment ideally should be placed in a different failure domain than VMs
265+ // in other MachineDeployments.
266+ // In order to do so, collect names of all the MachineDeployments except the one the VM belongs to.
253267 machineDeployments := & clusterv1.MachineDeploymentList {}
254268 if err := v .Client .List (ctx , machineDeployments ,
255269 client .InNamespace (supervisorMachineCtx .Cluster .Namespace ),
@@ -266,6 +280,7 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
266280
267281 affInfo .affinitySpec = vmoprv1.AffinitySpec {
268282 VMAffinity : & vmoprv1.VMAffinitySpec {
283+ // All the machines belonging to the same MachineDeployment should be placed in the same failure domain - required.
269284 RequiredDuringSchedulingPreferredDuringExecution : []vmoprv1.VMAffinityTerm {
270285 {
271286 LabelSelector : & metav1.LabelSelector {
@@ -278,6 +293,7 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
278293 },
279294 },
280295 VMAntiAffinity : & vmoprv1.VMAntiAffinitySpec {
296+ // All the machines belonging to the same MachineDeployment should be spread across esxi hosts in the same failure domain - best-efforts.
281297 PreferredDuringSchedulingPreferredDuringExecution : []vmoprv1.VMAffinityTerm {
282298 {
283299 LabelSelector : & metav1.LabelSelector {
@@ -291,6 +307,7 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
291307 },
292308 }
293309 if len (othermMDNames ) > 0 {
310+ // Different MachineDeployments and corresponding VMs should be spread across failure domains - best-efforts.
294311 affInfo .affinitySpec .VMAntiAffinity .PreferredDuringSchedulingPreferredDuringExecution = append (
295312 affInfo .affinitySpec .VMAntiAffinity .PreferredDuringSchedulingPreferredDuringExecution ,
296313 vmoprv1.VMAffinityTerm {
@@ -309,16 +326,13 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
309326 }
310327 }
311328
329+ // If the failureDomain is explicitly define for a machine, forward this info to the VM.
330+ // Note: for consistency, affinity rules will be set on all the VMs, no matter if they are explicitly assigned to a failureDomain or not.
312331 if supervisorMachineCtx .Machine .Spec .FailureDomain != "" {
313332 supervisorMachineCtx .VSphereMachine .Spec .FailureDomain = ptr .To (supervisorMachineCtx .Machine .Spec .FailureDomain )
314333 }
315334
316335 // Check for the presence of an existing object
317- vmOperatorVM := & vmoprv1.VirtualMachine {}
318- key , err := virtualMachineObjectKey (supervisorMachineCtx .Machine .Name , supervisorMachineCtx .Machine .Namespace , supervisorMachineCtx .VSphereMachine .Spec .NamingStrategy )
319- if err != nil {
320- return false , err
321- }
322336 if err := v .Client .Get (ctx , * key , vmOperatorVM ); err != nil {
323337 if ! apierrors .IsNotFound (err ) {
324338 return false , err
@@ -988,18 +1002,14 @@ func getMachineDeploymentNameForCluster(cluster *clusterv1.Cluster) string {
9881002}
9891003
9901004// checkVirtualMachineGroupMembership checks if the machine is in the first boot order group
991- // and performs logic if a match is found.
992- func (v * VmopMachineService ) checkVirtualMachineGroupMembership (vmOperatorVMGroup * vmoprv1.VirtualMachineGroup , supervisorMachineCtx * vmware. SupervisorMachineContext ) ( bool , error ) {
1005+ // and performs logic if a match is found, as first boot order contains all the worker VMs .
1006+ func (v * VmopMachineService ) checkVirtualMachineGroupMembership (vmOperatorVMGroup * vmoprv1.VirtualMachineGroup , virtualMachineName string ) bool {
9931007 if len (vmOperatorVMGroup .Spec .BootOrder ) > 0 {
9941008 for _ , member := range vmOperatorVMGroup .Spec .BootOrder [0 ].Members {
995- virtualMachineName , err := GenerateVirtualMachineName (supervisorMachineCtx .Machine .Name , supervisorMachineCtx .VSphereMachine .Spec .NamingStrategy )
996- if err != nil {
997- return false , err
998- }
9991009 if member .Name == virtualMachineName {
1000- return true , nil
1010+ return true
10011011 }
10021012 }
10031013 }
1004- return false , nil
1014+ return false
10051015}
0 commit comments