sigs.k8s.io/cluster-api@v1.7.1/internal/contract/controlplane_test.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 "testing" 21 "time" 22 23 . "github.com/onsi/gomega" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 27 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 28 ) 29 30 func TestControlPlane(t *testing.T) { 31 obj := &unstructured.Unstructured{Object: map[string]interface{}{}} 32 33 t.Run("Manages spec.version", func(t *testing.T) { 34 g := NewWithT(t) 35 36 g.Expect(ControlPlane().Version().Path()).To(Equal(Path{"spec", "version"})) 37 38 err := ControlPlane().Version().Set(obj, "vFoo") 39 g.Expect(err).ToNot(HaveOccurred()) 40 41 got, err := ControlPlane().Version().Get(obj) 42 g.Expect(err).ToNot(HaveOccurred()) 43 g.Expect(got).ToNot(BeNil()) 44 g.Expect(*got).To(Equal("vFoo")) 45 }) 46 t.Run("Manages status.version", func(t *testing.T) { 47 g := NewWithT(t) 48 49 g.Expect(ControlPlane().StatusVersion().Path()).To(Equal(Path{"status", "version"})) 50 51 err := ControlPlane().StatusVersion().Set(obj, "1.2.3") 52 g.Expect(err).ToNot(HaveOccurred()) 53 54 got, err := ControlPlane().StatusVersion().Get(obj) 55 g.Expect(err).ToNot(HaveOccurred()) 56 g.Expect(got).ToNot(BeNil()) 57 g.Expect(*got).To(Equal("1.2.3")) 58 }) 59 t.Run("Manages status.ready", func(t *testing.T) { 60 g := NewWithT(t) 61 62 g.Expect(ControlPlane().Ready().Path()).To(Equal(Path{"status", "ready"})) 63 64 err := ControlPlane().Ready().Set(obj, true) 65 g.Expect(err).ToNot(HaveOccurred()) 66 67 got, err := ControlPlane().Ready().Get(obj) 68 g.Expect(err).ToNot(HaveOccurred()) 69 g.Expect(got).ToNot(BeNil()) 70 g.Expect(*got).To(BeTrue()) 71 }) 72 t.Run("Manages status.initialized", func(t *testing.T) { 73 g := NewWithT(t) 74 75 g.Expect(ControlPlane().Initialized().Path()).To(Equal(Path{"status", "initialized"})) 76 77 err := ControlPlane().Initialized().Set(obj, true) 78 g.Expect(err).ToNot(HaveOccurred()) 79 80 got, err := ControlPlane().Initialized().Get(obj) 81 g.Expect(err).ToNot(HaveOccurred()) 82 g.Expect(got).ToNot(BeNil()) 83 g.Expect(*got).To(BeTrue()) 84 }) 85 t.Run("Manages spec.replicas", func(t *testing.T) { 86 g := NewWithT(t) 87 88 g.Expect(ControlPlane().Replicas().Path()).To(Equal(Path{"spec", "replicas"})) 89 90 err := ControlPlane().Replicas().Set(obj, int64(3)) 91 g.Expect(err).ToNot(HaveOccurred()) 92 93 got, err := ControlPlane().Replicas().Get(obj) 94 g.Expect(err).ToNot(HaveOccurred()) 95 g.Expect(got).ToNot(BeNil()) 96 g.Expect(*got).To(Equal(int64(3))) 97 }) 98 t.Run("Manages status.replicas", func(t *testing.T) { 99 g := NewWithT(t) 100 101 g.Expect(ControlPlane().StatusReplicas().Path()).To(Equal(Path{"status", "replicas"})) 102 103 err := ControlPlane().StatusReplicas().Set(obj, int64(3)) 104 g.Expect(err).ToNot(HaveOccurred()) 105 106 got, err := ControlPlane().StatusReplicas().Get(obj) 107 g.Expect(err).ToNot(HaveOccurred()) 108 g.Expect(got).ToNot(BeNil()) 109 g.Expect(*got).To(Equal(int64(3))) 110 }) 111 t.Run("Manages status.updatedreplicas", func(t *testing.T) { 112 g := NewWithT(t) 113 114 g.Expect(ControlPlane().UpdatedReplicas().Path()).To(Equal(Path{"status", "updatedReplicas"})) 115 116 err := ControlPlane().UpdatedReplicas().Set(obj, int64(3)) 117 g.Expect(err).ToNot(HaveOccurred()) 118 119 got, err := ControlPlane().UpdatedReplicas().Get(obj) 120 g.Expect(err).ToNot(HaveOccurred()) 121 g.Expect(got).ToNot(BeNil()) 122 g.Expect(*got).To(Equal(int64(3))) 123 }) 124 t.Run("Manages status.readyReplicas", func(t *testing.T) { 125 g := NewWithT(t) 126 127 g.Expect(ControlPlane().ReadyReplicas().Path()).To(Equal(Path{"status", "readyReplicas"})) 128 129 err := ControlPlane().ReadyReplicas().Set(obj, int64(3)) 130 g.Expect(err).ToNot(HaveOccurred()) 131 132 got, err := ControlPlane().ReadyReplicas().Get(obj) 133 g.Expect(err).ToNot(HaveOccurred()) 134 g.Expect(got).ToNot(BeNil()) 135 g.Expect(*got).To(Equal(int64(3))) 136 }) 137 t.Run("Manages status.unavailableReplicas", func(t *testing.T) { 138 g := NewWithT(t) 139 140 g.Expect(ControlPlane().UnavailableReplicas().Path()).To(Equal(Path{"status", "unavailableReplicas"})) 141 142 err := ControlPlane().UnavailableReplicas().Set(obj, int64(3)) 143 g.Expect(err).ToNot(HaveOccurred()) 144 145 got, err := ControlPlane().UnavailableReplicas().Get(obj) 146 g.Expect(err).ToNot(HaveOccurred()) 147 g.Expect(got).ToNot(BeNil()) 148 g.Expect(*got).To(Equal(int64(3))) 149 }) 150 t.Run("Manages status.selector", func(t *testing.T) { 151 g := NewWithT(t) 152 153 g.Expect(ControlPlane().Selector().Path()).To(Equal(Path{"status", "selector"})) 154 155 err := ControlPlane().Selector().Set(obj, "my-selector") 156 g.Expect(err).ToNot(HaveOccurred()) 157 158 got, err := ControlPlane().Selector().Get(obj) 159 g.Expect(err).ToNot(HaveOccurred()) 160 g.Expect(got).ToNot(BeNil()) 161 g.Expect(*got).To(Equal("my-selector")) 162 }) 163 t.Run("Manages spec.machineTemplate.infrastructureRef", func(t *testing.T) { 164 g := NewWithT(t) 165 166 refObj := fooRefBuilder() 167 168 g.Expect(ControlPlane().MachineTemplate().InfrastructureRef().Path()).To(Equal(Path{"spec", "machineTemplate", "infrastructureRef"})) 169 170 err := ControlPlane().MachineTemplate().InfrastructureRef().Set(obj, refObj) 171 g.Expect(err).ToNot(HaveOccurred()) 172 173 got, err := ControlPlane().MachineTemplate().InfrastructureRef().Get(obj) 174 g.Expect(err).ToNot(HaveOccurred()) 175 g.Expect(got).ToNot(BeNil()) 176 g.Expect(got.APIVersion).To(Equal(refObj.GetAPIVersion())) 177 g.Expect(got.Kind).To(Equal(refObj.GetKind())) 178 g.Expect(got.Name).To(Equal(refObj.GetName())) 179 g.Expect(got.Namespace).To(Equal(refObj.GetNamespace())) 180 }) 181 t.Run("Manages spec.machineTemplate.metadata", func(t *testing.T) { 182 g := NewWithT(t) 183 184 metadata := &clusterv1.ObjectMeta{ 185 Labels: map[string]string{ 186 "label1": "labelValue1", 187 }, 188 Annotations: map[string]string{ 189 "annotation1": "annotationValue1", 190 }, 191 } 192 193 g.Expect(ControlPlane().MachineTemplate().Metadata().Path()).To(Equal(Path{"spec", "machineTemplate", "metadata"})) 194 195 err := ControlPlane().MachineTemplate().Metadata().Set(obj, metadata) 196 g.Expect(err).ToNot(HaveOccurred()) 197 198 got, err := ControlPlane().MachineTemplate().Metadata().Get(obj) 199 g.Expect(err).ToNot(HaveOccurred()) 200 g.Expect(got).ToNot(BeNil()) 201 g.Expect(got).To(BeComparableTo(metadata)) 202 }) 203 204 t.Run("Manages spec.machineTemplate.nodeDrainTimeout", func(t *testing.T) { 205 g := NewWithT(t) 206 207 duration := metav1.Duration{Duration: 2*time.Minute + 5*time.Second} 208 expectedDurationString := "2m5s" 209 g.Expect(ControlPlane().MachineTemplate().NodeDrainTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeDrainTimeout"})) 210 211 err := ControlPlane().MachineTemplate().NodeDrainTimeout().Set(obj, duration) 212 g.Expect(err).ToNot(HaveOccurred()) 213 214 got, err := ControlPlane().MachineTemplate().NodeDrainTimeout().Get(obj) 215 g.Expect(err).ToNot(HaveOccurred()) 216 g.Expect(got).ToNot(BeNil()) 217 g.Expect(*got).To(Equal(duration)) 218 219 // Check that the literal string value of the duration is correctly formatted. 220 durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeDrainTimeout") 221 g.Expect(err).ToNot(HaveOccurred()) 222 g.Expect(found).To(BeTrue()) 223 g.Expect(durationString).To(Equal(expectedDurationString)) 224 }) 225 226 t.Run("Manages spec.machineTemplate.nodeVolumeDetachTimeout", func(t *testing.T) { 227 g := NewWithT(t) 228 229 duration := metav1.Duration{Duration: 2*time.Minute + 10*time.Second} 230 expectedDurationString := "2m10s" 231 g.Expect(ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeVolumeDetachTimeout"})) 232 233 err := ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Set(obj, duration) 234 g.Expect(err).ToNot(HaveOccurred()) 235 236 got, err := ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Get(obj) 237 g.Expect(err).ToNot(HaveOccurred()) 238 g.Expect(got).ToNot(BeNil()) 239 g.Expect(*got).To(Equal(duration)) 240 241 // Check that the literal string value of the duration is correctly formatted. 242 durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeVolumeDetachTimeout") 243 g.Expect(err).ToNot(HaveOccurred()) 244 g.Expect(found).To(BeTrue()) 245 g.Expect(durationString).To(Equal(expectedDurationString)) 246 }) 247 248 t.Run("Manages spec.machineTemplate.nodeDeletionTimeout", func(t *testing.T) { 249 g := NewWithT(t) 250 251 duration := metav1.Duration{Duration: 2*time.Minute + 5*time.Second} 252 expectedDurationString := "2m5s" 253 g.Expect(ControlPlane().MachineTemplate().NodeDeletionTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeDeletionTimeout"})) 254 255 err := ControlPlane().MachineTemplate().NodeDeletionTimeout().Set(obj, duration) 256 g.Expect(err).ToNot(HaveOccurred()) 257 258 got, err := ControlPlane().MachineTemplate().NodeDeletionTimeout().Get(obj) 259 g.Expect(err).ToNot(HaveOccurred()) 260 g.Expect(got).ToNot(BeNil()) 261 g.Expect(*got).To(Equal(duration)) 262 263 // Check that the literal string value of the duration is correctly formatted. 264 durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeDeletionTimeout") 265 g.Expect(err).ToNot(HaveOccurred()) 266 g.Expect(found).To(BeTrue()) 267 g.Expect(durationString).To(Equal(expectedDurationString)) 268 }) 269 } 270 271 func TestControlPlaneIsUpgrading(t *testing.T) { 272 tests := []struct { 273 name string 274 obj *unstructured.Unstructured 275 wantUpgrading bool 276 }{ 277 { 278 name: "should return false if status is not set on control plane", 279 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 280 "spec": map[string]interface{}{ 281 "version": "v1.2.3", 282 }, 283 }}, 284 wantUpgrading: false, 285 }, 286 { 287 name: "should return false if status.version is not set on control plane", 288 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 289 "spec": map[string]interface{}{ 290 "version": "v1.2.3", 291 }, 292 "status": map[string]interface{}{}, 293 }}, 294 wantUpgrading: false, 295 }, 296 { 297 name: "should return false if status.version is equal to spec.version", 298 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 299 "spec": map[string]interface{}{ 300 "version": "v1.2.3", 301 }, 302 "status": map[string]interface{}{ 303 "version": "v1.2.3", 304 }, 305 }}, 306 wantUpgrading: false, 307 }, 308 { 309 name: "should return true if status.version is less than spec.version", 310 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 311 "spec": map[string]interface{}{ 312 "version": "v1.2.3", 313 }, 314 "status": map[string]interface{}{ 315 "version": "v1.2.2", 316 }, 317 }}, 318 wantUpgrading: true, 319 }, 320 { 321 name: "should return false if status.version is greater than spec.version", 322 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 323 "spec": map[string]interface{}{ 324 "version": "v1.2.2", 325 }, 326 "status": map[string]interface{}{ 327 "version": "v1.2.3", 328 }, 329 }}, 330 wantUpgrading: false, 331 }, 332 } 333 for _, tt := range tests { 334 t.Run(tt.name, func(t *testing.T) { 335 g := NewWithT(t) 336 337 actual, _ := ControlPlane().IsUpgrading(tt.obj) 338 g.Expect(actual).To(Equal(tt.wantUpgrading)) 339 }) 340 } 341 } 342 343 func TestControlPlaneIsScaling(t *testing.T) { 344 tests := []struct { 345 name string 346 obj *unstructured.Unstructured 347 wantScaling bool 348 }{ 349 { 350 name: "should return false for stable control plane", 351 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 352 "spec": map[string]interface{}{ 353 "replicas": int64(2), 354 }, 355 "status": map[string]interface{}{ 356 "replicas": int64(2), 357 "updatedReplicas": int64(2), 358 "readyReplicas": int64(2), 359 "unavailableReplicas": int64(0), 360 }, 361 }}, 362 wantScaling: false, 363 }, 364 { 365 name: "should return true if status is not set on control plane", 366 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 367 "spec": map[string]interface{}{ 368 "replicas": int64(2), 369 }, 370 }}, 371 wantScaling: true, 372 }, 373 { 374 name: "should return true if status replicas is not set on control plane", 375 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 376 "spec": map[string]interface{}{ 377 "replicas": int64(2), 378 }, 379 "status": map[string]interface{}{ 380 "updatedReplicas": int64(2), 381 "readyReplicas": int64(2), 382 "unavailableReplicas": int64(0), 383 }, 384 }}, 385 wantScaling: true, 386 }, 387 { 388 name: "should return true if spec replicas and status replicas do not match", 389 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 390 "spec": map[string]interface{}{ 391 "replicas": int64(2), 392 }, 393 "status": map[string]interface{}{ 394 "replicas": int64(1), 395 "updatedReplicas": int64(2), 396 "readyReplicas": int64(2), 397 "unavailableReplicas": int64(0), 398 }, 399 }}, 400 wantScaling: true, 401 }, 402 { 403 name: "should return true if status updatedReplicas is not set on control plane", 404 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 405 "spec": map[string]interface{}{ 406 "replicas": int64(2), 407 }, 408 "status": map[string]interface{}{ 409 "replicas": int64(2), 410 "readyReplicas": int64(2), 411 "unavailableReplicas": int64(0), 412 }, 413 }}, 414 wantScaling: true, 415 }, 416 { 417 name: "should return true if spec replicas and status updatedReplicas do not match", 418 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 419 "spec": map[string]interface{}{ 420 "replicas": int64(2), 421 }, 422 "status": map[string]interface{}{ 423 "replicas": int64(2), 424 "updatedReplicas": int64(1), 425 "readyReplicas": int64(2), 426 "unavailableReplicas": int64(0), 427 }, 428 }}, 429 wantScaling: true, 430 }, 431 { 432 name: "should return true if status readyReplicas is not set on control plane", 433 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 434 "spec": map[string]interface{}{ 435 "replicas": int64(2), 436 }, 437 "status": map[string]interface{}{ 438 "replicas": int64(2), 439 "updatedReplicas": int64(2), 440 "unavailableReplicas": int64(0), 441 }, 442 }}, 443 wantScaling: true, 444 }, 445 { 446 name: "should return true if spec replicas and status readyReplicas do not match", 447 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 448 "spec": map[string]interface{}{ 449 "replicas": int64(2), 450 }, 451 "status": map[string]interface{}{ 452 "replicas": int64(2), 453 "updatedReplicas": int64(2), 454 "readyReplicas": int64(1), 455 "unavailableReplicas": int64(0), 456 }, 457 }}, 458 wantScaling: true, 459 }, 460 { 461 name: "should return false if status unavailableReplicas is not set on control plane", 462 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 463 "spec": map[string]interface{}{ 464 "replicas": int64(2), 465 }, 466 "status": map[string]interface{}{ 467 "replicas": int64(2), 468 "updatedReplicas": int64(2), 469 "readyReplicas": int64(2), 470 }, 471 }}, 472 wantScaling: false, 473 }, 474 { 475 name: "should return true if status unavailableReplicas is > 0", 476 obj: &unstructured.Unstructured{Object: map[string]interface{}{ 477 "spec": map[string]interface{}{ 478 "replicas": int64(2), 479 }, 480 "status": map[string]interface{}{ 481 "replicas": int64(2), 482 "updatedReplicas": int64(2), 483 "readyReplicas": int64(2), 484 "unavailableReplicas": int64(1), 485 }, 486 }}, 487 wantScaling: true, 488 }, 489 } 490 for _, tt := range tests { 491 t.Run(tt.name, func(t *testing.T) { 492 g := NewWithT(t) 493 494 actual, err := ControlPlane().IsScaling(tt.obj) 495 g.Expect(err).ToNot(HaveOccurred()) 496 g.Expect(actual).To(Equal(tt.wantScaling)) 497 }) 498 } 499 }