Skip to content

Commit f57cd5c

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 f57cd5c

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

e2e/machine_migration_vap_tests.go

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

0 commit comments

Comments
 (0)