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 }