@@ -41,6 +41,7 @@ import (
41
41
42
42
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
43
43
vmwarev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1"
44
+ "sigs.k8s.io/cluster-api-provider-vsphere/feature"
44
45
capvcontext "sigs.k8s.io/cluster-api-provider-vsphere/pkg/context"
45
46
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/context/vmware"
46
47
infrautilv1 "sigs.k8s.io/cluster-api-provider-vsphere/pkg/util"
@@ -163,6 +164,15 @@ func (v *VmopMachineService) SyncFailureReason(_ context.Context, machineCtx cap
163
164
return supervisorMachineCtx .VSphereMachine .Status .FailureReason != nil || supervisorMachineCtx .VSphereMachine .Status .FailureMessage != nil , nil
164
165
}
165
166
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
+
166
176
// ReconcileNormal reconciles create and update events for VM Operator VMs.
167
177
func (v * VmopMachineService ) ReconcileNormal (ctx context.Context , machineCtx capvcontext.MachineContext ) (bool , error ) {
168
178
log := ctrl .LoggerFrom (ctx )
@@ -171,10 +181,6 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
171
181
return false , errors .New ("received unexpected SupervisorMachineContext type" )
172
182
}
173
183
174
- if supervisorMachineCtx .Machine .Spec .FailureDomain != "" {
175
- supervisorMachineCtx .VSphereMachine .Spec .FailureDomain = ptr .To (supervisorMachineCtx .Machine .Spec .FailureDomain )
176
- }
177
-
178
184
// If debug logging is enabled, report the number of vms in the cluster before and after the reconcile
179
185
if log .V (5 ).Enabled () {
180
186
vms , err := v .getVirtualMachinesInCluster (ctx , supervisorMachineCtx )
@@ -188,6 +194,112 @@ func (v *VmopMachineService) ReconcileNormal(ctx context.Context, machineCtx cap
188
194
// Set the VM state. Will get reset throughout the reconcile
189
195
supervisorMachineCtx .VSphereMachine .Status .VMStatus = vmwarev1 .VirtualMachineStatePending
190
196
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
+
191
303
// Check for the presence of an existing object
192
304
vmOperatorVM := & vmoprv1.VirtualMachine {}
193
305
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
208
320
}
209
321
210
322
// 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 {
212
324
v1beta1conditions .MarkFalse (supervisorMachineCtx .VSphereMachine , infrav1 .VMProvisionedCondition , vmwarev1 .VMCreationFailedReason , clusterv1beta1 .ConditionSeverityWarning ,
213
325
"failed to create or update VirtualMachine: %v" , err )
214
326
v1beta2conditions .Set (supervisorMachineCtx .VSphereMachine , metav1.Condition {
@@ -378,7 +490,7 @@ func (v *VmopMachineService) GetHostInfo(ctx context.Context, machineCtx capvcon
378
490
return vmOperatorVM .Status .Host , nil
379
491
}
380
492
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 {
382
494
// All Machine resources should define the version of Kubernetes to use.
383
495
if supervisorMachineCtx .Machine .Spec .Version == "" {
384
496
return errors .Errorf (
@@ -472,7 +584,7 @@ func (v *VmopMachineService) reconcileVMOperatorVM(ctx context.Context, supervis
472
584
}
473
585
474
586
// Assign the VM's labels.
475
- vmOperatorVM .Labels = getVMLabels (supervisorMachineCtx , vmOperatorVM .Labels )
587
+ vmOperatorVM .Labels = getVMLabels (supervisorMachineCtx , vmOperatorVM .Labels , affinityInfo )
476
588
477
589
addResourcePolicyAnnotations (supervisorMachineCtx , vmOperatorVM )
478
590
@@ -494,6 +606,15 @@ func (v *VmopMachineService) reconcileVMOperatorVM(ctx context.Context, supervis
494
606
vmOperatorVM = typedModified
495
607
}
496
608
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
+
497
618
// Make sure the VSphereMachine owns the VM Operator VirtualMachine.
498
619
if err := ctrlutil .SetControllerReference (supervisorMachineCtx .VSphereMachine , vmOperatorVM , v .Client .Scheme ()); err != nil {
499
620
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
735
856
736
857
if zone := supervisorMachineCtx .VSphereMachine .Spec .FailureDomain ; zonal && zone != nil {
737
858
topology := []map [string ]string {
738
- {kubeTopologyZoneLabelKey : * zone },
859
+ {corev1 . LabelTopologyZone : * zone },
739
860
}
740
861
b , err := json .Marshal (topology )
741
862
if err != nil {
@@ -777,7 +898,7 @@ func (v *VmopMachineService) addVolumes(ctx context.Context, supervisorMachineCt
777
898
}
778
899
779
900
// 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 {
781
902
if vmLabels == nil {
782
903
vmLabels = map [string ]string {}
783
904
}
@@ -791,7 +912,11 @@ func getVMLabels(supervisorMachineCtx *vmware.SupervisorMachineContext, vmLabels
791
912
792
913
// Get the labels that determine the VM's placement inside of a stretched
793
914
// 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 )
795
920
for k , v := range topologyLabels {
796
921
vmLabels [k ] = v
797
922
}
@@ -800,6 +925,9 @@ func getVMLabels(supervisorMachineCtx *vmware.SupervisorMachineContext, vmLabels
800
925
// resources associated with the target cluster.
801
926
vmLabels [clusterv1 .ClusterNameLabel ] = supervisorMachineCtx .GetClusterContext ().Cluster .Name
802
927
928
+ // Ensure the VM has the machine deployment name label
929
+ vmLabels [clusterv1 .MachineDeploymentNameLabel ] = supervisorMachineCtx .Machine .Labels [clusterv1 .MachineDeploymentNameLabel ]
930
+
803
931
return vmLabels
804
932
}
805
933
@@ -809,10 +937,16 @@ func getVMLabels(supervisorMachineCtx *vmware.SupervisorMachineContext, vmLabels
809
937
//
810
938
// and thus the code is optimized as such. However, in the future
811
939
// 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)
813
942
if fd := supervisorMachineCtx .VSphereMachine .Spec .FailureDomain ; fd != nil && * fd != "" {
814
943
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 ,
816
950
}
817
951
}
818
952
return nil
@@ -823,3 +957,16 @@ func getTopologyLabels(supervisorMachineCtx *vmware.SupervisorMachineContext) ma
823
957
func getMachineDeploymentNameForCluster (cluster * clusterv1.Cluster ) string {
824
958
return fmt .Sprintf ("%s-workers-0" , cluster .Name )
825
959
}
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