44 "context"
55 "fmt"
66 "reflect"
7+ "strings"
78 "time"
89
910 features "github.com/openshift/api/features"
@@ -14,6 +15,7 @@ import (
1415 corev1 "k8s.io/api/core/v1"
1516 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1617 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
18+ k8sversion "k8s.io/apimachinery/pkg/util/version"
1719 "k8s.io/apimachinery/pkg/util/wait"
1820 coreinformersv1 "k8s.io/client-go/informers/core/v1"
1921 clientset "k8s.io/client-go/kubernetes"
@@ -77,6 +79,7 @@ type Controller struct {
7779// Stats structure for local bookkeeping of machine resources
7880type MachineResourceStats struct {
7981 inProgress int
82+ skippedCount int
8083 erroredCount int
8184 totalCount int
8285}
@@ -94,6 +97,17 @@ func (mrs MachineResourceStats) isFinished() bool {
9497 return mrs .totalCount == (mrs .inProgress + mrs .erroredCount )
9598}
9699
100+ func (mrs MachineResourceStats ) getProgressingStatusMessage (name string ) string {
101+ if mrs .skippedCount > 0 {
102+ return fmt .Sprintf ("Reconciled %d of %d %s (%d skipped)" , mrs .inProgress - mrs .skippedCount , mrs .totalCount , name , mrs .skippedCount )
103+ }
104+ return fmt .Sprintf ("Reconciled %d of %d %s" , mrs .inProgress , mrs .totalCount , name )
105+ }
106+
107+ func (mrs MachineResourceStats ) getDegradedStatusMessage (name string ) string {
108+ return fmt .Sprintf ("%d Degraded %s" , mrs .erroredCount , name )
109+ }
110+
97111const (
98112 // Name of machine api namespace
99113 MachineAPINamespace = "openshift-machine-api"
@@ -446,8 +460,11 @@ func (ctrl *Controller) updateMachineConfiguration(oldMC, newMC interface{}) {
446460 return
447461 }
448462
449- // Only take action if the there is an actual change in the MachineConfiguration's ManagedBootImagesStatus
450- if reflect .DeepEqual (oldMachineConfiguration .Status .ManagedBootImagesStatus , newMachineConfiguration .Status .ManagedBootImagesStatus ) {
463+ // Skip reconciliation if neither ManagedBootImagesStatus nor BootImageSkewEnforcementStatus has changed.
464+ // BootImageSkewEnforcementStatus is only checked when the BootImageSkewEnforcement feature gate is enabled.
465+ if reflect .DeepEqual (oldMachineConfiguration .Status .ManagedBootImagesStatus , newMachineConfiguration .Status .ManagedBootImagesStatus ) &&
466+ (! ctrl .fgHandler .Enabled (features .FeatureGateBootImageSkewEnforcement ) ||
467+ reflect .DeepEqual (oldMachineConfiguration .Status .BootImageSkewEnforcementStatus , newMachineConfiguration .Status .BootImageSkewEnforcementStatus )) {
451468 return
452469 }
453470
@@ -493,7 +510,13 @@ func (ctrl *Controller) updateConditions(newReason string, syncError error, targ
493510 for i , condition := range newConditions {
494511 if condition .Type == targetConditionType {
495512 if condition .Type == opv1 .MachineConfigurationBootImageUpdateProgressing {
496- newConditions [i ].Message = fmt .Sprintf ("Reconciled %d of %d MAPI MachineSets | Reconciled %d of %d ControlPlaneMachineSets | Reconciled %d of %d CAPI MachineSets | Reconciled %d of %d CAPI MachineDeployments" , ctrl .mapiStats .inProgress , ctrl .mapiStats .totalCount , ctrl .cpmsStats .inProgress , ctrl .cpmsStats .totalCount , ctrl .capiMachineSetStats .inProgress , ctrl .capiMachineSetStats .totalCount , ctrl .capiMachineDeploymentStats .inProgress , ctrl .capiMachineDeploymentStats .totalCount )
513+ messages := []string {
514+ ctrl .mapiStats .getProgressingStatusMessage ("MAPI MachineSets" ),
515+ ctrl .cpmsStats .getProgressingStatusMessage ("ControlPlaneMachineSets" ),
516+ ctrl .capiMachineSetStats .getProgressingStatusMessage ("CAPI MachineSets" ),
517+ ctrl .capiMachineDeploymentStats .getProgressingStatusMessage ("CAPI MachineDeployments" ),
518+ }
519+ newConditions [i ].Message = strings .Join (messages , " | " )
497520 newConditions [i ].Reason = newReason
498521 // If all machine resources have been processed, then the controller is no longer progressing.
499522 if ctrl .mapiStats .isFinished () && ctrl .cpmsStats .isFinished () && ctrl .capiMachineSetStats .isFinished () && ctrl .capiMachineDeploymentStats .isFinished () {
@@ -502,10 +525,16 @@ func (ctrl *Controller) updateConditions(newReason string, syncError error, targ
502525 newConditions [i ].Status = metav1 .ConditionTrue
503526 }
504527 } else if condition .Type == opv1 .MachineConfigurationBootImageUpdateDegraded {
528+ messages := []string {
529+ ctrl .mapiStats .getDegradedStatusMessage ("MAPI MachineSets" ),
530+ ctrl .cpmsStats .getDegradedStatusMessage ("ControlPlaneMachineSets" ),
531+ ctrl .capiMachineSetStats .getDegradedStatusMessage ("CAPI MachineSets" ),
532+ ctrl .capiMachineDeploymentStats .getDegradedStatusMessage ("CAPI MachineDeployments" ),
533+ }
505534 if syncError == nil {
506- newConditions [i ].Message = fmt . Sprintf ( "%d Degraded MAPI MachineSets | %d Degraded ControlPlaneMachineSets | %d Degraded CAPI MachineSets | %d CAPI MachineDeployments" , ctrl . mapiStats . erroredCount , ctrl . cpmsStats . erroredCount , ctrl . capiMachineSetStats . erroredCount , ctrl . capiMachineDeploymentStats . erroredCount )
535+ newConditions [i ].Message = strings . Join ( messages , " | " )
507536 } else {
508- newConditions [i ].Message = fmt .Sprintf ("%d Degraded MAPI MachineSets | %d Degraded ControlPlaneMachineSets | %d Degraded CAPI MachineSets | %d CAPI MachineDeployments | Error(s): %s" , ctrl . mapiStats . erroredCount , ctrl . cpmsStats . erroredCount , ctrl . capiMachineSetStats . erroredCount , ctrl . capiMachineDeploymentStats . erroredCount , syncError .Error ())
537+ newConditions [i ].Message = fmt .Sprintf ("%s | Error(s): %s" , strings . Join ( messages , " | " ) , syncError .Error ())
509538 }
510539 newConditions [i ].Reason = newReason
511540 if syncError != nil {
@@ -523,33 +552,77 @@ func (ctrl *Controller) updateConditions(newReason string, syncError error, targ
523552 }
524553 // Only make an API call if there is an update to the Conditions field
525554 if ! reflect .DeepEqual (newConditions , mcop .Status .Conditions ) {
526- ctrl .updateMachineConfigurationStatus (mcop , newConditions )
555+ mcop .Status .Conditions = newConditions
556+ ctrl .updateMachineConfigurationStatus (mcop .Status )
527557 }
528558}
529559
530- // updateMachineConfigurationStatus updates the MachineConfiguration status with new conditions
531- // using retry logic to handle concurrent updates.
532- func (ctrl * Controller ) updateMachineConfigurationStatus (mcop * opv1.MachineConfiguration , newConditions []metav1.Condition ) {
560+ // updateClusterBootImage updates the cluster boot image record if the skew enforcement is set to Automatic mode.
561+ func (ctrl * Controller ) updateClusterBootImage () {
562+
563+ mcop , err := ctrl .mcopClient .OperatorV1 ().MachineConfigurations ().Get (context .TODO (), ctrlcommon .MCOOperatorKnobsObjectName , metav1.GetOptions {})
564+ if err != nil {
565+ klog .Errorf ("error updating cluster boot image record: %s" , err )
566+ return
567+ }
568+ // No action to take if not in automatic mode
569+ if mcop .Status .BootImageSkewEnforcementStatus .Mode != opv1 .BootImageSkewEnforcementModeStatusAutomatic {
570+ return
571+ }
572+
573+ // Get OCP version of last boot image update from configmap
574+ configMap , err := ctrl .mcoCmLister .ConfigMaps (ctrlcommon .MCONamespace ).Get (ctrlcommon .BootImagesConfigMapName )
575+ if err != nil {
576+ klog .Warningf ("Failed to get boot images configmap: %v, skipping cluster boot image record update" , err )
577+ return
578+ }
579+
580+ releaseVersion , found := configMap .Data [ctrlcommon .OCPReleaseVersionKey ]
581+ if ! found {
582+ klog .Warningf ("OCP release version not found in boot images configmap, skipping cluster boot image record update" )
583+ return
584+ }
533585
586+ // Parse and extract semantic version (major.minor.patch) for API validation
587+ parsedVersion , err := k8sversion .ParseGeneric (releaseVersion )
588+ if err != nil {
589+ klog .Warningf ("Failed to parse release version %q from configmap: %v, skipping cluster boot image record update" , releaseVersion , err )
590+ return
591+ }
592+ ocpVersion := fmt .Sprintf ("%d.%d.%d" , parsedVersion .Major (), parsedVersion .Minor (), parsedVersion .Patch ())
593+
594+ newBootImageSkewEnforcementStatus := mcop .Status .BootImageSkewEnforcementStatus .DeepCopy ()
595+ newBootImageSkewEnforcementStatus .Automatic = opv1.ClusterBootImageAutomatic {
596+ OCPVersion : ocpVersion ,
597+ }
598+
599+ // Only make an API call if there is an update to the skew enforcement status
600+ if ! reflect .DeepEqual (mcop .Status .BootImageSkewEnforcementStatus , newBootImageSkewEnforcementStatus ) {
601+ mcop .Status .BootImageSkewEnforcementStatus = * newBootImageSkewEnforcementStatus
602+ ctrl .updateMachineConfigurationStatus (mcop .Status )
603+ }
604+ }
605+
606+ // updateMachineConfigurationStatus updates the MachineConfiguration status using retry logic to handle concurrent updates.
607+ func (ctrl * Controller ) updateMachineConfigurationStatus (mcopStatus opv1.MachineConfigurationStatus ) {
534608 // Using a retry here as there may be concurrent reconiliation loops updating conditions for multiple
535609 // resources at the same time and their local stores may be out of date
536- if ! reflect .DeepEqual (mcop .Status .Conditions , newConditions ) {
537- klog .V (4 ).Infof ("%v" , newConditions )
538- if err := retry .RetryOnConflict (retry .DefaultBackoff , func () error {
539- mcop , err := ctrl .mcopClient .OperatorV1 ().MachineConfigurations ().Get (context .TODO (), ctrlcommon .MCOOperatorKnobsObjectName , metav1.GetOptions {})
540- if err != nil {
541- return err
542- }
543- mcop .Status .Conditions = newConditions
544- _ , err = ctrl .mcopClient .OperatorV1 ().MachineConfigurations ().UpdateStatus (context .TODO (), mcop , metav1.UpdateOptions {})
545- if err != nil {
546- return err
547- }
548- return nil
549- }); err != nil {
550- klog .Errorf ("error updating MachineConfiguration status: %v" , err )
610+ klog .V (4 ).Infof ("MachineConfiguration status update: %v" , mcopStatus )
611+ if err := retry .RetryOnConflict (retry .DefaultBackoff , func () error {
612+ mcop , err := ctrl .mcopClient .OperatorV1 ().MachineConfigurations ().Get (context .TODO (), ctrlcommon .MCOOperatorKnobsObjectName , metav1.GetOptions {})
613+ if err != nil {
614+ return err
551615 }
616+ mcop .Status = mcopStatus
617+ _ , err = ctrl .mcopClient .OperatorV1 ().MachineConfigurations ().UpdateStatus (context .TODO (), mcop , metav1.UpdateOptions {})
618+ if err != nil {
619+ return err
620+ }
621+ return nil
622+ }); err != nil {
623+ klog .Errorf ("error updating MachineConfiguration status: %v" , err )
552624 }
625+
553626}
554627
555628// getDefaultConditions returns the default boot image update conditions when no
0 commit comments