@@ -23,6 +23,7 @@ import (
2323 clusterv1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/cluster-api/core/v1beta1"
2424 awsv1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/cluster-api/infrastructure/v1beta2"
2525 corev1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/core/v1"
26+ machinev1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/machine/v1beta1"
2627 admissiontestutils "github.com/openshift/cluster-capi-operator/pkg/admissionpolicy/testutils"
2728
2829 admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
@@ -234,4 +235,104 @@ var _ = Describe("MachineSet VAP Tests", func() {
234235 }), timeout ).Should (MatchError (ContainSubstring (".readinessGates is a forbidden field" )))
235236 })
236237 })
238+
239+ Context ("Prevent authoritative MAPI MachineSet creation when same-named CAPI MachineSet exists" , func () {
240+ var mapiMachineSetBuilder machinev1resourcebuilder.MachineSetBuilder
241+ const vapName string = "openshift-prevent-authoritative-mapi-machineset-create-when-capi-exists"
242+
243+ BeforeEach (func () {
244+ By ("Waiting for VAP to be ready" )
245+ machineSetVap = & admissionregistrationv1.ValidatingAdmissionPolicy {}
246+ Eventually (k8sClient .Get (ctx , client.ObjectKey {Name : vapName }, machineSetVap ), timeout ).Should (Succeed ())
247+
248+ // Add UPDATE operation for easier testing (same as Machine tests)
249+ resourceRules := machineSetVap .Spec .MatchConstraints .ResourceRules
250+ Expect (resourceRules ).To (HaveLen (1 ))
251+ resourceRules [0 ].Operations = append (resourceRules [0 ].Operations , admissionregistrationv1 .Update )
252+
253+ Eventually (k .Update (machineSetVap , func () {
254+ admissiontestutils .AddSentinelValidation (machineSetVap )
255+ machineSetVap .Spec .MatchConstraints .ResourceRules = resourceRules
256+ })).Should (Succeed ())
257+
258+ Eventually (k .Object (machineSetVap ), timeout ).Should (
259+ HaveField ("Status.ObservedGeneration" , BeNumerically (">=" , 2 )),
260+ )
261+
262+ By ("Updating the VAP binding" )
263+ policyBinding = & admissionregistrationv1.ValidatingAdmissionPolicyBinding {}
264+ Eventually (k8sClient .Get (ctx , client.ObjectKey {
265+ Name : vapName }, policyBinding ), timeout ).Should (Succeed ())
266+
267+ Eventually (k .Update (policyBinding , func () {
268+ // paramNamespace=capiNamespace (CAPI resources are params)
269+ // targetNamespace=mapiNamespace (MAPI resources are validated)
270+ admissiontestutils .UpdateVAPBindingNamespaces (policyBinding , capiNamespace .GetName (), mapiNamespace .GetName ())
271+ }), timeout ).Should (Succeed ())
272+
273+ // Wait until the binding shows the patched values
274+ Eventually (k .Object (policyBinding ), timeout ).Should (
275+ SatisfyAll (
276+ HaveField ("Spec.MatchResources.NamespaceSelector.MatchLabels" ,
277+ HaveKeyWithValue ("kubernetes.io/metadata.name" ,
278+ mapiNamespace .GetName ())),
279+ ),
280+ )
281+
282+ By ("Creating throwaway MachineSet pair for sentinel validation" )
283+ mapiMachineSetBuilder = machinev1resourcebuilder .MachineSet ().
284+ WithNamespace (mapiNamespace .Name )
285+
286+ sentinelMachineSet := machinev1resourcebuilder .MachineSet ().
287+ WithNamespace (mapiNamespace .Name ).
288+ WithName ("sentinel-machineset" ).
289+ WithAuthoritativeAPI (mapiv1beta1 .MachineAuthorityClusterAPI ).
290+ Build ()
291+ Eventually (k8sClient .Create (ctx , sentinelMachineSet ), timeout ).Should (Succeed ())
292+
293+ capiSentinelMachineSet := clusterv1resourcebuilder .MachineSet ().
294+ WithName ("sentinel-machineset" ).
295+ WithNamespace (capiNamespace .Name ).
296+ Build ()
297+ Eventually (k8sClient .Create (ctx , capiSentinelMachineSet )).Should (Succeed ())
298+
299+ Eventually (k .Get (capiSentinelMachineSet )).Should (Succeed ())
300+
301+ admissiontestutils .VerifySentinelValidation (k , sentinelMachineSet , timeout )
302+ })
303+
304+ It ("Does not allow creation of a MAPI MachineSet with spec.authoritativeAPI: MachineAPI and the same name" , func () {
305+ By ("Create the CAPI MachineSet" )
306+ Eventually (k8sClient .Create (ctx , capiMachineSet )).Should (Succeed ())
307+
308+ By ("Create the MAPI MachineSet" )
309+ newMapiMachineSet := mapiMachineSetBuilder .
310+ WithName ("test-machineset" ).
311+ WithAuthoritativeAPI (mapiv1beta1 .MachineAuthorityMachineAPI ).
312+ Build ()
313+ Eventually (k8sClient .Create (ctx , newMapiMachineSet ), timeout ).Should (
314+ MatchError (ContainSubstring ("with spec.authoritativeAPI: MachineAPI because a Cluster API MachineSet with the same name already exists." )))
315+ })
316+
317+ It ("Does allow creation of a MAPI machineset with authoritative API ClusterAPI and the same name" , func () {
318+ By ("Create the CAPI MachineSet" )
319+ Eventually (k8sClient .Create (ctx , capiMachineSet )).Should (Succeed ())
320+
321+ By ("Create the MAPI MachineSet" )
322+ newMapiMachineSet := mapiMachineSetBuilder .
323+ WithName ("test-machineset" ).
324+ WithAuthoritativeAPI (mapiv1beta1 .MachineAuthorityClusterAPI ).
325+ Build ()
326+ Eventually (k8sClient .Create (ctx , newMapiMachineSet ), timeout ).Should (Succeed ())
327+ })
328+
329+ It ("Does allow creation of a MAPI MachineSet when no matching CAPI MachineSet exists (parameterNotFoundAction)" , func () {
330+ By ("Create the MAPI MachineSet without creating a CAPI MachineSet first" )
331+ newMapiMachineSet := mapiMachineSetBuilder .
332+ WithName ("no-capi-equivalent" ).
333+ WithAuthoritativeAPI (mapiv1beta1 .MachineAuthorityMachineAPI ).
334+ Build ()
335+ Eventually (k8sClient .Create (ctx , newMapiMachineSet ), timeout ).Should (Succeed ())
336+ })
337+ })
237338})
0 commit comments