Skip to content

Commit 2eb2ed9

Browse files
committed
OCPCLOUD-2566 -tests with VAP msgs
removing the part which is covered in other PRs co-author: cursor & coderabbitAI
1 parent eea205c commit 2eb2ed9

File tree

1 file changed

+298
-0
lines changed

1 file changed

+298
-0
lines changed

e2e/machine_migration_vap_tests.go

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package e2e
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
configv1 "github.com/openshift/api/config/v1"
10+
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
11+
mapiframework "github.com/openshift/cluster-api-actuator-pkg/pkg/framework"
12+
capiframework "github.com/openshift/cluster-capi-operator/e2e/framework"
13+
corev1 "k8s.io/api/core/v1"
14+
"k8s.io/apimachinery/pkg/runtime"
15+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
18+
)
19+
20+
// Constants for VAP testing - based on actual VAP: machine-api-machine-vap
21+
const (
22+
// Test values for MAPI machine updates
23+
testProviderID = "aws:///us-west-2a/i-test123456"
24+
testTaintValue = "test-taint-value"
25+
testLabelValue = "test-label-value"
26+
testInstanceType = "m5.xlarge"
27+
testAMIID = "ami-test123456"
28+
testAvailabilityZone = "us-west-2b"
29+
testSubnetID = "subnet-test123456"
30+
testSecurityGroupID = "sg-test123456"
31+
testCapacityReservationID = "cr-test123456"
32+
33+
// VAP error messages - from actual VAP policy
34+
vapSpecLockedMessage = "You may only modify spec.authoritativeAPI. Any other change inside .spec is not allowed. This is because status.authoritativeAPI is set to Cluster API."
35+
vapProtectedLabelMessage = "Cannot add, modify or delete any machine.openshift.io/* or kubernetes.io/* label. This is because status.authoritativeAPI is set to Cluster API."
36+
vapProtectedAnnotationMessage = "Cannot add, modify or delete any machine.openshift.io/* annotation. This is because status.authoritativeAPI is set to Cluster API."
37+
)
38+
39+
var _ = Describe("[sig-cluster-lifecycle][OCPFeatureGate:MachineAPIMigration] MAPI Machine VAP Tests", Ordered, func() {
40+
BeforeAll(func() {
41+
if platform != configv1.AWSPlatformType {
42+
Skip(fmt.Sprintf("Skipping tests on %s, this is only supported on AWS", platform))
43+
}
44+
45+
if !capiframework.IsMachineAPIMigrationEnabled(ctx, cl) {
46+
Skip("Skipping, this feature is only supported on MachineAPIMigration enabled clusters")
47+
}
48+
})
49+
50+
var _ = Describe("VAP: machine-api-machine-vap enforcement", Ordered, func() {
51+
var testMachineName = "machine-vap-test-capi-auth"
52+
var testMAPIMachine *mapiv1beta1.Machine
53+
var testCAPIMachine *clusterv1.Machine
54+
55+
BeforeAll(func() {
56+
// Create a MAPI machine with ClusterAPI authority to trigger VAP enforcement
57+
testMAPIMachine = createMAPIMachineWithAuthority(ctx, cl, testMachineName, mapiv1beta1.MachineAuthorityClusterAPI)
58+
59+
// The VAP requires a matching CAPI machine as parameter
60+
testCAPIMachine = capiframework.GetMachine(cl, testMachineName, capiframework.CAPINamespace)
61+
62+
// Wait until VAP match conditions are met
63+
Eventually(komega.Object(testMAPIMachine), capiframework.WaitLong, capiframework.RetryMedium).Should(
64+
WithTransform(func(m *mapiv1beta1.Machine) mapiv1beta1.MachineAuthority {
65+
return m.Status.AuthoritativeAPI
66+
}, Equal(mapiv1beta1.MachineAuthorityClusterAPI)),
67+
"VAP requires status.authoritativeAPI=Cluster API before enforcement",
68+
)
69+
70+
DeferCleanup(func() {
71+
By("Cleaning up machine resources")
72+
cleanupMachineResources(
73+
ctx,
74+
cl,
75+
[]*clusterv1.Machine{testCAPIMachine},
76+
[]*mapiv1beta1.Machine{testMAPIMachine},
77+
)
78+
})
79+
})
80+
81+
Context("spec field restrictions", func() {
82+
It("should prevent updating spec.providerID", func() {
83+
verifyUpdatePrevented(testMAPIMachine, func() {
84+
providerIDValue := testProviderID
85+
testMAPIMachine.Spec.ProviderID = &providerIDValue
86+
}, vapSpecLockedMessage)
87+
})
88+
89+
It("should prevent updating spec.taints", func() {
90+
verifyUpdatePrevented(testMAPIMachine, func() {
91+
testMAPIMachine.Spec.Taints = []corev1.Taint{{
92+
Key: "test-taint",
93+
Value: testTaintValue,
94+
Effect: corev1.TaintEffectNoSchedule,
95+
}}
96+
}, vapSpecLockedMessage)
97+
})
98+
99+
It("should prevent updating spec.metadata", func() {
100+
verifyUpdatePrevented(testMAPIMachine, func() {
101+
if testMAPIMachine.Spec.ObjectMeta.Labels == nil {
102+
testMAPIMachine.Spec.ObjectMeta.Labels = make(map[string]string)
103+
}
104+
testMAPIMachine.Spec.ObjectMeta.Labels["test-spec-label"] = testLabelValue
105+
}, vapSpecLockedMessage)
106+
})
107+
})
108+
109+
Context("protected label restrictions", func() {
110+
It("should prevent modifying machine.openshift.io/* labels", func() {
111+
verifyUpdatePrevented(testMAPIMachine, func() {
112+
if testMAPIMachine.Labels == nil {
113+
testMAPIMachine.Labels = make(map[string]string)
114+
}
115+
testMAPIMachine.Labels["machine.openshift.io/test-label"] = testLabelValue
116+
}, vapProtectedLabelMessage)
117+
})
118+
119+
It("should allow modifying non-protected labels", func() {
120+
verifyUpdateAllowed(testMAPIMachine, func() {
121+
if testMAPIMachine.Labels == nil {
122+
testMAPIMachine.Labels = make(map[string]string)
123+
}
124+
testMAPIMachine.Labels["test-label"] = "allowed-value"
125+
})
126+
})
127+
})
128+
129+
Context("protected annotation restrictions", func() {
130+
It("should prevent modifying machine.openshift.io/* annotations", func() {
131+
verifyUpdatePrevented(testMAPIMachine, func() {
132+
if testMAPIMachine.Annotations == nil {
133+
testMAPIMachine.Annotations = make(map[string]string)
134+
}
135+
testMAPIMachine.Annotations["machine.openshift.io/test-annotation"] = "test-value"
136+
}, vapProtectedAnnotationMessage)
137+
})
138+
139+
It("should allow modifying non-protected annotations", func() {
140+
verifyUpdateAllowed(testMAPIMachine, func() {
141+
if testMAPIMachine.Annotations == nil {
142+
testMAPIMachine.Annotations = make(map[string]string)
143+
}
144+
testMAPIMachine.Annotations["test-annotation"] = "allowed-value"
145+
})
146+
})
147+
})
148+
149+
Context("AWS provider spec field restrictions", func() {
150+
It("should prevent updating providerSpec.instanceType", func() {
151+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "instanceType", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
152+
providerSpec.InstanceType = testInstanceType
153+
}, vapSpecLockedMessage)
154+
})
155+
156+
It("should prevent updating providerSpec.amiID", func() {
157+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "amiID", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
158+
amiID := testAMIID
159+
if providerSpec.AMI.ID == nil {
160+
providerSpec.AMI.ID = &amiID
161+
} else {
162+
*providerSpec.AMI.ID = amiID
163+
}
164+
}, vapSpecLockedMessage)
165+
})
166+
167+
It("should prevent updating providerSpec.availabilityZone", func() {
168+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "availabilityZone", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
169+
providerSpec.Placement.AvailabilityZone = testAvailabilityZone
170+
}, vapSpecLockedMessage)
171+
})
172+
173+
It("should prevent updating providerSpec.subnetID", func() {
174+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "subnetID", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
175+
subnetID := testSubnetID
176+
providerSpec.Subnet = mapiv1beta1.AWSResourceReference{
177+
ID: &subnetID,
178+
}
179+
}, vapSpecLockedMessage)
180+
})
181+
182+
It("should prevent updating providerSpec.securityGroups", func() {
183+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "securityGroups", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
184+
sgID := testSecurityGroupID
185+
providerSpec.SecurityGroups = []mapiv1beta1.AWSResourceReference{{
186+
ID: &sgID,
187+
}}
188+
}, vapSpecLockedMessage)
189+
})
190+
191+
It("should prevent updating providerSpec.tags", func() {
192+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "tags", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
193+
providerSpec.Tags = []mapiv1beta1.TagSpecification{{
194+
Name: "test-key",
195+
Value: "test-value",
196+
}}
197+
}, vapSpecLockedMessage)
198+
})
199+
200+
It("should prevent updating providerSpec.capacityReservationId", func() {
201+
verifyAWSProviderSpecUpdatePrevented(testMAPIMachine, "capacityReservationId", func(providerSpec *mapiv1beta1.AWSMachineProviderConfig) {
202+
providerSpec.CapacityReservationID = testCapacityReservationID
203+
}, vapSpecLockedMessage)
204+
})
205+
})
206+
207+
Context("VAP match conditions verification", func() {
208+
It("should not apply VAP when authoritativeAPI is MachineAPI", func() {
209+
verifyVAPNotAppliedForMachineAPIAuthority()
210+
})
211+
})
212+
})
213+
})
214+
215+
// verifyUpdatePrevented verifies that a machine update is prevented by VAP
216+
func verifyUpdatePrevented(machine *mapiv1beta1.Machine, updateFunc func(), expectedError string) {
217+
By("Verifying that machine update is prevented by VAP")
218+
219+
Eventually(komega.Update(machine, updateFunc), capiframework.WaitMedium, capiframework.RetryMedium).Should(
220+
MatchError(ContainSubstring(expectedError)),
221+
"Expected machine update to be blocked by VAP")
222+
}
223+
224+
// verifyUpdateAllowed verifies that a machine update is allowed (not blocked by VAP)
225+
func verifyUpdateAllowed(machine *mapiv1beta1.Machine, updateFunc func()) {
226+
By("Verifying that machine update is allowed")
227+
228+
Eventually(komega.Update(machine, updateFunc), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(),
229+
"Expected machine update to succeed")
230+
}
231+
232+
// updateAWSMachineProviderSpec updates an AWS machine's provider spec using the provided update function
233+
func updateAWSMachineProviderSpec(machine *mapiv1beta1.Machine, updateFunc func(*mapiv1beta1.AWSMachineProviderConfig)) error {
234+
providerSpec := getAWSProviderSpecFromMachine(machine)
235+
236+
updateFunc(providerSpec)
237+
238+
modifiedRaw, err := json.Marshal(providerSpec)
239+
if err != nil {
240+
return fmt.Errorf("failed to marshal modified providerSpec: %v", err)
241+
}
242+
243+
machine.Spec.ProviderSpec.Value = &runtime.RawExtension{Raw: modifiedRaw}
244+
return nil
245+
}
246+
247+
// getAWSProviderSpecFromMachine extracts the AWS provider spec from a machine
248+
func getAWSProviderSpecFromMachine(machine *mapiv1beta1.Machine) *mapiv1beta1.AWSMachineProviderConfig {
249+
Expect(machine.Spec.ProviderSpec.Value).ToNot(BeNil(), "providerSpec should not be nil")
250+
251+
providerSpec := &mapiv1beta1.AWSMachineProviderConfig{}
252+
err := json.Unmarshal(machine.Spec.ProviderSpec.Value.Raw, providerSpec)
253+
Expect(err).ToNot(HaveOccurred(), "should successfully unmarshal providerSpec")
254+
255+
return providerSpec
256+
}
257+
258+
// verifyAWSProviderSpecUpdatePrevented verifies that AWS providerSpec field updates are prevented by VAP
259+
func verifyAWSProviderSpecUpdatePrevented(machine *mapiv1beta1.Machine, fieldName string, updateFunc func(*mapiv1beta1.AWSMachineProviderConfig), expectedError string) {
260+
By(fmt.Sprintf("Verifying that updating AWS providerSpec.%s is prevented by VAP", fieldName))
261+
262+
Eventually(func() error {
263+
// Get fresh copy to avoid conflicts
264+
freshMachine := &mapiv1beta1.Machine{}
265+
if err := cl.Get(ctx, client.ObjectKeyFromObject(machine), freshMachine); err != nil {
266+
return err
267+
}
268+
269+
if err := updateAWSMachineProviderSpec(freshMachine, updateFunc); err != nil {
270+
return err
271+
}
272+
273+
return cl.Update(ctx, freshMachine)
274+
}, capiframework.WaitMedium, capiframework.RetryMedium).Should(
275+
MatchError(ContainSubstring(expectedError)),
276+
"Expected AWS providerSpec.%s update to be blocked by VAP", fieldName)
277+
}
278+
279+
// verifyVAPNotAppliedForMachineAPIAuthority verifies that VAP is not applied when authoritativeAPI is MachineAPI
280+
func verifyVAPNotAppliedForMachineAPIAuthority() {
281+
By("Verifying that VAP is not applied when authoritativeAPI is MachineAPI")
282+
283+
// Create a test machine with MachineAPI authority
284+
testMachine := createMAPIMachineWithAuthority(ctx, cl, "vap-test-mapi-authority", mapiv1beta1.MachineAuthorityMachineAPI)
285+
286+
DeferCleanup(func() {
287+
By("Cleaning up test machine")
288+
mapiframework.DeleteMachines(ctx, cl, testMachine)
289+
})
290+
291+
// Verify we can update spec fields (VAP should not apply)
292+
Eventually(komega.Update(testMachine, func() {
293+
// Try to update a spec field - this should be allowed since VAP doesn't apply
294+
providerIDValue := testProviderID
295+
testMachine.Spec.ProviderID = &providerIDValue
296+
}), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(),
297+
"Expected spec update to succeed when authoritativeAPI is MachineAPI (VAP should not apply)")
298+
}

0 commit comments

Comments
 (0)