Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,12 @@ func withStatusAvailableReplicas(r int32) fakeMachineSetOption {
}
}

func withStatusUpToDateReplicas(r int32) fakeMachineSetOption {
return func(ms *clusterv1.MachineSet) {
ms.Status.UpToDateReplicas = ptr.To(r)
}
}

func withMSAnnotation(name, value string) fakeMachineSetOption {
return func(ms *clusterv1.MachineSet) {
if ms.Annotations == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import (
ctrl "sigs.k8s.io/controller-runtime"

clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
runtimehooksv1 "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1"
"sigs.k8s.io/cluster-api/feature"
"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
"sigs.k8s.io/cluster-api/internal/hooks"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/collections"
)
Expand Down Expand Up @@ -502,6 +504,7 @@ func (p *rolloutPlanner) reconcileInPlaceUpdateIntent(ctx context.Context) error
}

func (p *rolloutPlanner) scalingOrInPlaceUpdateInProgress(_ context.Context) bool {
// Check if the new MS or old MS are scaling.
if ptr.Deref(p.newMS.Spec.Replicas, 0) != ptr.Deref(p.newMS.Status.Replicas, 0) {
return true
}
Expand All @@ -513,11 +516,30 @@ func (p *rolloutPlanner) scalingOrInPlaceUpdateInProgress(_ context.Context) boo
return true
}
}

// Check that there are no updates in progress.
// We check both that the newMS MachineSet will report that the update is completed via .status.upToDateReplicas
// and the Machine controller through the annotations so this code does not depend on a specific execution order
// of the MachineSet and Machine controllers.
if ptr.Deref(p.newMS.Spec.Replicas, 0) != ptr.Deref(p.newMS.Status.UpToDateReplicas, 0) {
return true
}
for _, m := range p.machines {
if _, ok := m.Annotations[clusterv1.UpdateInProgressAnnotation]; ok {
_, inPlaceUpdateInProgress := m.Annotations[clusterv1.UpdateInProgressAnnotation]
hasUpdateMachinePending := hooks.IsPending(runtimehooksv1.UpdateMachine, m)
if inPlaceUpdateInProgress || hasUpdateMachinePending {
return true
}
}

// We are also checking AvailableReplicas because we want to make sure that we wait until the
// Machine goes back to Available after the in-place update is completed.
// If we would not wait for this, the rolloutPlaner would use maxSurge to create an additional Machine.
// Note: This also means that if any Machine of the new MachineSet becomes unavailable we are blocking
// further progress of the in-place update.
if ptr.Deref(p.newMS.Spec.Replicas, 0) != ptr.Deref(p.newMS.Status.AvailableReplicas, 0) {
return true
}
return false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
{
name: "When moving replicas from oldMS to newMS, preserve newMS scale up intent if it does not use maxSurge",
md: createMD("v2", 3, withRollingUpdateStrategy(1, 0)),
newMS: createMS("ms2", "v2", 1),
newMS: createMS("ms2", "v2", 1, withStatusUpToDateReplicas(1), withStatusAvailableReplicas(1)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 1),
},
Expand Down Expand Up @@ -909,7 +909,7 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
{
name: "When moving replicas from oldMS to newMS, preserve one usage of MaxSurge in the newMS scale up intent when required to start the rollout (maxSurge 1, maxUnavailable 0)",
md: createMD("v2", 3, withRollingUpdateStrategy(1, 0)),
newMS: createMS("ms2", "v2", 0),
newMS: createMS("ms2", "v2", 0, withStatusUpToDateReplicas(0), withStatusAvailableReplicas(0)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3),
},
Expand Down Expand Up @@ -939,7 +939,7 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
{
name: "When moving replicas from oldMS to newMS, preserve one usage of MaxSurge in the newMS scale up intent when required to start the rollout (maxSurge 3, maxUnavailable 0)",
md: createMD("v2", 3, withRollingUpdateStrategy(3, 0)),
newMS: createMS("ms2", "v2", 0),
newMS: createMS("ms2", "v2", 0, withStatusUpToDateReplicas(0), withStatusAvailableReplicas(0)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3),
},
Expand Down Expand Up @@ -967,9 +967,39 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
},
},
{
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there are oldMS with scale down intent (maxSurge 3, maxUnavailable 1)",
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there are oldMS with scale down from a previous reconcile (maxSurge 3, maxUnavailable 1)",
md: createMD("v2", 3, withRollingUpdateStrategy(3, 1)),
newMS: createMS("ms2", "v2", 0),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3, withStatusReplicas(4), withStatusUpToDateReplicas(4), withStatusAvailableReplicas(4)), // scale down from a previous reconcile
},
machines: []*clusterv1.Machine{
createM("m1", "ms1", "v1"),
createM("m2", "ms1", "v1"),
createM("m3", "ms1", "v1"),
createM("m4", "ms1", "v1"),
},
scaleIntents: map[string]int32{
"ms2": 2, // +2 => MD expect 3, has currently 4 replicas, +2 replica it is using maxSurge 3
},
upToDateResults: map[string]mdutil.UpToDateResult{
"ms1": {EligibleForInPlaceUpdate: true},
},
expectedCanUpdateCalls: map[string]bool{
"ms1": true,
},
canUpdateAnswer: map[string]bool{
"ms1": true,
},
expectMoveFromMS: []string{"ms1"},
expectScaleIntents: map[string]int32{
// "ms2": 3, +3 replica using maxSurge dropped, oldMS is scaling down
},
},
{
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there are oldMS with scale down intent (maxSurge 3, maxUnavailable 1)",
md: createMD("v2", 3, withRollingUpdateStrategy(3, 1)),
newMS: createMS("ms2", "v2", 0, withStatusUpToDateReplicas(0), withStatusAvailableReplicas(0)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3),
},
Expand Down Expand Up @@ -998,20 +1028,22 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
},
},
{
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there are oldMS with scale down from a previous reconcile (maxSurge 3, maxUnavailable 1)",
md: createMD("v2", 3, withRollingUpdateStrategy(3, 1)),
newMS: createMS("ms2", "v2", 0),
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there newMS is scaling from a previous reconcile (maxSurge 3, maxUnavailable 1)",
md: createMD("v6", 6, withRollingUpdateStrategy(3, 1)),
newMS: createMS("ms2", "v2", 3, withStatusReplicas(2), withStatusUpToDateReplicas(2), withStatusAvailableReplicas(2)), // scaling from a previous reconcile
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3, withStatusReplicas(4)), // scale down from a previous reconcile
createMS("ms1", "v1", 3),
},
machines: []*clusterv1.Machine{
createM("m1", "ms1", "v1"),
createM("m2", "ms1", "v1"),
createM("m3", "ms1", "v1"),
createM("m4", "ms1", "v1"),
createM("m4", "ms2", "v2"),
createM("m5", "ms2", "v2"),
createM("m6", "ms2", "v2"),
},
scaleIntents: map[string]int32{
"ms2": 2, // +2 => MD expect 3, has currently 4 replicas, +2 replica it is using maxSurge 3
"ms2": 6, // +3 => MD expect 6, has currently 6 replicas, +3 replica it is using maxSurge 3
},
upToDateResults: map[string]mdutil.UpToDateResult{
"ms1": {EligibleForInPlaceUpdate: true},
Expand All @@ -1024,13 +1056,42 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
},
expectMoveFromMS: []string{"ms1"},
expectScaleIntents: map[string]int32{
// "ms2": 3, +3 replica using maxSurge dropped, oldMS is scaling down
// "ms2": 6, +3 replicas from maxSurge dropped, newMS is already scaling up
},
},
{
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there are still not UpToDateReplicas on the newMS (maxSurge 1, maxUnavailable 0)",
md: createMD("v2", 3, withRollingUpdateStrategy(1, 0)),
newMS: createMS("ms2", "v2", 2, withStatusUpToDateReplicas(1), withStatusAvailableReplicas(1)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 1),
},
machines: []*clusterv1.Machine{
createM("m1", "ms1", "v1"),
createM("m2", "ms2", "v1"),
createM("m3", "ms2", "v1"),
},
scaleIntents: map[string]int32{
"ms2": 3, // +1 => MD expect 3, has currently 3 replicas, +1 replica it is using maxSurge
},
upToDateResults: map[string]mdutil.UpToDateResult{
"ms1": {EligibleForInPlaceUpdate: true},
},
expectedCanUpdateCalls: map[string]bool{
"ms1": true,
},
canUpdateAnswer: map[string]bool{
"ms1": true,
},
expectMoveFromMS: []string{"ms1"},
expectScaleIntents: map[string]int32{
// "ms2": 1, +1 replica using maxSurge dropped, there is a machine updating in place
},
},
{
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there machines in-place updating (maxSurge 3, maxUnavailable 0)",
md: createMD("v2", 3, withRollingUpdateStrategy(3, 0)),
newMS: createMS("ms2", "v2", 0),
newMS: createMS("ms2", "v2", 0, withStatusUpToDateReplicas(0), withStatusAvailableReplicas(0)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3),
},
Expand All @@ -1057,22 +1118,19 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
},
},
{
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there newMS is scaling from a previous reconcile (maxSurge 3, maxUnavailable 1)",
md: createMD("v6", 6, withRollingUpdateStrategy(3, 1)),
newMS: createMS("ms2", "v2", 3, withStatusReplicas(2)), // scaling from a previous reconcile
name: "When moving replicas from oldMS to newMS, drop usage of MaxSurge in the newMS scale up intent when there are still not AvailableReplicas on the newMS (maxSurge 1, maxUnavailable 0)",
md: createMD("v2", 3, withRollingUpdateStrategy(1, 0)),
newMS: createMS("ms2", "v2", 2, withStatusUpToDateReplicas(2), withStatusAvailableReplicas(1)),
oldMS: []*clusterv1.MachineSet{
createMS("ms1", "v1", 3),
createMS("ms1", "v1", 1),
},
machines: []*clusterv1.Machine{
createM("m1", "ms1", "v1"),
createM("m2", "ms1", "v1"),
createM("m3", "ms1", "v1"),
createM("m4", "ms2", "v2"),
createM("m5", "ms2", "v2"),
createM("m6", "ms2", "v2"),
createM("m2", "ms2", "v1"),
createM("m3", "ms2", "v1"),
},
scaleIntents: map[string]int32{
"ms2": 6, // +3 => MD expect 6, has currently 6 replicas, +3 replica it is using maxSurge 3
"ms2": 3, // +1 => MD expect 3, has currently 3 replicas, +1 replica it is using maxSurge
},
upToDateResults: map[string]mdutil.UpToDateResult{
"ms1": {EligibleForInPlaceUpdate: true},
Expand All @@ -1085,7 +1143,7 @@ func TestReconcileInPlaceUpdateIntent(t *testing.T) {
},
expectMoveFromMS: []string{"ms1"},
expectScaleIntents: map[string]int32{
// "ms2": 6, +3 replicas from maxSurge dropped, newMS is already scaling up
// "ms2": 1, +1 replica using maxSurge dropped, there is a machine updating in place
},
},
}
Expand Down