Skip to content

Commit a4dd8b0

Browse files
committed
machineupdate
1 parent a1a97d6 commit a4dd8b0

File tree

3 files changed

+408
-0
lines changed

3 files changed

+408
-0
lines changed

e2e/machine_migration_capi_authoritative_test.go

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

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)