1+ /*
2+ Copyright 2025 The Kubernetes Authors.
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+ */
16+
117package vmware
218
319import (
@@ -14,60 +30,117 @@ import (
1430 "k8s.io/apimachinery/pkg/runtime"
1531 "k8s.io/apimachinery/pkg/types"
1632 "k8s.io/client-go/tools/record"
17- vmwarev1 "sigs. k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1 "
33+ " k8s.io/utils/ptr "
1834 clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
1935 "sigs.k8s.io/cluster-api/util/conditions"
2036 ctrl "sigs.k8s.io/controller-runtime"
2137 "sigs.k8s.io/controller-runtime/pkg/client"
2238 "sigs.k8s.io/controller-runtime/pkg/client/fake"
2339 "sigs.k8s.io/controller-runtime/pkg/reconcile"
24- )
2540
26- var s = runtime .NewScheme ()
41+ vmwarev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1"
42+ )
2743
2844const (
2945 clusterName = "test-cluster"
46+ otherClusterName = "other-cluster"
3047 clusterNamespace = "test-ns"
3148 mdName1 = "md-worker-a"
3249 mdName2 = "md-worker-b"
3350 zoneA = "zone-a"
3451 zoneB = "zone-b"
3552)
3653
54+ // Helper function to create a MachineDeployment object.
55+ func newMachineDeployment (name , clusterName , clusterNS string , replicas * int32 ) * clusterv1.MachineDeployment {
56+ return & clusterv1.MachineDeployment {
57+ ObjectMeta : metav1.ObjectMeta {
58+ Name : name ,
59+ Namespace : clusterNS ,
60+ Labels : map [string ]string {clusterv1 .ClusterNameLabel : clusterName },
61+ },
62+ Spec : clusterv1.MachineDeploymentSpec {
63+ Replicas : replicas ,
64+ },
65+ }
66+ }
67+
68+ // Helper function to create a basic Cluster object used as input.
69+ func newTestCluster (name , namespace string ) * clusterv1.Cluster {
70+ return & clusterv1.Cluster {
71+ ObjectMeta : metav1.ObjectMeta {
72+ Name : name ,
73+ Namespace : namespace ,
74+ },
75+ }
76+ }
77+
3778func TestGetExpectedVSphereMachines (t * testing.T ) {
3879 g := NewWithT (t )
3980 ctx := context .Background ()
4081
82+ // Replica helper values
83+ r3 := int32 (3 )
84+ r5 := int32 (5 )
85+ r0 := int32 (0 )
86+
87+ // Cluster object used as input parameter
88+ targetCluster := newTestCluster (clusterName , clusterNamespace )
89+
90+ // Create MDs for the target cluster
91+ mdA := newMachineDeployment ("md-a" , clusterName , clusterNamespace , & r3 )
92+ mdB := newMachineDeployment ("md-b" , clusterName , clusterNamespace , & r5 )
93+ mdC_nil := newMachineDeployment ("md-c-nil" , clusterName , clusterNamespace , nil )
94+ mdD_zero := newMachineDeployment ("md-d-zero" , clusterName , clusterNamespace , & r0 )
95+
96+ // Create an MD for a different cluster (should be filtered)
97+ mdOtherCluster := newMachineDeployment ("md-other" , otherClusterName , clusterNamespace , & r5 )
98+
4199 tests := []struct {
42- name string
43- cluster * clusterv1.Cluster
44- expected int32
100+ name string
101+ initialObjects []client.Object
102+ expectedTotal int32
103+ wantErr bool
45104 }{
46105 {
47- name : "Defined topology with replicas" ,
48- cluster : newCluster (clusterName , clusterNamespace , true , 3 , 2 ),
49- expected : 5 ,
106+ name : "Success: Sum of two MDs (3 + 5 = 8)" ,
107+ initialObjects : []client.Object {mdA , mdB },
108+ expectedTotal : 8 ,
109+ wantErr : false ,
50110 },
51111 {
52- name : "Defined topology with zero replicas" ,
53- cluster : newCluster (clusterName , clusterNamespace , true , 0 , 0 ),
54- expected : 0 ,
112+ name : "Success: Includes nil and zero replicas (3 + 5 + 0 + 0 = 8)" ,
113+ initialObjects : []client.Object {mdA , mdB , mdC_nil , mdD_zero },
114+ expectedTotal : 8 ,
115+ wantErr : false ,
55116 },
56117 {
57- name : "Undefined topology" ,
58- cluster : func () * clusterv1.Cluster {
59- c := newCluster (clusterName , clusterNamespace , true , 1 , 1 )
60- c .Spec .Topology = clusterv1.Topology {}
61- return c
62- }(),
63- expected : 0 ,
118+ name : "Success: Filters MDs from other clusters" ,
119+ initialObjects : []client.Object {mdA , mdB , mdOtherCluster },
120+ expectedTotal : 8 ,
121+ wantErr : false ,
122+ },
123+ {
124+ name : "Success: No MachineDeployments found" ,
125+ initialObjects : []client.Object {},
126+ expectedTotal : 0 ,
127+ wantErr : false ,
64128 },
65129 }
66130
67131 for _ , tt := range tests {
68- fakeClient := fake .NewClientBuilder ().WithScheme (s ).WithObjects (tt .cluster ).Build ()
69132 t .Run (tt .name , func (t * testing.T ) {
70- g .Expect (getExpectedVSphereMachines (ctx , fakeClient , tt .cluster )).To (Equal (tt .expected ))
133+ scheme := runtime .NewScheme ()
134+ g .Expect (clusterv1 .AddToScheme (scheme )).To (Succeed ())
135+
136+ fakeClient := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects (tt .initialObjects ... ).Build ()
137+ total , err := getExpectedVSphereMachines (ctx , fakeClient , targetCluster )
138+ if tt .wantErr {
139+ g .Expect (err ).To (HaveOccurred ())
140+ } else {
141+ g .Expect (err ).NotTo (HaveOccurred ())
142+ g .Expect (total ).To (Equal (tt .expectedTotal ))
143+ }
71144 })
72145 }
73146}
@@ -76,6 +149,9 @@ func TestGetCurrentVSphereMachines(t *testing.T) {
76149 g := NewWithT (t )
77150 ctx := context .Background ()
78151
152+ scheme := runtime .NewScheme ()
153+ g .Expect (vmwarev1 .AddToScheme (scheme )).To (Succeed ())
154+
79155 // VM names are based on CAPI Machine names, not VSphereMachine names, but we use VSphereMachine here.
80156 vsm1 := newVSphereMachine ("vsm-1" , mdName1 , false , nil )
81157 vsm2 := newVSphereMachine ("vsm-2" , mdName2 , false , nil )
@@ -106,11 +182,11 @@ func TestGetCurrentVSphereMachines(t *testing.T) {
106182 }
107183
108184 for _ , tt := range tests {
109- t .Run (tt .name , func (t * testing.T ) {
110- fakeClient := fake .NewClientBuilder ().WithScheme (s ).WithObjects (tt .objects ... ).Build ()
185+ t .Run (tt .name , func (_ * testing.T ) {
186+ fakeClient := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects (tt .objects ... ).Build ()
111187 got , err := getCurrentVSphereMachines (ctx , fakeClient , clusterNamespace , clusterName )
112188 g .Expect (err ).NotTo (HaveOccurred ())
113- g .Expect (len ( got )) .To (Equal (tt .want ))
189+ g .Expect (got ).To (HaveLen (tt .want ))
114190
115191 // Check that the correct Machines are present
116192 if tt .want > 0 {
@@ -221,7 +297,7 @@ func TestGenerateVMGPlacementAnnotations(t *testing.T) {
221297 }
222298
223299 for _ , tt := range tests {
224- t .Run (tt .name , func (t * testing.T ) {
300+ t .Run (tt .name , func (_ * testing.T ) {
225301 ctx := ctrl .LoggerInto (context .Background (), ctrl .LoggerFrom (context .Background ()))
226302
227303 got , err := GenerateVMGPlacementAnnotations (ctx , tt .vmg , tt .machineDeployments )
@@ -240,10 +316,14 @@ func TestVirtualMachineGroupReconciler_ReconcileFlow(t *testing.T) {
240316 g := NewWithT (t )
241317 ctx := context .Background ()
242318
319+ scheme := runtime .NewScheme ()
320+ g .Expect (clusterv1 .AddToScheme (scheme )).To (Succeed ())
321+ g .Expect (vmwarev1 .AddToScheme (scheme )).To (Succeed ())
322+
243323 // Initial objects for the successful VMG creation path (Expected: 1, Current: 1)
244324 cluster := newCluster (clusterName , clusterNamespace , true , 1 , 0 )
245325 vsm1 := newVSphereMachine ("vsm-1" , mdName1 , false , nil )
246- md1 := newMachineDeployment (mdName1 )
326+ md1 := newMachineDeployment (mdName1 , clusterName , clusterNamespace , ptr . To ( int32 ( 1 )) )
247327
248328 tests := []struct {
249329 name string
@@ -312,8 +392,8 @@ func TestVirtualMachineGroupReconciler_ReconcileFlow(t *testing.T) {
312392 }
313393
314394 for _ , tt := range tests {
315- t .Run (tt .name , func (t * testing.T ) {
316- fakeClient := fake .NewClientBuilder ().WithScheme (s ).WithObjects (tt .initialObjects ... ).Build ()
395+ t .Run (tt .name , func (_ * testing.T ) {
396+ fakeClient := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects (tt .initialObjects ... ).Build ()
317397 reconciler := & VirtualMachineGroupReconciler {
318398 Client : fakeClient ,
319399 Recorder : record .NewFakeRecorder (1 ),
@@ -347,7 +427,7 @@ func TestVirtualMachineGroupReconciler_ReconcileFlow(t *testing.T) {
347427 }
348428}
349429
350- // Helper function to create a basic Cluster object
430+ // Helper function to create a basic Cluster object.
351431func newCluster (name , namespace string , initialized bool , replicasMD1 , replicasMD2 int32 ) * clusterv1.Cluster {
352432 cluster := & clusterv1.Cluster {
353433 ObjectMeta : metav1.ObjectMeta {
@@ -375,7 +455,7 @@ func newCluster(name, namespace string, initialized bool, replicasMD1, replicasM
375455 return cluster
376456}
377457
378- // Helper function to create a VSphereMachine (worker, owned by a CAPI Machine)
458+ // Helper function to create a VSphereMachine (worker, owned by a CAPI Machine).
379459func newVSphereMachine (name , mdName string , deleted bool , namingStrategy * vmwarev1.VirtualMachineNamingStrategy ) * vmwarev1.VSphereMachine {
380460 vsm := & vmwarev1.VSphereMachine {
381461 ObjectMeta : metav1.ObjectMeta {
@@ -391,12 +471,13 @@ func newVSphereMachine(name, mdName string, deleted bool, namingStrategy *vmware
391471 },
392472 }
393473 if deleted {
474+ vsm .Finalizers = []string {"test.finalizer.0" }
394475 vsm .DeletionTimestamp = & metav1.Time {Time : time .Now ()}
395476 }
396477 return vsm
397478}
398479
399- // Helper function to create a VMG member status with placement info
480+ // Helper function to create a VMG member status with placement info.
400481func newVMGMemberStatus (name , kind string , isPlacementReady bool , zone string ) vmoprv1.VirtualMachineGroupMemberStatus {
401482 memberStatus := vmoprv1.VirtualMachineGroupMemberStatus {
402483 Name : name ,
@@ -412,14 +493,3 @@ func newVMGMemberStatus(name, kind string, isPlacementReady bool, zone string) v
412493 }
413494 return memberStatus
414495}
415-
416- // Helper function to create a MachineDeployment (for listing MD names)
417- func newMachineDeployment (name string ) * clusterv1.MachineDeployment {
418- return & clusterv1.MachineDeployment {
419- ObjectMeta : metav1.ObjectMeta {
420- Name : name ,
421- Namespace : clusterNamespace ,
422- Labels : map [string ]string {clusterv1 .ClusterNameLabel : clusterName },
423- },
424- }
425- }
0 commit comments