Skip to content

Commit c983b51

Browse files
Merge pull request #405 from theobarberbany/tb/validate-creation-mapi-machinesets
OCPCLOUD-3188: Prevent MAPI MachineSet creation when CAPI MachineSet with same name exists
2 parents 5f7410a + 2b33cbc commit c983b51

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

manifests/0000_30_cluster-api_09_admission-policies.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,53 @@ data:
354354
---
355355
apiVersion: admissionregistration.k8s.io/v1
356356
kind: ValidatingAdmissionPolicy
357+
metadata:
358+
name: openshift-prevent-authoritative-mapi-machineset-create-when-capi-exists
359+
spec:
360+
failurePolicy: Fail
361+
paramKind:
362+
apiVersion: cluster.x-k8s.io/v1beta1
363+
kind: MachineSet
364+
matchConstraints:
365+
resourceRules:
366+
- apiGroups: ["machine.openshift.io"]
367+
apiVersions: ["*"]
368+
operations: ["CREATE"]
369+
resources: ["machinesets"]
370+
# Requests must satisfy every matchCondition to reach the validations
371+
matchConditions:
372+
- name: check-param-match
373+
expression: 'object.metadata.name == params.metadata.name'
374+
# All validations must evaluate to true
375+
validations:
376+
# Only allow creation of a Machine API MachineSet with the same name as an
377+
# existing CAPI one if spec.authoritativeAPI == ClusterAPI
378+
- expression: 'object.spec.authoritativeAPI == "ClusterAPI"'
379+
messageExpression: "'Can\\'t create Machine API MachineSet ' + object.metadata.name + ' with spec.authoritativeAPI: ' + object.spec.?authoritativeAPI.orValue('<unset>') + ' because a Cluster API MachineSet with the same name already exists.'"
380+
---
381+
apiVersion: admissionregistration.k8s.io/v1
382+
kind: ValidatingAdmissionPolicyBinding
383+
metadata:
384+
name: openshift-prevent-authoritative-mapi-machineset-create-when-capi-exists
385+
spec:
386+
matchResources:
387+
namespaceSelector:
388+
matchLabels:
389+
kubernetes.io/metadata.name: openshift-machine-api
390+
paramRef:
391+
namespace: openshift-cluster-api
392+
# We 'Allow' here as we don't want to block MAPI MachineSet
393+
# functionality when no CAPI machineset (param) exists.
394+
# This might happen when the synchronisation controller first
395+
# is installed or when an unmigrateable machineset is encountered.
396+
parameterNotFoundAction: Allow
397+
selector: {}
398+
policyName: openshift-prevent-authoritative-mapi-machineset-create-when-capi-exists
399+
validationActions:
400+
- Deny
401+
---
402+
apiVersion: admissionregistration.k8s.io/v1
403+
kind: ValidatingAdmissionPolicy
357404
metadata:
358405
name: openshift-prevent-migration-when-machine-updating
359406
spec:

pkg/controllers/machinesetsync/machineset_vap_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)