Skip to content

Commit 661e30e

Browse files
Merge pull request #5279 from umohnani8/build-2
OCPBUGS-61114: Fix ImageBuildDegraded Status updates
2 parents ca0c19d + e56dd25 commit 661e30e

File tree

3 files changed

+271
-49
lines changed

3 files changed

+271
-49
lines changed

pkg/controller/build/reconciler.go

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -263,56 +263,20 @@ func (b *buildReconciler) updateMachineOSBuild(ctx context.Context, old, current
263263
}
264264

265265
if !oldState.IsBuildFailure() && curState.IsBuildFailure() {
266-
klog.Infof("MachineOSBuild %s failed, leaving ephemeral objects in place for inspection and setting BuildDegraded condition", current.Name)
267-
268-
// Before setting BuildDegraded, check if another MachineOSBuild is active or if this one has been superseded
269-
mosbList, err := b.getMachineOSBuildsForMachineOSConfig(mosc)
270-
if err != nil {
271-
return fmt.Errorf("could not get MachineOSBuilds for MachineOSConfig %q: %w", mosc.Name, err)
272-
}
273-
274-
// Check if there are any newer or active builds that would supersede this failure
275-
hasActiveBuild := false
276-
isCurrentBuildStale := false
277-
for _, mosb := range mosbList {
278-
// Skip the current failed build
279-
if mosb.Name == current.Name {
280-
continue
281-
}
282-
mosbState := ctrlcommon.NewMachineOSBuildState(mosb)
283-
// Check if there's an active build (building, prepared, or succeeded)
284-
if mosbState.IsBuilding() || mosbState.IsBuildPrepared() || mosbState.IsBuildSuccess() {
285-
hasActiveBuild = true
286-
klog.Infof("Found active MachineOSBuild %s, skipping BuildDegraded condition for failed build %s", mosb.Name, current.Name)
287-
break
288-
}
289-
}
290-
291-
// Also check if the failed MachineOSBuild is no longer referenced by the MachineOSConfig
292-
if mosc.Status.CurrentImagePullSpec != "" && !isMachineOSBuildCurrentForMachineOSConfigWithPullspec(mosc, current) {
293-
isCurrentBuildStale = true
294-
klog.Infof("Failed MachineOSBuild %s is no longer current for MachineOSConfig %s, skipping BuildDegraded condition", current.Name, mosc.Name)
295-
}
296-
297-
// Only set BuildDegraded if there are no active builds and this build is still current
298-
if hasActiveBuild || isCurrentBuildStale {
299-
klog.Infof("Skipping BuildDegraded condition for failed MachineOSBuild %s (hasActiveBuild=%v, isStale=%v)", current.Name, hasActiveBuild, isCurrentBuildStale)
300-
return nil
301-
}
266+
klog.Infof("MachineOSBuild %s failed, leaving ephemeral objects in place for inspection", current.Name)
302267

303268
mcp, err := b.machineConfigPoolLister.Get(mosc.Spec.MachineConfigPool.Name)
304269
if err != nil {
305270
return fmt.Errorf("could not get MachineConfigPool from MachineOSConfig %q: %w", mosc.Name, err)
306271
}
307272

308-
// Set BuildDegraded condition
309-
buildError := getBuildErrorFromMOSB(current)
310-
return b.syncBuildFailureStatus(ctx, mcp, buildError, current.Name)
273+
// Always update ImageBuildDegraded condition based on current active build status
274+
return b.updateImageBuildDegradedCondition(ctx, mcp, mosc)
311275
}
312276

313277
// If the build was successful, clean up the build objects and propagate the
314278
// final image pushspec onto the MachineOSConfig object.
315-
// Also clear BuildDegraded condition if it was set due to a previously failed build
279+
// Also update BuildDegraded condition based on current active build status
316280
if !oldState.IsBuildSuccess() && curState.IsBuildSuccess() {
317281
klog.Infof("MachineOSBuild %s succeeded, cleaning up all ephemeral objects used for the build", current.Name)
318282

@@ -321,9 +285,9 @@ func (b *buildReconciler) updateMachineOSBuild(ctx context.Context, old, current
321285
return fmt.Errorf("could not get MachineConfigPool from MachineOSConfig %q: %w", mosc.Name, err)
322286
}
323287

324-
// Clear BuildDegraded condition if set
325-
if err := b.syncBuildSuccessStatus(ctx, mcp); err != nil {
326-
klog.Errorf("Failed to clear BuildDegraded condition for pool %s: %v", mcp.Name, err)
288+
// Update BuildDegraded condition based on current active build status
289+
if err := b.updateImageBuildDegradedCondition(ctx, mcp, mosc); err != nil {
290+
klog.Errorf("Failed to update ImageBuildDegraded condition for pool %s: %v", mcp.Name, err)
327291
}
328292

329293
// Clean up ephemeral objects
@@ -369,6 +333,18 @@ func (b *buildReconciler) updateMachineOSConfigStatus(ctx context.Context, mosc
369333
klog.Infof("Updated annotations on MachineOSConfig %q", mosc.Name)
370334

371335
mosc = updatedMosc
336+
337+
// When annotations are updated (new build starts), also update observedGeneration
338+
// to signal that the controller is processing the current generation
339+
if mosc.Status.ObservedGeneration != mosc.GetGeneration() {
340+
mosc.Status.ObservedGeneration = mosc.GetGeneration()
341+
_, err = b.mcfgclient.MachineconfigurationV1().MachineOSConfigs().UpdateStatus(ctx, mosc, metav1.UpdateOptions{})
342+
if err != nil {
343+
klog.Errorf("Failed to update observedGeneration on MachineOSConfig %q: %v", mosc.Name, err)
344+
} else {
345+
klog.Infof("Updated observedGeneration on MachineOSConfig %q to %d", mosc.Name, mosc.GetGeneration())
346+
}
347+
}
372348
}
373349

374350
// Skip the status update if digest image pushspec hasn't been set yet.
@@ -457,14 +433,14 @@ func (b *buildReconciler) startBuild(ctx context.Context, mosb *mcfgv1.MachineOS
457433
return fmt.Errorf("could not update MachineOSConfig %q status for MachineOSBuild %q: %w", mosc.Name, mosb.Name, err)
458434
}
459435

460-
// Initialize BuildDegraded condition to False when build starts
436+
// Update BuildDegraded condition based on current active build status when build starts
461437
mcp, err := b.machineConfigPoolLister.Get(mosc.Spec.MachineConfigPool.Name)
462438
if err != nil {
463439
return fmt.Errorf("could not get MachineConfigPool from MachineOSConfig %q: %w", mosc.Name, err)
464440
}
465441

466-
if err := b.initializeBuildDegradedCondition(ctx, mcp); err != nil {
467-
klog.Errorf("Failed to initialize BuildDegraded condition for pool %s: %v", mcp.Name, err)
442+
if err := b.updateImageBuildDegradedCondition(ctx, mcp, mosc); err != nil {
443+
klog.Errorf("Failed to update ImageBuildDegraded condition for pool %s: %v", mcp.Name, err)
468444
}
469445

470446
return nil
@@ -1633,3 +1609,79 @@ func (b *buildReconciler) syncBuildFailureStatus(ctx context.Context, pool *mcfg
16331609
}
16341610
return buildErr
16351611
}
1612+
1613+
// getCurrentBuild finds the currently active (most relevant) build from a list of MachineOSBuilds
1614+
// Priority: 1) Currently referenced by MOSC, 2) Building/Prepared, 3) Most recent
1615+
func (b *buildReconciler) getCurrentBuild(mosc *mcfgv1.MachineOSConfig, mosbList []*mcfgv1.MachineOSBuild) *mcfgv1.MachineOSBuild {
1616+
var activeBuild *mcfgv1.MachineOSBuild
1617+
var mostRecentBuild *mcfgv1.MachineOSBuild
1618+
1619+
// First, look for the build currently referenced by the MachineOSConfig
1620+
for _, mosb := range mosbList {
1621+
if isMachineOSBuildCurrentForMachineOSConfig(mosc, mosb) {
1622+
activeBuild = mosb
1623+
break
1624+
}
1625+
}
1626+
1627+
// If no current build found, look for active builds (building/prepared)
1628+
if activeBuild == nil {
1629+
for _, mosb := range mosbList {
1630+
mosbState := ctrlcommon.NewMachineOSBuildState(mosb)
1631+
if mosbState.IsBuilding() || mosbState.IsBuildPrepared() {
1632+
activeBuild = mosb
1633+
break
1634+
}
1635+
}
1636+
}
1637+
1638+
// Keep track of the most recent build regardless
1639+
for _, mosb := range mosbList {
1640+
if mostRecentBuild == nil || mosb.CreationTimestamp.After(mostRecentBuild.CreationTimestamp.Time) {
1641+
mostRecentBuild = mosb
1642+
}
1643+
}
1644+
1645+
// If still no active build, use the most recent one
1646+
if activeBuild == nil {
1647+
activeBuild = mostRecentBuild
1648+
}
1649+
1650+
return activeBuild
1651+
}
1652+
1653+
// updateImageBuildDegradedCondition examines all MachineOSBuilds for the MachineOSConfig
1654+
// and sets the ImageBuildDegraded condition based on the status of the currently active build
1655+
func (b *buildReconciler) updateImageBuildDegradedCondition(ctx context.Context, pool *mcfgv1.MachineConfigPool, mosc *mcfgv1.MachineOSConfig) error {
1656+
mosbList, err := b.getMachineOSBuildsForMachineOSConfig(mosc)
1657+
if err != nil {
1658+
return fmt.Errorf("could not get MachineOSBuilds for MachineOSConfig %q: %w", mosc.Name, err)
1659+
}
1660+
1661+
// Find the currently active build
1662+
activeBuild := b.getCurrentBuild(mosc, mosbList)
1663+
1664+
// If no builds exist at all, clear any existing BuildDegraded condition
1665+
if activeBuild == nil {
1666+
return b.syncBuildSuccessStatus(ctx, pool)
1667+
}
1668+
1669+
// Update condition based on the active build's status
1670+
activeState := ctrlcommon.NewMachineOSBuildState(activeBuild)
1671+
1672+
switch {
1673+
case activeState.IsBuildFailure():
1674+
// Set BuildDegraded=True for failed builds
1675+
buildError := getBuildErrorFromMOSB(activeBuild)
1676+
return b.syncBuildFailureStatus(ctx, pool, buildError, activeBuild.Name)
1677+
case activeState.IsBuildSuccess():
1678+
// Clear BuildDegraded=False for successful builds
1679+
return b.syncBuildSuccessStatus(ctx, pool)
1680+
case activeState.IsBuilding(), activeState.IsBuildPrepared():
1681+
// Clear BuildDegraded=False for builds in progress (allow retry after previous failure)
1682+
return b.initializeBuildDegradedCondition(ctx, pool)
1683+
}
1684+
1685+
// For any other states, don't change the condition
1686+
return nil
1687+
}

pkg/controller/node/status.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ func (ctrl *Controller) calculateStatus(mcns []*mcfgv1.MachineConfigNode, cconfi
247247
// if the pool is paused or true if the pool is not paused and the PIS is not degraded
248248
allUpdated := updatedMachineCount == totalMachineCount &&
249249
readyMachineCount == totalMachineCount &&
250-
unavailableMachineCount == 0
250+
unavailableMachineCount == 0 &&
251+
!isLayeredPoolBuilding(isLayeredPool, mosc, mosb)
251252
if allUpdated {
252253
//TODO: update api to only have one condition regarding status of update.
253254
updatedMsg := fmt.Sprintf("All nodes are updated with %s", getPoolUpdateLine(pool, mosc, isLayeredPool))
@@ -504,3 +505,35 @@ func isPinnedImageSetsInProgressForPool(pool *mcfgv1.MachineConfigPool) bool {
504505
}
505506
return false
506507
}
508+
509+
// isLayeredPoolBuilding checks if a layered pool has an active build or failed build that would
510+
// make nodes not truly "updated" even if they have the current machine config
511+
func isLayeredPoolBuilding(isLayeredPool bool, mosc *mcfgv1.MachineOSConfig, mosb *mcfgv1.MachineOSBuild) bool {
512+
if !isLayeredPool || mosc == nil || mosb == nil {
513+
return false
514+
}
515+
516+
mosbState := ctrlcommon.NewMachineOSBuildState(mosb)
517+
518+
// Check if there's an active build (building or prepared)
519+
if mosbState.IsBuilding() || mosbState.IsBuildPrepared() {
520+
return true
521+
}
522+
523+
// Check if there's a failed build - this means the update attempt failed
524+
// so nodes should not be considered "updated"
525+
if mosbState.IsBuildFailure() {
526+
return true
527+
}
528+
529+
// Check if there's a successful build that nodes haven't applied yet
530+
// This happens when a build completes but the MOSC status hasn't been updated
531+
// or nodes haven't picked up the new image yet
532+
if mosbState.IsBuildSuccess() && mosb.Status.DigestedImagePushSpec != "" {
533+
// If the successful build's image differs from what MOSC thinks is current,
534+
// then nodes are not truly updated to the latest successful build
535+
return string(mosb.Status.DigestedImagePushSpec) != string(mosc.Status.CurrentImagePullSpec)
536+
}
537+
538+
return false
539+
}

test/e2e-ocl/onclusterlayering_test.go

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,13 +1452,150 @@ func TestImageBuildDegradedOnFailureAndClearedOnBuildStart(t *testing.T) {
14521452
moscChangeMosb := buildrequest.NewMachineOSBuildFromAPIOrDie(ctx, cs.GetKubeclient(), updated, mcp)
14531453

14541454
// Wait for the second build to start
1455-
waitForBuildToStart(t, cs, moscChangeMosb)
1455+
secondMosb := waitForBuildToStart(t, cs, moscChangeMosb)
1456+
t.Logf("Second build started successfully: %s", secondMosb.Name)
14561457

14571458
// Wait for and verify ImageBuildDegraded condition is False after the new build starts.
1459+
// The condition should be cleared when the build starts.
14581460
degradedCondition = waitForImageBuildDegradedCondition(ctx, t, cs, layeredMCPName, corev1.ConditionFalse)
14591461
require.NotNil(t, degradedCondition, "ImageBuildDegraded condition should still be present")
14601462
assert.Equal(t, string(mcfgv1.MachineConfigPoolBuilding), degradedCondition.Reason, "ImageBuildDegraded reason should be Building")
1461-
t.Logf("ImageBuildDegraded condition correctly cleared to False with message: %s", degradedCondition.Message)
1463+
t.Logf("ImageBuildDegraded condition correctly cleared to False when build started with message: %s", degradedCondition.Message)
1464+
1465+
// Wait for the second build to complete successfully
1466+
finishedBuild := waitForBuildToComplete(t, cs, secondMosb)
1467+
t.Logf("Second build completed successfully: %s", finishedBuild.Name)
1468+
1469+
// Wait for the MachineOSConfig to get the new pullspec, which indicates full reconciliation
1470+
waitForMOSCToGetNewPullspec(ctx, t, cs, mosc.Name, string(finishedBuild.Status.DigestedImagePushSpec))
1471+
1472+
// Wait for and verify ImageBuildDegraded condition is False with reason BuildSucceeded
1473+
degradedCondition = waitForImageBuildDegradedCondition(ctx, t, cs, layeredMCPName, corev1.ConditionFalse)
1474+
require.NotNil(t, degradedCondition, "ImageBuildDegraded condition should still be present")
1475+
assert.Equal(t, string(mcfgv1.MachineConfigPoolBuildSuccess), degradedCondition.Reason, "ImageBuildDegraded reason should be BuildSuccess")
1476+
t.Logf("ImageBuildDegraded condition correctly set to False when build succeeded with message: %s", degradedCondition.Message)
1477+
1478+
// Verify MCP status is correct after successful build and full reconciliation
1479+
successMcp, err := cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, layeredMCPName, metav1.GetOptions{})
1480+
require.NoError(t, err)
1481+
1482+
// After successful build completion and full reconciliation, MCP should show:
1483+
// Updated=True, Updating=False, Degraded=False, ImageBuildDegraded=False
1484+
kubeassert.Eventually().MachineConfigPoolReachesState(successMcp, func(mcp *mcfgv1.MachineConfigPool, err error) (bool, error) {
1485+
if err != nil {
1486+
return false, err
1487+
}
1488+
// Return false (keep polling) if conditions don't match expected state
1489+
if !apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated) ||
1490+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdating) ||
1491+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolDegraded) ||
1492+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolImageBuildDegraded) {
1493+
return false, nil
1494+
}
1495+
1496+
// Return true when expected state is reached
1497+
t.Logf("MCP status after successful build - Updated: %v, Updating: %v, Degraded: %v, ImageBuildDegraded: %v",
1498+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated),
1499+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdating),
1500+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolDegraded),
1501+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolImageBuildDegraded))
1502+
1503+
return true, nil
1504+
}, "MCP should reach correct state after successful build (Updated=True, Updating=False, Degraded=False, ImageBuildDegraded=False)")
1505+
1506+
// Now trigger another build to test MCP status transitions when a new build starts
1507+
t.Logf("Triggering a third build to test MCP status transitions")
1508+
1509+
// Modify the containerfile slightly to trigger a new build
1510+
apiMosc, err = cs.MachineconfigurationV1Interface.MachineOSConfigs().Get(ctx, mosc.Name, metav1.GetOptions{})
1511+
require.NoError(t, err)
1512+
1513+
// Add a comment to the containerfile to change it and trigger a new build
1514+
modifiedDockerfile := cowsayDockerfile + "\n# Comment to trigger new build"
1515+
apiMosc.Spec.Containerfile = []mcfgv1.MachineOSContainerfile{
1516+
{
1517+
ContainerfileArch: mcfgv1.NoArch,
1518+
Content: modifiedDockerfile,
1519+
},
1520+
}
1521+
1522+
updated, err = cs.MachineconfigurationV1Interface.MachineOSConfigs().Update(ctx, apiMosc, metav1.UpdateOptions{})
1523+
require.NoError(t, err)
1524+
1525+
t.Logf("Modified containerfile, waiting for third build to start")
1526+
1527+
// Get the updated MCP to compute the new build
1528+
mcp, err = cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, layeredMCPName, metav1.GetOptions{})
1529+
require.NoError(t, err)
1530+
1531+
// Compute the new MachineOSBuild name for the third build
1532+
thirdMoscMosb := buildrequest.NewMachineOSBuildFromAPIOrDie(ctx, cs.GetKubeclient(), updated, mcp)
1533+
1534+
// Wait for the third build to start
1535+
thirdMosb := waitForBuildToStart(t, cs, thirdMoscMosb)
1536+
t.Logf("Third build started: %s", thirdMosb.Name)
1537+
1538+
// Verify MCP status during active build:
1539+
// Updated=False, Updating=True, Degraded=False, ImageBuildDegraded=False
1540+
buildingMcp, err := cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, layeredMCPName, metav1.GetOptions{})
1541+
require.NoError(t, err)
1542+
1543+
kubeassert.Eventually().MachineConfigPoolReachesState(buildingMcp, func(mcp *mcfgv1.MachineConfigPool, err error) (bool, error) {
1544+
if err != nil {
1545+
return false, err
1546+
}
1547+
// During build, MCP should show: Updated=False, Updating=True
1548+
if apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated) ||
1549+
!apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdating) ||
1550+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolDegraded) ||
1551+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolImageBuildDegraded) {
1552+
return false, nil
1553+
}
1554+
1555+
t.Logf("MCP status during active build - Updated: %v, Updating: %v, Degraded: %v, ImageBuildDegraded: %v",
1556+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated),
1557+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdating),
1558+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolDegraded),
1559+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolImageBuildDegraded))
1560+
1561+
return true, nil
1562+
}, "MCP should reach correct state during active build (Updated=False, Updating=True, Degraded=False, ImageBuildDegraded=False)")
1563+
1564+
// Wait for the third build to complete successfully
1565+
finalBuild := waitForBuildToComplete(t, cs, thirdMosb)
1566+
t.Logf("Third build completed successfully: %s", finalBuild.Name)
1567+
1568+
// Wait for the MachineOSConfig to get the new pullspec, which indicates full reconciliation
1569+
waitForMOSCToGetNewPullspec(ctx, t, cs, mosc.Name, string(finalBuild.Status.DigestedImagePushSpec))
1570+
1571+
// Final verification: MCP status should return to:
1572+
// Updated=True, Updating=False, Degraded=False, ImageBuildDegraded=False
1573+
finalMcp, err := cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, layeredMCPName, metav1.GetOptions{})
1574+
require.NoError(t, err)
1575+
1576+
kubeassert.Eventually().MachineConfigPoolReachesState(finalMcp, func(mcp *mcfgv1.MachineConfigPool, err error) (bool, error) {
1577+
if err != nil {
1578+
return false, err
1579+
}
1580+
// Return false (keep polling) if conditions don't match expected state
1581+
if !apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated) ||
1582+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdating) ||
1583+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolDegraded) ||
1584+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolImageBuildDegraded) {
1585+
return false, nil
1586+
}
1587+
1588+
// Return true when expected state is reached
1589+
t.Logf("Final MCP status after third build completion - Updated: %v, Updating: %v, Degraded: %v, ImageBuildDegraded: %v",
1590+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated),
1591+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdating),
1592+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolDegraded),
1593+
apihelpers.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolImageBuildDegraded))
1594+
1595+
return true, nil
1596+
}, "MCP should return to correct state after final build completion (Updated=True, Updating=False, Degraded=False, ImageBuildDegraded=False)")
1597+
1598+
t.Logf("All MCP status transitions verified successfully across build failure, success, and subsequent new build")
14621599
}
14631600

14641601
// TestCurrentMachineOSBuildAnnotationHandling tests that the node controller correctly uses the

0 commit comments

Comments
 (0)