sigs.k8s.io/cluster-api@v1.7.1/exp/topology/scope/upgradetracker.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package scope
    18  
    19  import "k8s.io/apimachinery/pkg/util/sets"
    20  
    21  // UpgradeTracker is a helper to capture the upgrade status and make upgrade decisions.
    22  type UpgradeTracker struct {
    23  	ControlPlane       ControlPlaneUpgradeTracker
    24  	MachineDeployments WorkerUpgradeTracker
    25  	MachinePools       WorkerUpgradeTracker
    26  }
    27  
    28  // ControlPlaneUpgradeTracker holds the current upgrade status of the Control Plane.
    29  type ControlPlaneUpgradeTracker struct {
    30  	// IsPendingUpgrade is true if the Control Plane version needs to be updated. False otherwise.
    31  	// If IsPendingUpgrade is true it also means the Control Plane is not going to pick up the new version
    32  	// in the current reconcile loop.
    33  	// Example cases when IsPendingUpgrade is set to true:
    34  	// - Upgrade is blocked by BeforeClusterUpgrade hook
    35  	// - Upgrade is blocked because the current ControlPlane is not stable (provisioning OR scaling OR upgrading)
    36  	// - Upgrade is blocked because any of the current MachineDeployments or MachinePools are upgrading.
    37  	IsPendingUpgrade bool
    38  
    39  	// IsProvisioning is true if the current Control Plane is being provisioned for the first time. False otherwise.
    40  	IsProvisioning bool
    41  
    42  	// IsUpgrading is true if the Control Plane is in the middle of an upgrade.
    43  	// Note: Refer to Control Plane contract for definition of upgrading.
    44  	// IsUpgrading is set to true if the current ControlPlane (ControlPlane at the beginning of the reconcile)
    45  	// is upgrading.
    46  	// Note: IsUpgrading only represents the current ControlPlane state. If the Control Plane is about to pick up the
    47  	// version in the reconcile loop IsUpgrading will not be true, because the current ControlPlane is not upgrading,
    48  	// the desired ControlPlane is.
    49  	// Also look at: IsStartingUpgrade.
    50  	IsUpgrading bool
    51  
    52  	// IsStartingUpgrade is true if the Control Plane is picking up the new version in the current reconcile loop.
    53  	// If IsStartingUpgrade is true it implies that the desired Control Plane version and the current Control Plane
    54  	// versions are different.
    55  	IsStartingUpgrade bool
    56  
    57  	// IsScaling is true if the current Control Plane is scaling. False otherwise.
    58  	// IsScaling only represents the state of the current Control Plane. IsScaling does not represent the state
    59  	// of the desired Control Plane.
    60  	// Example:
    61  	// - IsScaling will be true if the current ControlPlane is scaling.
    62  	// - IsScaling will not be true if the current Control Plane is stable and the reconcile loop is going to scale the Control Plane.
    63  	// Note: Refer to control plane contract for definition of scaling.
    64  	// Note: IsScaling will be false if the Control Plane does not support replicas.
    65  	IsScaling bool
    66  }
    67  
    68  // WorkerUpgradeTracker holds the current upgrade status of MachineDeployments or MachinePools.
    69  type WorkerUpgradeTracker struct {
    70  	// pendingCreateTopologyNames is the set of MachineDeployment/MachinePool topology names that are newly added to the
    71  	// Cluster Topology but will not be created in the current reconcile loop.
    72  	// By marking a MachineDeployment/MachinePool topology as pendingCreate we skip creating the MachineDeployment/MachinePool.
    73  	// Nb. We use MachineDeployment/MachinePool topology names instead of MachineDeployment/MachinePool names because the new
    74  	// MachineDeployment/MachinePool names can keep changing for each reconcile loop leading to continuous updates to the
    75  	// TopologyReconciled condition.
    76  	pendingCreateTopologyNames sets.Set[string]
    77  
    78  	// pendingUpgradeNames is the set of MachineDeployment/MachinePool names that are not going to pick up the new version
    79  	// in the current reconcile loop.
    80  	// By marking a MachineDeployment/MachinePool as pendingUpgrade we skip reconciling the MachineDeployment/MachinePool.
    81  	pendingUpgradeNames sets.Set[string]
    82  
    83  	// deferredNames is the set of MachineDeployment/MachinePool names that are not going to pick up the new version
    84  	// in the current reconcile loop because they are deferred by the user.
    85  	// Note: If a MachineDeployment/MachinePool is marked as deferred it should also be marked as pendingUpgrade.
    86  	deferredNames sets.Set[string]
    87  
    88  	// upgradingNames is the set of MachineDeployment/MachinePool names that are upgrading. This set contains the names of
    89  	// MachineDeployments/MachinePools that are currently upgrading and the names of MachineDeployments/MachinePools that
    90  	// will pick up the upgrade in the current reconcile loop.
    91  	// Note: This information is used to:
    92  	// - decide if ControlPlane can be upgraded.
    93  	// - calculate MachineDeployment/MachinePool upgrade concurrency.
    94  	// - update TopologyReconciled Condition.
    95  	// - decide if the AfterClusterUpgrade hook can be called.
    96  	upgradingNames sets.Set[string]
    97  
    98  	// maxUpgradeConcurrency defines the maximum number of MachineDeployments/MachinePools that should be in an
    99  	// upgrading state. This includes the MachineDeployments/MachinePools that are currently upgrading and the
   100  	// MachineDeployments/MachinePools that will start the upgrade after the current reconcile loop.
   101  	maxUpgradeConcurrency int
   102  }
   103  
   104  // UpgradeTrackerOptions contains the options for NewUpgradeTracker.
   105  type UpgradeTrackerOptions struct {
   106  	maxMDUpgradeConcurrency int
   107  	maxMPUpgradeConcurrency int
   108  }
   109  
   110  // UpgradeTrackerOption returns an option for the NewUpgradeTracker function.
   111  type UpgradeTrackerOption interface {
   112  	ApplyToUpgradeTracker(options *UpgradeTrackerOptions)
   113  }
   114  
   115  // MaxMDUpgradeConcurrency sets the upper limit for the number of Machine Deployments that can upgrade
   116  // concurrently.
   117  type MaxMDUpgradeConcurrency int
   118  
   119  // ApplyToUpgradeTracker applies the given UpgradeTrackerOptions.
   120  func (m MaxMDUpgradeConcurrency) ApplyToUpgradeTracker(options *UpgradeTrackerOptions) {
   121  	options.maxMDUpgradeConcurrency = int(m)
   122  }
   123  
   124  // MaxMPUpgradeConcurrency sets the upper limit for the number of Machine Pools that can upgrade
   125  // concurrently.
   126  type MaxMPUpgradeConcurrency int
   127  
   128  // ApplyToUpgradeTracker applies the given UpgradeTrackerOptions.
   129  func (m MaxMPUpgradeConcurrency) ApplyToUpgradeTracker(options *UpgradeTrackerOptions) {
   130  	options.maxMPUpgradeConcurrency = int(m)
   131  }
   132  
   133  // NewUpgradeTracker returns an upgrade tracker with empty tracking information.
   134  func NewUpgradeTracker(opts ...UpgradeTrackerOption) *UpgradeTracker {
   135  	options := &UpgradeTrackerOptions{}
   136  	for _, o := range opts {
   137  		o.ApplyToUpgradeTracker(options)
   138  	}
   139  	if options.maxMDUpgradeConcurrency < 1 {
   140  		// The concurrency should be at least 1.
   141  		options.maxMDUpgradeConcurrency = 1
   142  	}
   143  	if options.maxMPUpgradeConcurrency < 1 {
   144  		// The concurrency should be at least 1.
   145  		options.maxMPUpgradeConcurrency = 1
   146  	}
   147  	return &UpgradeTracker{
   148  		MachineDeployments: WorkerUpgradeTracker{
   149  			pendingCreateTopologyNames: sets.Set[string]{},
   150  			pendingUpgradeNames:        sets.Set[string]{},
   151  			deferredNames:              sets.Set[string]{},
   152  			upgradingNames:             sets.Set[string]{},
   153  			maxUpgradeConcurrency:      options.maxMDUpgradeConcurrency,
   154  		},
   155  		MachinePools: WorkerUpgradeTracker{
   156  			pendingCreateTopologyNames: sets.Set[string]{},
   157  			pendingUpgradeNames:        sets.Set[string]{},
   158  			deferredNames:              sets.Set[string]{},
   159  			upgradingNames:             sets.Set[string]{},
   160  			maxUpgradeConcurrency:      options.maxMPUpgradeConcurrency,
   161  		},
   162  	}
   163  }
   164  
   165  // IsControlPlaneStable returns true is the ControlPlane is stable.
   166  func (t *ControlPlaneUpgradeTracker) IsControlPlaneStable() bool {
   167  	// If the current control plane is upgrading it is not considered stable.
   168  	if t.IsUpgrading {
   169  		return false
   170  	}
   171  
   172  	// If control plane supports replicas, check if the control plane is in the middle of a scale operation.
   173  	// If the current control plane is scaling then it is not considered stable.
   174  	if t.IsScaling {
   175  		return false
   176  	}
   177  
   178  	// Check if we are about to upgrade the control plane. Since the control plane is about to start its upgrade process
   179  	// it cannot be considered stable.
   180  	if t.IsStartingUpgrade {
   181  		return false
   182  	}
   183  
   184  	// If the ControlPlane is pending picking up an upgrade then it is not yet at the desired state and
   185  	// cannot be considered stable.
   186  	if t.IsPendingUpgrade {
   187  		return false
   188  	}
   189  
   190  	return true
   191  }
   192  
   193  // MarkUpgrading marks a MachineDeployment/MachinePool as currently upgrading or about to upgrade.
   194  func (m *WorkerUpgradeTracker) MarkUpgrading(names ...string) {
   195  	for _, name := range names {
   196  		m.upgradingNames.Insert(name)
   197  	}
   198  }
   199  
   200  // UpgradingNames returns the list of machine deployments that are upgrading or
   201  // are about to upgrade.
   202  func (m *WorkerUpgradeTracker) UpgradingNames() []string {
   203  	return sets.List(m.upgradingNames)
   204  }
   205  
   206  // UpgradeConcurrencyReached returns true if the number of MachineDeployments/MachinePools upgrading is at the concurrency limit.
   207  func (m *WorkerUpgradeTracker) UpgradeConcurrencyReached() bool {
   208  	return m.upgradingNames.Len() >= m.maxUpgradeConcurrency
   209  }
   210  
   211  // MarkPendingCreate marks a machine deployment topology that is pending to be created.
   212  // This is generally used to capture machine deployments that are yet to be created
   213  // because the control plane is not yet stable.
   214  func (m *WorkerUpgradeTracker) MarkPendingCreate(mdTopologyName string) {
   215  	m.pendingCreateTopologyNames.Insert(mdTopologyName)
   216  }
   217  
   218  // IsPendingCreate returns true is the MachineDeployment/MachinePool topology is marked as pending create.
   219  func (m *WorkerUpgradeTracker) IsPendingCreate(mdTopologyName string) bool {
   220  	return m.pendingCreateTopologyNames.Has(mdTopologyName)
   221  }
   222  
   223  // IsAnyPendingCreate returns true if any of the machine deployments are pending
   224  // to be created. Returns false, otherwise.
   225  func (m *WorkerUpgradeTracker) IsAnyPendingCreate() bool {
   226  	return len(m.pendingCreateTopologyNames) != 0
   227  }
   228  
   229  // PendingCreateTopologyNames returns the list of machine deployment topology names that
   230  // are pending create.
   231  func (m *WorkerUpgradeTracker) PendingCreateTopologyNames() []string {
   232  	return sets.List(m.pendingCreateTopologyNames)
   233  }
   234  
   235  // MarkPendingUpgrade marks a machine deployment as in need of an upgrade.
   236  // This is generally used to capture machine deployments that have not yet
   237  // picked up the topology version.
   238  func (m *WorkerUpgradeTracker) MarkPendingUpgrade(name string) {
   239  	m.pendingUpgradeNames.Insert(name)
   240  }
   241  
   242  // IsPendingUpgrade returns true is the MachineDeployment/MachinePool marked as pending upgrade.
   243  func (m *WorkerUpgradeTracker) IsPendingUpgrade(name string) bool {
   244  	return m.pendingUpgradeNames.Has(name)
   245  }
   246  
   247  // IsAnyPendingUpgrade returns true if any of the machine deployments are pending
   248  // an upgrade. Returns false, otherwise.
   249  func (m *WorkerUpgradeTracker) IsAnyPendingUpgrade() bool {
   250  	return len(m.pendingUpgradeNames) != 0
   251  }
   252  
   253  // PendingUpgradeNames returns the list of machine deployment names that
   254  // are pending an upgrade.
   255  func (m *WorkerUpgradeTracker) PendingUpgradeNames() []string {
   256  	return sets.List(m.pendingUpgradeNames)
   257  }
   258  
   259  // MarkDeferredUpgrade marks that the upgrade for a MachineDeployment/MachinePool
   260  // has been deferred.
   261  func (m *WorkerUpgradeTracker) MarkDeferredUpgrade(name string) {
   262  	m.deferredNames.Insert(name)
   263  }
   264  
   265  // DeferredUpgradeNames returns the list of MachineDeployment/MachinePool names for
   266  // which the upgrade has been deferred.
   267  func (m *WorkerUpgradeTracker) DeferredUpgradeNames() []string {
   268  	return sets.List(m.deferredNames)
   269  }
   270  
   271  // DeferredUpgrade returns true if the upgrade has been deferred for any of the
   272  // MachineDeployments/MachinePools. Returns false, otherwise.
   273  func (m *WorkerUpgradeTracker) DeferredUpgrade() bool {
   274  	return len(m.deferredNames) != 0
   275  }