Skip to content

Commit 5e0d75b

Browse files
committed
machineupdate
1 parent a1a97d6 commit 5e0d75b

File tree

3 files changed

+398
-0
lines changed

3 files changed

+398
-0
lines changed

e2e/machine_migration_capi_authoritative_test.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package e2e
22

33
import (
44
"fmt"
5+
"time"
56

67
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
79
configv1 "github.com/openshift/api/config/v1"
810
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
911
mapiframework "github.com/openshift/cluster-api-actuator-pkg/pkg/framework"
@@ -12,6 +14,7 @@ import (
1214

1315
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
1416
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
17+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
1518
)
1619

1720
var _ = Describe("[sig-cluster-lifecycle][OCPFeatureGate:MachineAPIMigration] Machine Migration CAPI Authoritative Tests", Ordered, func() {
@@ -298,4 +301,161 @@ var _ = Describe("[sig-cluster-lifecycle][OCPFeatureGate:MachineAPIMigration] Ma
298301
})
299302
})*/
300303
})
304+
305+
var _ = Describe("Update CAPI Machine", Ordered, func() {
306+
var capiUpdateTestName = "machine-update-capi-test"
307+
var newMapiMachine *mapiv1beta1.Machine
308+
var newCapiMachine *clusterv1.Machine
309+
var previousState *MachineState
310+
Context("with spec.authoritativeAPI: ClusterAPI (both MAPI and CAPI machine exist)", func() {
311+
BeforeAll(func() {
312+
newMapiMachine = createMAPIMachineWithAuthority(ctx, cl, capiUpdateTestName, mapiv1beta1.MachineAuthorityClusterAPI)
313+
newCapiMachine = capiframework.GetMachine(cl, newMapiMachine.Name, capiframework.CAPINamespace)
314+
verifyMachineRunning(cl, newCapiMachine)
315+
316+
DeferCleanup(func() {
317+
By("Cleaning up machine resources")
318+
cleanupMachineResources(
319+
ctx,
320+
cl,
321+
[]*clusterv1.Machine{newCapiMachine},
322+
[]*mapiv1beta1.Machine{newMapiMachine},
323+
)
324+
})
325+
})
326+
327+
It("should add labels/annotations on CAPI machine", func() {
328+
// Capture state before the update
329+
previousState = captureMachineStateBeforeUpdate(cl, newMapiMachine.Name)
330+
331+
// Add a small delay to ensure timestamps are distinguishable
332+
time.Sleep(1 * time.Second)
333+
334+
Eventually(komega.Update(newCapiMachine, func() {
335+
if newCapiMachine.Labels == nil {
336+
newCapiMachine.Labels = make(map[string]string)
337+
}
338+
if newCapiMachine.Annotations == nil {
339+
newCapiMachine.Annotations = make(map[string]string)
340+
}
341+
newCapiMachine.Labels["test-capi-label"] = "test-capi-label-value"
342+
newCapiMachine.Annotations["test-capi-annotation"] = "test-capi-annotation-value"
343+
}), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to add CAPI Machine labels/annotations")
344+
})
345+
346+
It("should synchronize added labels/annotations to both metadata and spec on MAPI", func() {
347+
Eventually(komega.Object(newMapiMachine), capiframework.WaitMedium, capiframework.RetryMedium).Should(
348+
SatisfyAll(
349+
// Check metadata
350+
WithTransform(func(m *mapiv1beta1.Machine) string {
351+
return m.Labels["test-capi-label"]
352+
}, Equal("test-capi-label-value")),
353+
WithTransform(func(m *mapiv1beta1.Machine) string {
354+
return m.Annotations["test-capi-annotation"]
355+
}, Equal("test-capi-annotation-value")),
356+
// Check spec template metadata
357+
WithTransform(func(m *mapiv1beta1.Machine) string {
358+
return m.Spec.ObjectMeta.Labels["test-capi-label"]
359+
}, Equal("test-capi-label-value")),
360+
WithTransform(func(m *mapiv1beta1.Machine) string {
361+
return m.Spec.ObjectMeta.Annotations["test-capi-annotation"]
362+
}, Equal("test-capi-annotation-value")),
363+
),
364+
"Added labels should be copied to both metadata and spec on MAPI Machine",
365+
)
366+
})
367+
368+
It("should verify MAPI generation changed and synchronized time changed after syncing labels to spec", func() {
369+
Eventually(komega.Get(newMapiMachine), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to get updated MAPI Machine")
370+
371+
verifyMachineStateChanged(cl, newMapiMachine, previousState, mapiv1beta1.MachineAuthorityClusterAPI, "adding labels/annotations")
372+
})
373+
374+
It("should modify existing labels/annotations on CAPI machine", func() {
375+
// Capture state before the update
376+
previousState = captureMachineStateBeforeUpdate(cl, newMapiMachine.Name)
377+
378+
// Add a small delay to ensure timestamps are distinguishable
379+
time.Sleep(1 * time.Second)
380+
381+
Eventually(komega.Update(newCapiMachine, func() {
382+
newCapiMachine.Labels["test-capi-label"] = "modified-capi-label-value"
383+
newCapiMachine.Annotations["test-capi-annotation"] = "modified-capi-annotation-value"
384+
}), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to modify CAPI Machine labels/annotations")
385+
})
386+
387+
It("should synchronize modified labels/annotations to both metadata and spec on MAPI", func() {
388+
Eventually(komega.Object(newMapiMachine), capiframework.WaitMedium, capiframework.RetryMedium).Should(
389+
SatisfyAll(
390+
// Check metadata
391+
WithTransform(func(m *mapiv1beta1.Machine) string {
392+
return m.Labels["test-capi-label"]
393+
}, Equal("modified-capi-label-value")),
394+
WithTransform(func(m *mapiv1beta1.Machine) string {
395+
return m.Annotations["test-capi-annotation"]
396+
}, Equal("modified-capi-annotation-value")),
397+
// Check spec template metadata
398+
WithTransform(func(m *mapiv1beta1.Machine) string {
399+
return m.Spec.ObjectMeta.Labels["test-capi-label"]
400+
}, Equal("modified-capi-label-value")),
401+
WithTransform(func(m *mapiv1beta1.Machine) string {
402+
return m.Spec.ObjectMeta.Annotations["test-capi-annotation"]
403+
}, Equal("modified-capi-annotation-value")),
404+
),
405+
"Modified labels/annotations should be synchronized to both metadata and spec on MAPI Machine",
406+
)
407+
})
408+
409+
It("should verify MAPI generation changed and synchronized time changed after syncing modified labels to spec", func() {
410+
Eventually(komega.Get(newMapiMachine), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to get updated MAPI Machine")
411+
412+
verifyMachineStateChanged(cl, newMapiMachine, previousState, mapiv1beta1.MachineAuthorityClusterAPI, "modifying labels/annotations")
413+
})
414+
415+
It("should delete labels/annotations on CAPI machine", func() {
416+
// Capture state before the update
417+
previousState = captureMachineStateBeforeUpdate(cl, newMapiMachine.Name)
418+
419+
// Add a small delay to ensure timestamps are distinguishable
420+
time.Sleep(1 * time.Second)
421+
422+
Eventually(komega.Update(newCapiMachine, func() {
423+
delete(newCapiMachine.Labels, "test-capi-label")
424+
delete(newCapiMachine.Annotations, "test-capi-annotation")
425+
}), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to delete CAPI Machine labels/annotations")
426+
})
427+
428+
It("should synchronize label/annotation deletion to both metadata and spec on MAPI", func() {
429+
Eventually(komega.Object(newMapiMachine), capiframework.WaitMedium, capiframework.RetryMedium).Should(
430+
SatisfyAll(
431+
// Check metadata deletion
432+
WithTransform(func(m *mapiv1beta1.Machine) bool {
433+
_, exists := m.Labels["test-capi-label"]
434+
return exists
435+
}, BeFalse()),
436+
WithTransform(func(m *mapiv1beta1.Machine) bool {
437+
_, exists := m.Annotations["test-capi-annotation"]
438+
return exists
439+
}, BeFalse()),
440+
// Verify deleted labels are also removed from spec
441+
WithTransform(func(m *mapiv1beta1.Machine) bool {
442+
_, exists := m.Spec.ObjectMeta.Labels["test-capi-label"]
443+
return exists
444+
}, BeFalse()),
445+
WithTransform(func(m *mapiv1beta1.Machine) bool {
446+
_, exists := m.Spec.ObjectMeta.Annotations["test-capi-annotation"]
447+
return exists
448+
}, BeFalse()),
449+
),
450+
"Deleted labels/annotations should be synchronized to both metadata and spec on MAPI Machine",
451+
)
452+
})
453+
454+
It("should verify MAPI generation changed and synchronized time changed after syncing deleted labels to spec", func() {
455+
Eventually(komega.Get(newMapiMachine), capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to get updated MAPI Machine")
456+
457+
verifyMachineStateChanged(cl, newMapiMachine, previousState, mapiv1beta1.MachineAuthorityClusterAPI, "deleting labels/annotations")
458+
})
459+
})
460+
})
301461
})

e2e/machine_migration_helpers.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,105 @@ func verifyMachineSynchronizedGeneration(cl client.Client, mapiMachine *mapiv1be
324324
fmt.Sprintf("MAPI Machine SynchronizedGeneration should equal %s Machine Generation (%d)", authoritativeMachineType, expectedGeneration),
325325
)
326326
}
327+
328+
// MachineState holds the state of both MAPI and CAPI Machines before an update operation.
329+
type MachineState struct {
330+
MAPIGeneration int64
331+
CAPIGeneration int64
332+
SynchronizedTime *metav1.Time
333+
}
334+
335+
// captureMachineStateBeforeUpdate captures the current state (generation and synchronized time) of both MAPI and CAPI Machines
336+
// before an update operation. The captured state is later used by verifyMachineStateChanged or verifyMachineStateUnchanged
337+
// to verify whether the state has changed or remained the same after the operation.
338+
func captureMachineStateBeforeUpdate(cl client.Client, machineName string) *MachineState {
339+
var currentMAPIMachine *mapiv1beta1.Machine
340+
Eventually(func() error {
341+
var err error
342+
currentMAPIMachine, err = mapiframework.GetMachine(cl, machineName)
343+
return err
344+
}, capiframework.WaitMedium, capiframework.RetryMedium).Should(Succeed(), "Failed to get MAPI Machine before capturing state")
345+
346+
currentCAPIMachine := capiframework.GetMachine(cl, machineName, capiframework.CAPINamespace)
347+
348+
state := &MachineState{
349+
MAPIGeneration: currentMAPIMachine.Generation,
350+
CAPIGeneration: currentCAPIMachine.Generation,
351+
}
352+
353+
// Find and store the synchronized condition's last transition time
354+
for _, condition := range currentMAPIMachine.Status.Conditions {
355+
if condition.Type == SynchronizedCondition {
356+
state.SynchronizedTime = &condition.LastTransitionTime
357+
break
358+
}
359+
}
360+
361+
return state
362+
}
363+
364+
// verifyMachineStateChanged verifies that:
365+
// 1. CAPI Machine generation remains unchanged (metadata-only changes don't bump spec generation)
366+
// 2. MAPI Machine generation has changed (spec.objectMeta was updated)
367+
// 3. Synchronized time has changed (indicating a new synchronization occurred)
368+
// This is used for CAPI authoritative scenarios where CAPI metadata changes are synced to MAPI spec.
369+
func verifyMachineStateChanged(cl client.Client, mapiMachine *mapiv1beta1.Machine, previousState *MachineState, authority mapiv1beta1.MachineAuthority, operation string) {
370+
// First verify CAPI Machine generation remains unchanged
371+
currentCAPIMachine := capiframework.GetMachine(cl, mapiMachine.Name, capiframework.CAPINamespace)
372+
By(fmt.Sprintf("Verifying CAPI Machine generation unchanged after %s (previous: %d, current: %d)", operation, previousState.CAPIGeneration, currentCAPIMachine.Generation))
373+
Expect(currentCAPIMachine.Generation).To(Equal(previousState.CAPIGeneration), "CAPI Machine generation should not change for metadata-only updates")
374+
375+
// Then verify MAPI Machine generation has changed
376+
By(fmt.Sprintf("Verifying MAPI Machine generation changed after %s (previous: %d, current: %d)", operation, previousState.MAPIGeneration, mapiMachine.Generation))
377+
Expect(mapiMachine.Generation).To(BeNumerically(">", previousState.MAPIGeneration), "MAPI Machine generation should increment when spec.objectMeta is updated")
378+
379+
// Verify synchronized time is updated
380+
var currentSynchronizedTime *metav1.Time
381+
for _, condition := range mapiMachine.Status.Conditions {
382+
if condition.Type == SynchronizedCondition {
383+
currentSynchronizedTime = &condition.LastTransitionTime
384+
break
385+
}
386+
}
387+
Expect(currentSynchronizedTime).ToNot(BeNil(), "Synchronized condition should exist")
388+
By(fmt.Sprintf("Verifying synchronized time changed after %s (previous: %v, current: %v)", operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time))
389+
Expect(currentSynchronizedTime.Time).To(BeTemporally(">", previousState.SynchronizedTime.Time), fmt.Sprintf("Synchronized time should change when syncing %s metadata to spec", operation))
390+
391+
// Verify status remains correct
392+
verifyMAPIMachineSynchronizedCondition(mapiMachine, authority)
393+
verifyMachineSynchronizedGeneration(cl, mapiMachine, authority)
394+
}
395+
396+
// verifyMachineStateUnchanged verifies that:
397+
// 1. CAPI Machine generation remains unchanged
398+
// 2. MAPI Machine generation remains unchanged
399+
// 3. Synchronized time remains unchanged
400+
// This is used for MAPI authoritative scenarios where MAPI metadata-only changes should not trigger any updates.
401+
func verifyMachineStateUnchanged(cl client.Client, mapiMachine *mapiv1beta1.Machine, previousState *MachineState, authority mapiv1beta1.MachineAuthority, operation string) {
402+
// Verify CAPI Machine generation remains unchanged
403+
currentCAPIMachine := capiframework.GetMachine(cl, mapiMachine.Name, capiframework.CAPINamespace)
404+
By(fmt.Sprintf("Verifying CAPI Machine generation unchanged after %s (previous: %d, current: %d)", operation, previousState.CAPIGeneration, currentCAPIMachine.Generation))
405+
Expect(currentCAPIMachine.Generation).To(Equal(previousState.CAPIGeneration), "CAPI Machine generation should not change for metadata-only updates")
406+
407+
// Verify MAPI Machine generation remains unchanged
408+
By(fmt.Sprintf("Verifying MAPI Machine generation unchanged after %s (previous: %d, current: %d)", operation, previousState.MAPIGeneration, mapiMachine.Generation))
409+
Expect(mapiMachine.Generation).To(Equal(previousState.MAPIGeneration), "MAPI Machine generation should not change for metadata-only updates")
410+
411+
// Verify synchronized time remains unchanged
412+
if previousState.SynchronizedTime != nil {
413+
var currentSynchronizedTime *metav1.Time
414+
for _, condition := range mapiMachine.Status.Conditions {
415+
if condition.Type == SynchronizedCondition {
416+
currentSynchronizedTime = &condition.LastTransitionTime
417+
break
418+
}
419+
}
420+
Expect(currentSynchronizedTime).ToNot(BeNil(), "Synchronized condition should exist")
421+
By(fmt.Sprintf("Verifying synchronized time unchanged after %s (previous: %v, current: %v)", operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time))
422+
Expect(currentSynchronizedTime.Time).To(Equal(previousState.SynchronizedTime.Time), "Synchronized time should not change for metadata-only updates")
423+
}
424+
425+
// Verify status remains correct
426+
verifyMAPIMachineSynchronizedCondition(mapiMachine, authority)
427+
verifyMachineSynchronizedGeneration(cl, mapiMachine, authority)
428+
}

0 commit comments

Comments
 (0)