- 
                Notifications
    You must be signed in to change notification settings 
- Fork 48
OCPCLOUD-2992: machine update #400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| WalkthroughAdds machine state-tracking helpers to validate MAPI/CAPI synchronization during updates, including capture and verification functions. Introduces comprehensive end-to-end tests for MAPI machine update operations with label/annotation changes and synchronization validation. Adds Gomega import to test files. Note: duplicate test suites appear in both test files. Changes
 Sequence Diagram(s)sequenceDiagram
    participant Test as Test Case
    participant Helper as State Helper
    participant MAPI as MAPI Machine
    participant CAPI as CAPI Machine
    
    Test->>Helper: captureMachineStateBeforeUpdate
    Helper->>MAPI: fetch machine & status
    Helper->>CAPI: fetch machine & status
    Helper->>Test: return MachineState (pre-op)
    
    Test->>MAPI: perform operation (label/annotation change)
    Test->>Helper: wait & verify synchronization
    Helper->>CAPI: check synchronized to CAPI
    Helper->>Test: sync confirmed
    
    Test->>Helper: verifyMachineStateChanged/Unchanged
    Helper->>MAPI: fetch post-op status
    Helper->>CAPI: fetch post-op status
    Helper->>Helper: compare generations & times
    Helper->>Test: validation result (pass/fail)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 
 Poem
 Pre-merge checks and finishing touches❌ Failed checks (1 warning)
 ✅ Passed checks (2 passed)
 ✨ Finishing touches
 🧪 Generate unit tests (beta)
 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment  | 
| [APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. 
Needs approval from an approver in each of these files:
 Approvers can indicate their approval by writing  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
e2e/machine_migration_helpers.go (3)
328-333: Prefer value + presence flag over pointer for SynchronizedTimeUsing a pointer here (and later taking an address of a range copy) is fragile. Store the time value and a boolean presence flag instead; it’s simpler and avoids accidental aliasing.
type MachineState struct { MAPIGeneration int64 CAPIGeneration int64 - SynchronizedTime *metav1.Time + SynchronizedTime metav1.Time + HasSynchronizedTime bool }Follow-ups (outside this hunk):
- In captureMachineStateBeforeUpdate: set both SynchronizedTime and HasSynchronizedTime when the condition is found.
- In verify helpers: gate temporal assertions on HasSynchronizedTime and use SynchronizedTime directly.
339-356: Don’t take the address of a for-range copy; capture the value insteadThis loop takes the address of condition.LastTransitionTime where condition is a copy. Prefer copying the value into state (or use the boolean flag from the previous comment).
- for _, condition := range currentMAPIMachine.Status.Conditions { + for _, condition := range currentMAPIMachine.Status.Conditions { if condition.Type == SynchronizedCondition { - state.SynchronizedTime = &condition.LastTransitionTime + // value copy; no pointer to range variable + state.SynchronizedTime = condition.LastTransitionTime + state.HasSynchronizedTime = true break } }
408-419: DRY the “find synchronized time” scan into a tiny helperBoth verify functions repeat the same loop. Extract a small helper to read the synchronized condition’s LastTransitionTime and reuse it. Keeps tests terse and less error-prone.
// outside: helpers.go func getSynchronizedLastTransitionTime(m *mapiv1beta1.Machine) (*metav1.Time, bool) { for _, c := range m.Status.Conditions { if c.Type == SynchronizedCondition { t := c.LastTransitionTime return &t, true } } return nil, false }Then replace the loops with getSynchronizedLastTransitionTime(mapiMachine).
e2e/machine_migration_capi_authoritative_test.go (1)
325-341: Stabilize capture: wait for Synchronized condition before recording previous stateIf the synchronized condition isn’t set yet, previousState.SynchronizedTime may be nil and downstream assertions may skip/flake. Ensure it’s True before capture.
It("should add labels/annotations on CAPI machine", func() { - // Capture state before the update - previousState = captureMachineStateBeforeUpdate(cl, newMapiMachine.Name) + // Ensure sync condition is present, then capture state + verifyMAPIMachineSynchronizedCondition(newMapiMachine, mapiv1beta1.MachineAuthorityClusterAPI) + previousState = captureMachineStateBeforeUpdate(cl, newMapiMachine.Name) Eventually(func() error {Please run this suite once locally to confirm reduced flakes in the first “Update CAPI Machine” block.
e2e/machine_migration_mapi_authoritative_test.go (1)
229-248: Optional: add the same precondition before capture for consistencyAlign with the CAPI suite by waiting for the Synchronized condition before capturing; reduces any timing window variability.
It("should add labels/annotations on MAPI machine", func() { - // Capture state before the update + // Optional: ensure sync condition is present, then capture state + verifyMAPIMachineSynchronizedCondition(newMapiMachine, mapiv1beta1.MachineAuthorityMachineAPI) previousState = captureMachineStateBeforeUpdate(cl, newMapiMachine.Name)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to data retention organization setting
📒 Files selected for processing (3)
- e2e/machine_migration_capi_authoritative_test.go(2 hunks)
- e2e/machine_migration_helpers.go(1 hunks)
- e2e/machine_migration_mapi_authoritative_test.go(2 hunks)
| // Verify synchronized time is updated | ||
| var currentSynchronizedTime *metav1.Time | ||
| for _, condition := range mapiMachine.Status.Conditions { | ||
| if condition.Type == SynchronizedCondition { | ||
| currentSynchronizedTime = &condition.LastTransitionTime | ||
| break | ||
| } | ||
| } | ||
| Expect(currentSynchronizedTime).ToNot(BeNil(), "Synchronized condition should exist") | ||
| By(fmt.Sprintf("Verifying synchronized time changed after %s (previous: %v, current: %v)", operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time)) | ||
| Expect(currentSynchronizedTime.Time).To(BeTemporally(">", previousState.SynchronizedTime.Time), fmt.Sprintf("Synchronized time should change when syncing %s metadata to spec", operation)) | ||
|  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nil-deref risk: previousState.SynchronizedTime can be nil
If the synchronized condition wasn’t present when captured, this dereferences a nil pointer (both in By() and BeTemporally(">")). Guard it and still assert that current sync time is set.
- // Verify synchronized time is updated
- var currentSynchronizedTime *metav1.Time
+ // Verify synchronized time is updated
+ var currentSynchronizedTime *metav1.Time
   for _, condition := range mapiMachine.Status.Conditions {
     if condition.Type == SynchronizedCondition {
       currentSynchronizedTime = &condition.LastTransitionTime
       break
     }
   }
   Expect(currentSynchronizedTime).ToNot(BeNil(), "Synchronized condition should exist")
- By(fmt.Sprintf("Verifying synchronized time changed after %s (previous: %v, current: %v)", operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time))
- Expect(currentSynchronizedTime.Time).To(BeTemporally(">", previousState.SynchronizedTime.Time), fmt.Sprintf("Synchronized time should change when syncing %s metadata to spec", operation))
+ if previousState.SynchronizedTime != nil {
+   By(fmt.Sprintf(
+     "Verifying synchronized time changed after %s (previous: %v, current: %v)",
+     operation, previousState.SynchronizedTime.Time, currentSynchronizedTime.Time))
+   Expect(currentSynchronizedTime.Time).
+     To(BeTemporally(">", previousState.SynchronizedTime.Time),
+        fmt.Sprintf("Synchronized time should change when syncing %s metadata to spec", operation))
+ } else {
+   By(fmt.Sprintf("Previous synchronized time missing; asserting current time is set after %s", operation))
+   Expect(currentSynchronizedTime.Time.IsZero()).To(BeFalse(), "Synchronized time should be set after synchronization")
+ }Optional hardening: re-fetch mapiMachine at the start of this function to reduce reliance on the caller providing a fresh object.
| @huali9: This pull request references OCPCLOUD-2992 which is a valid jira issue. Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.21.0" version, but no target version was set. In response to this: 
 Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. | 
| @huali9: The following test failed, say  
 Full PR test history. Your PR dashboard. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. | 
| @huali9 can we use Komega for Eventually? | 
machine update e2e, I tested in my local and works @sunzhaohua2 @miyadav @damdo @theobarberbany PTAL, thanks!
Summary by CodeRabbit