sigs.k8s.io/cluster-api@v1.7.1/internal/contract/controlplane.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 contract
    18  
    19  import (
    20  	"sync"
    21  
    22  	"github.com/blang/semver/v4"
    23  	"github.com/pkg/errors"
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  	"k8s.io/utils/ptr"
    26  
    27  	"sigs.k8s.io/cluster-api/util/version"
    28  )
    29  
    30  // ControlPlaneContract encodes information about the Cluster API contract for ControlPlane objects
    31  // like e.g the KubeadmControlPlane etc.
    32  type ControlPlaneContract struct{}
    33  
    34  var controlPlane *ControlPlaneContract
    35  var onceControlPlane sync.Once
    36  
    37  // ControlPlane provide access to the information about the Cluster API contract for ControlPlane objects.
    38  func ControlPlane() *ControlPlaneContract {
    39  	onceControlPlane.Do(func() {
    40  		controlPlane = &ControlPlaneContract{}
    41  	})
    42  	return controlPlane
    43  }
    44  
    45  // MachineTemplate provides access to MachineTemplate in a ControlPlane object, if any.
    46  // NOTE: When working with unstructured there is no way to understand if the ControlPlane provider
    47  // do support a field in the type definition from the fact that a field is not set in a given instance.
    48  // This is why in we are deriving if MachineTemplate is required from the ClusterClass in the topology reconciler code.
    49  func (c *ControlPlaneContract) MachineTemplate() *ControlPlaneMachineTemplate {
    50  	return &ControlPlaneMachineTemplate{}
    51  }
    52  
    53  // Version provide access to version field in a ControlPlane object, if any.
    54  // NOTE: When working with unstructured there is no way to understand if the ControlPlane provider
    55  // do support a field in the type definition from the fact that a field is not set in a given instance.
    56  // This is why in we are deriving if version is required from the ClusterClass in the topology reconciler code.
    57  func (c *ControlPlaneContract) Version() *String {
    58  	return &String{
    59  		path: []string{"spec", "version"},
    60  	}
    61  }
    62  
    63  // StatusVersion provide access to the version field in a ControlPlane object status, if any.
    64  func (c *ControlPlaneContract) StatusVersion() *String {
    65  	return &String{
    66  		path: []string{"status", "version"},
    67  	}
    68  }
    69  
    70  // Ready provide access to the status.ready field in a ControlPlane object.
    71  func (c *ControlPlaneContract) Ready() *Bool {
    72  	return &Bool{
    73  		path: []string{"status", "ready"},
    74  	}
    75  }
    76  
    77  // Initialized provide access to status.initialized field in a ControlPlane object.
    78  func (c *ControlPlaneContract) Initialized() *Bool {
    79  	return &Bool{
    80  		path: []string{"status", "initialized"},
    81  	}
    82  }
    83  
    84  // Replicas provide access to replicas field in a ControlPlane object, if any.
    85  // NOTE: When working with unstructured there is no way to understand if the ControlPlane provider
    86  // do support a field in the type definition from the fact that a field is not set in a given instance.
    87  // This is why in we are deriving if replicas is required from the ClusterClass in the topology reconciler code.
    88  func (c *ControlPlaneContract) Replicas() *Int64 {
    89  	return &Int64{
    90  		path: []string{"spec", "replicas"},
    91  	}
    92  }
    93  
    94  // StatusReplicas provide access to the status.replicas field in a ControlPlane object, if any. Applies to implementations using replicas.
    95  func (c *ControlPlaneContract) StatusReplicas() *Int64 {
    96  	return &Int64{
    97  		path: []string{"status", "replicas"},
    98  	}
    99  }
   100  
   101  // UpdatedReplicas provide access to the status.updatedReplicas field in a ControlPlane object, if any. Applies to implementations using replicas.
   102  func (c *ControlPlaneContract) UpdatedReplicas() *Int64 {
   103  	return &Int64{
   104  		path: []string{"status", "updatedReplicas"},
   105  	}
   106  }
   107  
   108  // ReadyReplicas provide access to the status.readyReplicas field in a ControlPlane object, if any. Applies to implementations using replicas.
   109  func (c *ControlPlaneContract) ReadyReplicas() *Int64 {
   110  	return &Int64{
   111  		path: []string{"status", "readyReplicas"},
   112  	}
   113  }
   114  
   115  // UnavailableReplicas provide access to the status.unavailableReplicas field in a ControlPlane object, if any. Applies to implementations using replicas.
   116  func (c *ControlPlaneContract) UnavailableReplicas() *Int64 {
   117  	return &Int64{
   118  		path: []string{"status", "unavailableReplicas"},
   119  	}
   120  }
   121  
   122  // Selector provide access to the status.selector field in a ControlPlane object, if any. Applies to implementations using replicas.
   123  func (c *ControlPlaneContract) Selector() *String {
   124  	return &String{
   125  		path: []string{"status", "selector"},
   126  	}
   127  }
   128  
   129  // FailureReason provides access to the status.failureReason field in an ControlPlane object. Note that this field is optional.
   130  func (c *ControlPlaneContract) FailureReason() *String {
   131  	return &String{
   132  		path: []string{"status", "failureReason"},
   133  	}
   134  }
   135  
   136  // FailureMessage provides access to the status.failureMessage field in an ControlPlane object. Note that this field is optional.
   137  func (c *ControlPlaneContract) FailureMessage() *String {
   138  	return &String{
   139  		path: []string{"status", "failureMessage"},
   140  	}
   141  }
   142  
   143  // ExternalManagedControlPlane provides access to the status.externalManagedControlPlane field in an ControlPlane object.
   144  // Note that this field is optional.
   145  func (c *ControlPlaneContract) ExternalManagedControlPlane() *Bool {
   146  	return &Bool{
   147  		path: []string{"status", "externalManagedControlPlane"},
   148  	}
   149  }
   150  
   151  // IsProvisioning returns true if the control plane is being created for the first time.
   152  // Returns false, if the control plane was already previously provisioned.
   153  func (c *ControlPlaneContract) IsProvisioning(obj *unstructured.Unstructured) (bool, error) {
   154  	// We can know if the control plane was previously created or is being cretaed for the first
   155  	// time by looking at controlplane.status.version. If the version in status is set to a valid
   156  	// value then the control plane was already provisioned at a previous time. If not, we can
   157  	// assume that the control plane is being created for the first time.
   158  	statusVersion, err := c.StatusVersion().Get(obj)
   159  	if err != nil {
   160  		if errors.Is(err, ErrFieldNotFound) {
   161  			return true, nil
   162  		}
   163  		return false, errors.Wrap(err, "failed to get control plane status version")
   164  	}
   165  	if *statusVersion == "" {
   166  		return true, nil
   167  	}
   168  	return false, nil
   169  }
   170  
   171  // IsUpgrading returns true if the control plane is in the middle of an upgrade, false otherwise.
   172  // A control plane is considered upgrading if:
   173  // - if spec.version is greater than status.version.
   174  // Note: A control plane is considered not upgrading if the status or status.version is not set.
   175  func (c *ControlPlaneContract) IsUpgrading(obj *unstructured.Unstructured) (bool, error) {
   176  	specVersion, err := c.Version().Get(obj)
   177  	if err != nil {
   178  		return false, errors.Wrap(err, "failed to get control plane spec version")
   179  	}
   180  	specV, err := semver.ParseTolerant(*specVersion)
   181  	if err != nil {
   182  		return false, errors.Wrap(err, "failed to parse control plane spec version")
   183  	}
   184  	statusVersion, err := c.StatusVersion().Get(obj)
   185  	if err != nil {
   186  		if errors.Is(err, ErrFieldNotFound) { // status version is not yet set
   187  			// If the status.version is not yet present in the object, it implies the
   188  			// first machine of the control plane is provisioning. We can reasonably assume
   189  			// that the control plane is not upgrading at this stage.
   190  			return false, nil
   191  		}
   192  		return false, errors.Wrap(err, "failed to get control plane status version")
   193  	}
   194  	statusV, err := semver.ParseTolerant(*statusVersion)
   195  	if err != nil {
   196  		return false, errors.Wrap(err, "failed to parse control plane status version")
   197  	}
   198  
   199  	// NOTE: we are considering the control plane upgrading when the version is greater
   200  	// or when the version has a different build metadata.
   201  	return version.Compare(specV, statusV, version.WithBuildTags()) >= 1, nil
   202  }
   203  
   204  // IsScaling returns true if the control plane is in the middle of a scale operation, false otherwise.
   205  // A control plane is considered scaling if:
   206  // - status.replicas is not yet set.
   207  // - spec.replicas != status.replicas.
   208  // - spec.replicas != status.updatedReplicas.
   209  // - spec.replicas != status.readyReplicas.
   210  // - status.unavailableReplicas > 0.
   211  func (c *ControlPlaneContract) IsScaling(obj *unstructured.Unstructured) (bool, error) {
   212  	desiredReplicas, err := c.Replicas().Get(obj)
   213  	if err != nil {
   214  		return false, errors.Wrap(err, "failed to get control plane spec replicas")
   215  	}
   216  
   217  	statusReplicas, err := c.StatusReplicas().Get(obj)
   218  	if err != nil {
   219  		if errors.Is(err, ErrFieldNotFound) {
   220  			// status is probably not yet set on the control plane
   221  			// if status is missing we can consider the control plane to be scaling
   222  			// so that we can block any operations that expect control plane to be stable.
   223  			return true, nil
   224  		}
   225  		return false, errors.Wrap(err, "failed to get control plane status replicas")
   226  	}
   227  
   228  	updatedReplicas, err := c.UpdatedReplicas().Get(obj)
   229  	if err != nil {
   230  		if errors.Is(err, ErrFieldNotFound) {
   231  			// If updatedReplicas is not set on the control plane
   232  			// we should consider the control plane to be scaling so that
   233  			// we block any operation that expect the control plane to be stable.
   234  			return true, nil
   235  		}
   236  		return false, errors.Wrap(err, "failed to get control plane status updatedReplicas")
   237  	}
   238  
   239  	readyReplicas, err := c.ReadyReplicas().Get(obj)
   240  	if err != nil {
   241  		if errors.Is(err, ErrFieldNotFound) {
   242  			// If readyReplicas is not set on the control plane
   243  			// we should consider the control plane to be scaling so that
   244  			// we block any operation that expect the control plane to be stable.
   245  			return true, nil
   246  		}
   247  		return false, errors.Wrap(err, "failed to get control plane status readyReplicas")
   248  	}
   249  
   250  	unavailableReplicas, err := c.UnavailableReplicas().Get(obj)
   251  	if err != nil {
   252  		if !errors.Is(err, ErrFieldNotFound) {
   253  			return false, errors.Wrap(err, "failed to get control plane status unavailableReplicas")
   254  		}
   255  		// If unavailableReplicas is not set on the control plane we assume it is 0.
   256  		// We have to do this as the following happens after clusterctl move with KCP:
   257  		// * clusterctl move creates the KCP object without status
   258  		// * the KCP controller won't patch the field to 0 if it doesn't exist
   259  		//   * This is because the patchHelper marshals before/after object to JSON to calculate a diff
   260  		//     and as the unavailableReplicas field is not a pointer, not set and 0 are both rendered as 0.
   261  		//     If before/after of the field is the same (i.e. 0), there is no diff and thus also no patch to set it to 0.
   262  		unavailableReplicas = ptr.To[int64](0)
   263  	}
   264  
   265  	// Control plane is still scaling if:
   266  	// * .spec.replicas, .status.replicas, .status.updatedReplicas,
   267  	//   .status.readyReplicas are not equal and
   268  	// * unavailableReplicas > 0
   269  	if *statusReplicas != *desiredReplicas ||
   270  		*updatedReplicas != *desiredReplicas ||
   271  		*readyReplicas != *desiredReplicas ||
   272  		*unavailableReplicas > 0 {
   273  		return true, nil
   274  	}
   275  	return false, nil
   276  }
   277  
   278  // ControlPlaneMachineTemplate provides a helper struct for working with MachineTemplate in ClusterClass.
   279  type ControlPlaneMachineTemplate struct{}
   280  
   281  // InfrastructureRef provides access to the infrastructureRef of a MachineTemplate.
   282  func (c *ControlPlaneMachineTemplate) InfrastructureRef() *Ref {
   283  	return &Ref{
   284  		path: Path{"spec", "machineTemplate", "infrastructureRef"},
   285  	}
   286  }
   287  
   288  // Metadata provides access to the metadata of a MachineTemplate.
   289  func (c *ControlPlaneMachineTemplate) Metadata() *Metadata {
   290  	return &Metadata{
   291  		path: Path{"spec", "machineTemplate", "metadata"},
   292  	}
   293  }
   294  
   295  // NodeDrainTimeout provides access to the nodeDrainTimeout of a MachineTemplate.
   296  func (c *ControlPlaneMachineTemplate) NodeDrainTimeout() *Duration {
   297  	return &Duration{
   298  		path: Path{"spec", "machineTemplate", "nodeDrainTimeout"},
   299  	}
   300  }
   301  
   302  // NodeVolumeDetachTimeout provides access to the nodeVolumeDetachTimeout of a MachineTemplate.
   303  func (c *ControlPlaneMachineTemplate) NodeVolumeDetachTimeout() *Duration {
   304  	return &Duration{
   305  		path: Path{"spec", "machineTemplate", "nodeVolumeDetachTimeout"},
   306  	}
   307  }
   308  
   309  // NodeDeletionTimeout provides access to the nodeDeletionTimeout of a MachineTemplate.
   310  func (c *ControlPlaneMachineTemplate) NodeDeletionTimeout() *Duration {
   311  	return &Duration{
   312  		path: Path{"spec", "machineTemplate", "nodeDeletionTimeout"},
   313  	}
   314  }