Skip to content

Commit 05bc270

Browse files
authored
add tests for getReplicaSetFraction in the deployment controller (kubernetes#128535)
* better name variables in deployment_util * add tests for getReplicaSetFraction in the deployment controller - make validation more robust and make sure we do not divide by 0
1 parent 7a4d755 commit 05bc270

File tree

3 files changed

+137
-13
lines changed

3 files changed

+137
-13
lines changed

pkg/controller/deployment/sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ func (dc *DeploymentController) scale(ctx context.Context, deployment *apps.Depl
360360
// Estimate proportions if we have replicas to add, otherwise simply populate
361361
// nameToSize with the current sizes for each replica set.
362362
if deploymentReplicasToAdd != 0 {
363-
proportion := deploymentutil.GetProportion(logger, rs, *deployment, deploymentReplicasToAdd, deploymentReplicasAdded)
363+
proportion := deploymentutil.GetReplicaSetProportion(logger, rs, *deployment, deploymentReplicasToAdd, deploymentReplicasAdded)
364364

365365
nameToSize[rs.Name] = *(rs.Spec.Replicas) + proportion
366366
deploymentReplicasAdded += proportion

pkg/controller/deployment/util/deployment_util.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,10 @@ func MaxSurge(deployment apps.Deployment) int32 {
474474
return maxSurge
475475
}
476476

477-
// GetProportion will estimate the proportion for the provided replica set using 1. the current size
477+
// GetReplicaSetProportion will estimate the proportion for the provided replica set using 1. the current size
478478
// of the parent deployment, 2. the replica count that needs be added on the replica sets of the
479479
// deployment, and 3. the total replicas added in the replica sets of the deployment so far.
480-
func GetProportion(logger klog.Logger, rs *apps.ReplicaSet, d apps.Deployment, deploymentReplicasToAdd, deploymentReplicasAdded int32) int32 {
480+
func GetReplicaSetProportion(logger klog.Logger, rs *apps.ReplicaSet, d apps.Deployment, deploymentReplicasToAdd, deploymentReplicasAdded int32) int32 {
481481
if rs == nil || *(rs.Spec.Replicas) == 0 || deploymentReplicasToAdd == 0 || deploymentReplicasToAdd == deploymentReplicasAdded {
482482
return int32(0)
483483
}
@@ -505,19 +505,25 @@ func getReplicaSetFraction(logger klog.Logger, rs apps.ReplicaSet, d apps.Deploy
505505
return -*(rs.Spec.Replicas)
506506
}
507507

508-
deploymentReplicas := *(d.Spec.Replicas) + MaxSurge(d)
509-
annotatedReplicas, ok := getMaxReplicasAnnotation(logger, &rs)
510-
if !ok {
511-
// If we cannot find the annotation then fallback to the current deployment size. Note that this
512-
// will not be an accurate proportion estimation in case other replica sets have different values
508+
deploymentMaxReplicas := *(d.Spec.Replicas) + MaxSurge(d)
509+
deploymentMaxReplicasBeforeScale, ok := getMaxReplicasAnnotation(logger, &rs)
510+
if !ok || deploymentMaxReplicasBeforeScale == 0 {
511+
// If we cannot find the annotation then fallback to the current deployment size.
512+
// This can occur if someone tampers with the annotation (removes it, sets it to an invalid value, or to 0).
513+
// Note that this will not be an accurate proportion estimation in case other replica sets have different values
513514
// which means that the deployment was scaled at some point but we at least will stay in limits
514-
// due to the min-max comparisons in getProportion.
515-
annotatedReplicas = d.Status.Replicas
515+
// due to the min-max comparisons in GetReplicaSetProportion.
516+
deploymentMaxReplicasBeforeScale = d.Status.Replicas
517+
if deploymentMaxReplicasBeforeScale == 0 {
518+
// Rare situation: missing annotation; some actor has removed it and pods are failing to be created.
519+
return 0
520+
}
516521
}
517522

518-
// We should never proportionally scale up from zero which means rs.spec.replicas and annotatedReplicas
519-
// will never be zero here.
520-
newRSsize := (float64(*(rs.Spec.Replicas) * deploymentReplicas)) / float64(annotatedReplicas)
523+
// We should never proportionally scale up from zero (see GetReplicaSetProportion) which means rs.spec.replicas will never be zero here.
524+
scaleBase := *(rs.Spec.Replicas)
525+
// deploymentMaxReplicasBeforeScale should normally be a positive value, and we have made sure that it is not a zero.
526+
newRSsize := (float64(scaleBase * deploymentMaxReplicas)) / float64(deploymentMaxReplicasBeforeScale)
521527
return integer.RoundToInt32(newRSsize) - *(rs.Spec.Replicas)
522528
}
523529

pkg/controller/deployment/util/deployment_util_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,3 +1401,121 @@ func TestMinAvailable(t *testing.T) {
14011401
})
14021402
}
14031403
}
1404+
1405+
func TestGetReplicaSetFraction(t *testing.T) {
1406+
tests := []struct {
1407+
name string
1408+
enableDeploymentPodReplacementPolicy bool
1409+
deploymentReplicas int32
1410+
deploymentStatusReplicas int32
1411+
deploymentMaxSurge int32
1412+
rsReplicas int32
1413+
rsAnnotations map[string]string
1414+
expectedFraction int32
1415+
}{
1416+
{
1417+
name: "empty deployment always scales to 0",
1418+
deploymentReplicas: 0,
1419+
rsReplicas: 10,
1420+
expectedFraction: -10,
1421+
},
1422+
{
1423+
name: "unsynced deployment does not scale when max-replicas annotation is missing (removed by a 3rd party)",
1424+
deploymentReplicas: 10,
1425+
rsReplicas: 5,
1426+
expectedFraction: 0,
1427+
},
1428+
{
1429+
name: "unsynced deployment does not scale when max-replicas annotation is incorrectly set to 0 (by a 3rd party)",
1430+
deploymentReplicas: 10,
1431+
rsReplicas: 5,
1432+
rsAnnotations: map[string]string{
1433+
MaxReplicasAnnotation: "0",
1434+
},
1435+
expectedFraction: 0,
1436+
},
1437+
{
1438+
name: "scale up by 1/5 should increase RS replicas by 1/5 when max-replicas annotation is missing (removed by a 3rd party)",
1439+
deploymentReplicas: 120,
1440+
deploymentStatusReplicas: 100,
1441+
rsReplicas: 50,
1442+
expectedFraction: 10,
1443+
},
1444+
{
1445+
name: "scale up by 1/5 should increase RS replicas by 1/5 when max-replicas annotation is incorrectly set to 0 (by a 3rd party)",
1446+
deploymentReplicas: 120,
1447+
deploymentStatusReplicas: 100,
1448+
rsReplicas: 50,
1449+
rsAnnotations: map[string]string{
1450+
MaxReplicasAnnotation: "0",
1451+
},
1452+
expectedFraction: 10,
1453+
},
1454+
{
1455+
name: "scale up by 1/5 should increase RS replicas by 1/5",
1456+
deploymentReplicas: 120,
1457+
rsReplicas: 50,
1458+
rsAnnotations: map[string]string{
1459+
MaxReplicasAnnotation: "100",
1460+
},
1461+
expectedFraction: 10,
1462+
},
1463+
{
1464+
name: "scale up with maxSurge by 1/5 should increase RS replicas approximately by 1/5",
1465+
deploymentReplicas: 120,
1466+
deploymentMaxSurge: 10,
1467+
rsReplicas: 50,
1468+
rsAnnotations: map[string]string{
1469+
MaxReplicasAnnotation: "110",
1470+
},
1471+
// expectedFraction is not the whole 1/5 (10) since maxSurge pods have to be taken into account
1472+
// and replica sets with these surge pods should proportionally scale as well during a rollout
1473+
expectedFraction: 9,
1474+
},
1475+
{
1476+
name: "scale down by 1/6 should decrease RS replicas by 1/6",
1477+
deploymentReplicas: 10,
1478+
rsReplicas: 6,
1479+
rsAnnotations: map[string]string{
1480+
MaxReplicasAnnotation: "12",
1481+
},
1482+
expectedFraction: -1,
1483+
},
1484+
{
1485+
name: "scale down with maxSurge by 1/6 should decrease RS replicas approximately by 1/6",
1486+
deploymentReplicas: 100,
1487+
deploymentMaxSurge: 10,
1488+
rsReplicas: 50,
1489+
rsAnnotations: map[string]string{
1490+
MaxReplicasAnnotation: "130",
1491+
},
1492+
expectedFraction: -8,
1493+
},
1494+
}
1495+
1496+
for _, test := range tests {
1497+
t.Run(test.name, func(t *testing.T) {
1498+
logger, _ := ktesting.NewTestContext(t)
1499+
1500+
tDeployment := generateDeployment("nginx")
1501+
tDeployment.Status.Replicas = test.deploymentStatusReplicas
1502+
tDeployment.Spec.Replicas = ptr.To(test.deploymentReplicas)
1503+
tDeployment.Spec.Strategy = apps.DeploymentStrategy{
1504+
Type: apps.RollingUpdateDeploymentStrategyType,
1505+
RollingUpdate: &apps.RollingUpdateDeployment{
1506+
MaxSurge: ptr.To(intstr.FromInt32(test.deploymentMaxSurge)),
1507+
MaxUnavailable: ptr.To(intstr.FromInt32(1)),
1508+
},
1509+
}
1510+
1511+
tRS := generateRS(tDeployment)
1512+
tRS.Annotations = test.rsAnnotations
1513+
tRS.Spec.Replicas = ptr.To(test.rsReplicas)
1514+
1515+
fraction := getReplicaSetFraction(logger, tRS, tDeployment)
1516+
if test.expectedFraction != fraction {
1517+
t.Fatalf("expected fraction: %v, got:%v", test.expectedFraction, fraction)
1518+
}
1519+
})
1520+
}
1521+
}

0 commit comments

Comments
 (0)