Skip to content

Commit e499366

Browse files
committed
ClusterClass: IsScaling now considers unavailableReplicas
Signed-off-by: Stefan Büringer [email protected]
1 parent b3665e0 commit e499366

File tree

2 files changed

+102
-16
lines changed

2 files changed

+102
-16
lines changed

internal/contract/controlplane.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/blang/semver"
2323
"github.com/pkg/errors"
2424
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/utils/pointer"
2526

2627
"sigs.k8s.io/cluster-api/util/version"
2728
)
@@ -245,9 +246,29 @@ func (c *ControlPlaneContract) IsScaling(obj *unstructured.Unstructured) (bool,
245246
return false, errors.Wrap(err, "failed to get control plane status readyReplicas")
246247
}
247248

249+
unavailableReplicas, err := c.UnavailableReplicas().Get(obj)
250+
if err != nil && !errors.Is(err, errNotFound) {
251+
if !errors.Is(err, errNotFound) {
252+
return false, errors.Wrap(err, "failed to get control plane status unavailableReplicas")
253+
}
254+
// If unavailableReplicas is not set on the control plane we assume it is 0.
255+
// We have to do this as the following happens after clusterctl move with KCP:
256+
// * clusterctl move creates the KCP object without status
257+
// * the KCP controller won't patch the field to 0 if it doesn't exist
258+
// * This is because the patchHelper marshals before/after object to JSON to calculate a diff
259+
// and as the unavailableReplicas field is not a pointer, not set and 0 are both rendered as 0.
260+
// 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.
261+
unavailableReplicas = pointer.Int64(0)
262+
}
263+
264+
// Control plane is still scaling if:
265+
// * .spec.replicas, .status.replicas, .status.updatedReplicas,
266+
// .status.readyReplicas are not equal and
267+
// * unavailableReplicas > 0
248268
if *statusReplicas != *desiredReplicas ||
249269
*updatedReplicas != *desiredReplicas ||
250-
*readyReplicas != *desiredReplicas {
270+
*readyReplicas != *desiredReplicas ||
271+
*unavailableReplicas > 0 {
251272
return true, nil
252273
}
253274
return false, nil

internal/contract/controlplane_test.go

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,10 @@ func TestControlPlaneIsScaling(t *testing.T) {
353353
"replicas": int64(2),
354354
},
355355
"status": map[string]interface{}{
356-
"replicas": int64(2),
357-
"updatedReplicas": int64(2),
358-
"readyReplicas": int64(2),
356+
"replicas": int64(2),
357+
"updatedReplicas": int64(2),
358+
"readyReplicas": int64(2),
359+
"unavailableReplicas": int64(0),
359360
},
360361
}},
361362
wantScaling: false,
@@ -370,53 +371,117 @@ func TestControlPlaneIsScaling(t *testing.T) {
370371
wantScaling: true,
371372
},
372373
{
373-
name: "should return true if status.replicas is not set on control plane",
374+
name: "should return true if status replicas is not set on control plane",
374375
obj: &unstructured.Unstructured{Object: map[string]interface{}{
375376
"spec": map[string]interface{}{
376377
"replicas": int64(2),
377378
},
378-
"status": map[string]interface{}{},
379+
"status": map[string]interface{}{
380+
"updatedReplicas": int64(2),
381+
"readyReplicas": int64(2),
382+
"unavailableReplicas": int64(0),
383+
},
379384
}},
380385
wantScaling: true,
381386
},
382387
{
383-
name: "should return true if spec and status replicas do not match",
388+
name: "should return true if spec replicas and status replicas do not match",
384389
obj: &unstructured.Unstructured{Object: map[string]interface{}{
385390
"spec": map[string]interface{}{
386391
"replicas": int64(2),
387392
},
388393
"status": map[string]interface{}{
389-
"replicas": int64(1),
390-
"updatedReplicas": int64(2),
391-
"readyReplicas": int64(2),
394+
"replicas": int64(1),
395+
"updatedReplicas": int64(2),
396+
"readyReplicas": int64(2),
397+
"unavailableReplicas": int64(0),
392398
},
393399
}},
394400
wantScaling: true,
395401
},
396402
{
397-
name: "should return true if spec and status updatedReplicas do not match",
403+
name: "should return true if status updatedReplicas is not set on control plane",
398404
obj: &unstructured.Unstructured{Object: map[string]interface{}{
399405
"spec": map[string]interface{}{
400406
"replicas": int64(2),
401407
},
402408
"status": map[string]interface{}{
403-
"replicas": int64(2),
404-
"updatedReplicas": int64(1),
405-
"readyReplicas": int64(2),
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),
406427
},
407428
}},
408429
wantScaling: true,
409430
},
410431
{
411-
name: "should return true if spec and status readyReplicas do not match",
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",
412462
obj: &unstructured.Unstructured{Object: map[string]interface{}{
413463
"spec": map[string]interface{}{
414464
"replicas": int64(2),
415465
},
416466
"status": map[string]interface{}{
417467
"replicas": int64(2),
418468
"updatedReplicas": int64(2),
419-
"readyReplicas": int64(1),
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),
420485
},
421486
}},
422487
wantScaling: true,

0 commit comments

Comments
 (0)