Skip to content

Commit 36d3beb

Browse files
committed
machineupdate
1 parent a1a97d6 commit 36d3beb

File tree

3 files changed

+371
-0
lines changed

3 files changed

+371
-0
lines changed

e2e/machine_migration_capi_authoritative_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
78
configv1 "github.com/openshift/api/config/v1"
89
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
910
mapiframework "github.com/openshift/cluster-api-actuator-pkg/pkg/framework"
@@ -12,6 +13,7 @@ import (
1213

1314
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
1415
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
16+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
1517
)
1618

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

e2e/machine_migration_helpers.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,101 @@ 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+
currentMAPIMachine, err := mapiframework.GetMachine(cl, machineName)
340+
Expect(err).ToNot(HaveOccurred())
341+
342+
currentCAPIMachine := capiframework.GetMachine(cl, machineName, capiframework.CAPINamespace)
343+
344+
state := &MachineState{
345+
MAPIGeneration: currentMAPIMachine.Generation,
346+
CAPIGeneration: currentCAPIMachine.Generation,
347+
}
348+
349+
// Find and store the synchronized condition's last transition time
350+
for _, condition := range currentMAPIMachine.Status.Conditions {
351+
if condition.Type == SynchronizedCondition {
352+
state.SynchronizedTime = &condition.LastTransitionTime
353+
break
354+
}
355+
}
356+
357+
return state
358+
}
359+
360+
// verifyMachineStateChanged verifies that:
361+
// 1. CAPI Machine generation remains unchanged (metadata-only changes don't bump spec generation)
362+
// 2. MAPI Machine generation has changed (spec.objectMeta was updated)
363+
// 3. Synchronized time has changed (indicating a new synchronization occurred)
364+
// This is used for CAPI authoritative scenarios where CAPI metadata changes are synced to MAPI spec.
365+
func verifyMachineStateChanged(cl client.Client, mapiMachine *mapiv1beta1.Machine, previousState *MachineState, authority mapiv1beta1.MachineAuthority, operation string) {
366+
// First verify CAPI Machine generation remains unchanged
367+
currentCAPIMachine := capiframework.GetMachine(cl, mapiMachine.Name, capiframework.CAPINamespace)
368+
By(fmt.Sprintf("Verifying CAPI Machine generation unchanged after %s (previous: %d, current: %d)", operation, previousState.CAPIGeneration, currentCAPIMachine.Generation))
369+
Expect(currentCAPIMachine.Generation).To(Equal(previousState.CAPIGeneration), "CAPI Machine generation should not change for metadata-only updates")
370+
371+
// Then verify MAPI Machine generation has changed
372+
By(fmt.Sprintf("Verifying MAPI Machine generation changed after %s (previous: %d, current: %d)", operation, previousState.MAPIGeneration, mapiMachine.Generation))
373+
Expect(mapiMachine.Generation).To(BeNumerically(">", previousState.MAPIGeneration), "MAPI Machine generation should increment when spec.objectMeta is updated")
374+
375+
// Verify synchronized time is updated
376+
var currentSynchronizedTime *metav1.Time
377+
for _, condition := range mapiMachine.Status.Conditions {
378+
if condition.Type == SynchronizedCondition {
379+
currentSynchronizedTime = &condition.LastTransitionTime
380+
break
381+
}
382+
}
383+
Expect(currentSynchronizedTime).ToNot(BeNil(), "Synchronized condition should exist")
384+
By(fmt.Sprintf("Verifying synchronized time changed after %s (previous: %v, current: %v)", operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time))
385+
Expect(currentSynchronizedTime.Time).To(BeTemporally(">", previousState.SynchronizedTime.Time), fmt.Sprintf("Synchronized time should change when syncing %s metadata to spec", operation))
386+
387+
// Verify status remains correct
388+
verifyMAPIMachineSynchronizedCondition(mapiMachine, authority)
389+
verifyMachineSynchronizedGeneration(cl, mapiMachine, authority)
390+
}
391+
392+
// verifyMachineStateUnchanged verifies that:
393+
// 1. CAPI Machine generation remains unchanged
394+
// 2. MAPI Machine generation remains unchanged
395+
// 3. Synchronized time remains unchanged
396+
// This is used for MAPI authoritative scenarios where MAPI metadata-only changes should not trigger any updates.
397+
func verifyMachineStateUnchanged(cl client.Client, mapiMachine *mapiv1beta1.Machine, previousState *MachineState, authority mapiv1beta1.MachineAuthority, operation string) {
398+
// Verify CAPI Machine generation remains unchanged
399+
currentCAPIMachine := capiframework.GetMachine(cl, mapiMachine.Name, capiframework.CAPINamespace)
400+
By(fmt.Sprintf("Verifying CAPI Machine generation unchanged after %s (previous: %d, current: %d)", operation, previousState.CAPIGeneration, currentCAPIMachine.Generation))
401+
Expect(currentCAPIMachine.Generation).To(Equal(previousState.CAPIGeneration), "CAPI Machine generation should not change for metadata-only updates")
402+
403+
// Verify MAPI Machine generation remains unchanged
404+
By(fmt.Sprintf("Verifying MAPI Machine generation unchanged after %s (previous: %d, current: %d)", operation, previousState.MAPIGeneration, mapiMachine.Generation))
405+
Expect(mapiMachine.Generation).To(Equal(previousState.MAPIGeneration), "MAPI Machine generation should not change for metadata-only updates")
406+
407+
// Verify synchronized time remains unchanged
408+
if previousState.SynchronizedTime != nil {
409+
var currentSynchronizedTime *metav1.Time
410+
for _, condition := range mapiMachine.Status.Conditions {
411+
if condition.Type == SynchronizedCondition {
412+
currentSynchronizedTime = &condition.LastTransitionTime
413+
break
414+
}
415+
}
416+
Expect(currentSynchronizedTime).ToNot(BeNil(), "Synchronized condition should exist")
417+
By(fmt.Sprintf("Verifying synchronized time unchanged after %s (previous: %v, current: %v)", operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time))
418+
Expect(currentSynchronizedTime.Time).To(Equal(previousState.SynchronizedTime.Time), "Synchronized time should not change for metadata-only updates")
419+
}
420+
421+
// Verify status remains correct
422+
verifyMAPIMachineSynchronizedCondition(mapiMachine, authority)
423+
verifyMachineSynchronizedGeneration(cl, mapiMachine, authority)
424+
}

0 commit comments

Comments
 (0)