Skip to content

Commit a0bb17c

Browse files
🌱 Add rollout planner (#12804)
* Add rollout planner * Minor improvements * Address feedback * More feedback * More feedback
1 parent 1c45137 commit a0bb17c

14 files changed

+1733
-192
lines changed

internal/controllers/machinedeployment/machinedeployment_rolling.go

Lines changed: 132 additions & 75 deletions
Large diffs are not rendered by default.

internal/controllers/machinedeployment/machinedeployment_rolling_test.go

Lines changed: 32 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,13 @@ limitations under the License.
1717
package machinedeployment
1818

1919
import (
20-
"strconv"
2120
"testing"
2221

2322
. "github.com/onsi/gomega"
24-
"github.com/pkg/errors"
2523
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26-
"k8s.io/client-go/tools/record"
2724
"k8s.io/utils/ptr"
28-
"sigs.k8s.io/controller-runtime/pkg/client"
29-
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3025

3126
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
32-
"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
3327
)
3428

3529
func TestReconcileNewMachineSet(t *testing.T) {
@@ -41,36 +35,6 @@ func TestReconcileNewMachineSet(t *testing.T) {
4135
expectedNewMachineSetReplicas int
4236
error error
4337
}{
44-
{
45-
name: "It fails when machineDeployment has no replicas",
46-
machineDeployment: &clusterv1.MachineDeployment{
47-
ObjectMeta: metav1.ObjectMeta{
48-
Namespace: "foo",
49-
Name: "bar",
50-
},
51-
},
52-
newMachineSet: &clusterv1.MachineSet{
53-
Spec: clusterv1.MachineSetSpec{
54-
Replicas: ptr.To[int32](2),
55-
},
56-
},
57-
error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"),
58-
},
59-
{
60-
name: "It fails when new machineSet has no replicas",
61-
machineDeployment: &clusterv1.MachineDeployment{
62-
Spec: clusterv1.MachineDeploymentSpec{
63-
Replicas: ptr.To[int32](2),
64-
},
65-
},
66-
newMachineSet: &clusterv1.MachineSet{
67-
ObjectMeta: metav1.ObjectMeta{
68-
Namespace: "foo",
69-
Name: "bar",
70-
},
71-
},
72-
error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"),
73-
},
7438
{
7539
name: "RollingUpdate strategy: Scale up: 0 -> 2",
7640
machineDeployment: &clusterv1.MachineDeployment{
@@ -268,21 +232,12 @@ func TestReconcileNewMachineSet(t *testing.T) {
268232
t.Run(tc.name, func(t *testing.T) {
269233
g := NewWithT(t)
270234

271-
resources := []client.Object{
272-
tc.machineDeployment,
273-
}
274-
275-
allMachineSets := append(tc.oldMachineSets, tc.newMachineSet)
276-
for key := range allMachineSets {
277-
resources = append(resources, allMachineSets[key])
278-
}
279-
280-
r := &Reconciler{
281-
Client: fake.NewClientBuilder().WithObjects(resources...).Build(),
282-
recorder: record.NewFakeRecorder(32),
283-
}
235+
planner := newRolloutPlanner()
236+
planner.md = tc.machineDeployment
237+
planner.newMS = tc.newMachineSet
238+
planner.oldMSs = tc.oldMachineSets
284239

285-
err := r.reconcileNewMachineSet(ctx, allMachineSets, tc.newMachineSet, tc.machineDeployment)
240+
err := planner.reconcileNewMachineSet(ctx)
286241
if tc.error != nil {
287242
g.Expect(err).To(HaveOccurred())
288243
g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error()))
@@ -291,22 +246,23 @@ func TestReconcileNewMachineSet(t *testing.T) {
291246

292247
g.Expect(err).ToNot(HaveOccurred())
293248

294-
freshNewMachineSet := &clusterv1.MachineSet{}
295-
err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.newMachineSet), freshNewMachineSet)
296-
g.Expect(err).ToNot(HaveOccurred())
297-
298-
g.Expect(*freshNewMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.expectedNewMachineSetReplicas))
299-
300-
_, ok := freshNewMachineSet.GetAnnotations()[clusterv1.DisableMachineCreateAnnotation]
301-
g.Expect(ok).To(BeFalse())
302-
303-
desiredReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.DesiredReplicasAnnotation]
304-
g.Expect(ok).To(BeTrue())
305-
g.Expect(strconv.Atoi(desiredReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas))
249+
scaleIntent := ptr.Deref(tc.newMachineSet.Spec.Replicas, 0)
250+
if v, ok := planner.scaleIntents[tc.newMachineSet.Name]; ok {
251+
scaleIntent = v
252+
}
253+
g.Expect(scaleIntent).To(BeEquivalentTo(tc.expectedNewMachineSetReplicas))
306254

307-
maxReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.MaxReplicasAnnotation]
308-
g.Expect(ok).To(BeTrue())
309-
g.Expect(strconv.Atoi(maxReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas + mdutil.MaxSurge(*tc.machineDeployment)))
255+
// TODO(in-place): Restore tests on DisableMachineCreateAnnotation and MaxReplicasAnnotation as soon as handling those annotation is moved into the rollout planner
256+
// _, ok := freshNewMachineSet.GetAnnotations()[clusterv1.DisableMachineCreateAnnotation]
257+
// g.Expect(ok).To(BeFalse())
258+
//
259+
// desiredReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.DesiredReplicasAnnotation]
260+
// g.Expect(ok).To(BeTrue())
261+
// g.Expect(strconv.Atoi(desiredReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas))
262+
//
263+
// maxReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.MaxReplicasAnnotation]
264+
// g.Expect(ok).To(BeTrue())
265+
// g.Expect(strconv.Atoi(maxReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas + mdutil.MaxSurge(*tc.machineDeployment)))
310266
})
311267
}
312268
}
@@ -320,36 +276,6 @@ func TestReconcileOldMachineSets(t *testing.T) {
320276
expectedOldMachineSetsReplicas int
321277
error error
322278
}{
323-
{
324-
name: "It fails when machineDeployment has no replicas",
325-
machineDeployment: &clusterv1.MachineDeployment{
326-
ObjectMeta: metav1.ObjectMeta{
327-
Namespace: "foo",
328-
Name: "bar",
329-
},
330-
},
331-
newMachineSet: &clusterv1.MachineSet{
332-
Spec: clusterv1.MachineSetSpec{
333-
Replicas: ptr.To[int32](2),
334-
},
335-
},
336-
error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"),
337-
},
338-
{
339-
name: "It fails when new machineSet has no replicas",
340-
machineDeployment: &clusterv1.MachineDeployment{
341-
Spec: clusterv1.MachineDeploymentSpec{
342-
Replicas: ptr.To[int32](2),
343-
},
344-
},
345-
newMachineSet: &clusterv1.MachineSet{
346-
ObjectMeta: metav1.ObjectMeta{
347-
Namespace: "foo",
348-
Name: "bar",
349-
},
350-
},
351-
error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"),
352-
},
353279
{
354280
name: "RollingUpdate strategy: Scale down old MachineSets when all new replicas are available",
355281
machineDeployment: &clusterv1.MachineDeployment{
@@ -465,33 +391,25 @@ func TestReconcileOldMachineSets(t *testing.T) {
465391
t.Run(tc.name, func(t *testing.T) {
466392
g := NewWithT(t)
467393

468-
resources := []client.Object{
469-
tc.machineDeployment,
470-
}
471-
472-
allMachineSets := append(tc.oldMachineSets, tc.newMachineSet)
473-
for key := range allMachineSets {
474-
resources = append(resources, allMachineSets[key])
475-
}
476-
477-
r := &Reconciler{
478-
Client: fake.NewClientBuilder().WithObjects(resources...).Build(),
479-
recorder: record.NewFakeRecorder(32),
480-
}
394+
planner := newRolloutPlanner()
395+
planner.md = tc.machineDeployment
396+
planner.newMS = tc.newMachineSet
397+
planner.oldMSs = tc.oldMachineSets
481398

482-
err := r.reconcileOldMachineSets(ctx, allMachineSets, tc.oldMachineSets, tc.newMachineSet, tc.machineDeployment)
399+
err := planner.reconcileOldMachineSets(ctx)
483400
if tc.error != nil {
484401
g.Expect(err).To(HaveOccurred())
485402
g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error()))
486403
return
487404
}
488405

489406
g.Expect(err).ToNot(HaveOccurred())
490-
for key := range tc.oldMachineSets {
491-
freshOldMachineSet := &clusterv1.MachineSet{}
492-
err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.oldMachineSets[key]), freshOldMachineSet)
493-
g.Expect(err).ToNot(HaveOccurred())
494-
g.Expect(*freshOldMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.expectedOldMachineSetsReplicas))
407+
for i := range tc.oldMachineSets {
408+
scaleIntent := ptr.Deref(tc.oldMachineSets[i].Spec.Replicas, 0)
409+
if v, ok := planner.scaleIntents[tc.oldMachineSets[i].Name]; ok {
410+
scaleIntent = v
411+
}
412+
g.Expect(scaleIntent).To(BeEquivalentTo(tc.expectedOldMachineSetsReplicas))
495413
}
496414
})
497415
}

internal/controllers/machinedeployment/machinedeployment_rollout_ondelete.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/pkg/errors"
2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2525
"k8s.io/klog/v2"
26+
"k8s.io/utils/ptr"
2627
ctrl "sigs.k8s.io/controller-runtime"
2728
"sigs.k8s.io/controller-runtime/pkg/client"
2829

@@ -48,7 +49,7 @@ func (r *Reconciler) rolloutOnDelete(ctx context.Context, md *clusterv1.MachineD
4849
allMSs := append(oldMSs, newMS)
4950

5051
// Scale up, if we can.
51-
if err := r.reconcileNewMachineSetOnDelete(ctx, allMSs, newMS, md); err != nil {
52+
if err := r.reconcileNewMachineSetOnDelete(ctx, md, oldMSs, newMS); err != nil {
5253
return err
5354
}
5455

@@ -164,10 +165,25 @@ func (r *Reconciler) reconcileOldMachineSetsOnDelete(ctx context.Context, oldMSs
164165
}
165166

166167
// reconcileNewMachineSetOnDelete handles reconciliation of the latest MachineSet associated with the MachineDeployment in the OnDelete rollout strategy.
167-
func (r *Reconciler) reconcileNewMachineSetOnDelete(ctx context.Context, allMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet, deployment *clusterv1.MachineDeployment) error {
168+
func (r *Reconciler) reconcileNewMachineSetOnDelete(ctx context.Context, md *clusterv1.MachineDeployment, oldMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet) error {
169+
// TODO(in-place): also apply/remove labels should go into rolloutPlanner
168170
if err := r.cleanupDisableMachineCreateAnnotation(ctx, newMS); err != nil {
169171
return err
170172
}
171173

172-
return r.reconcileNewMachineSet(ctx, allMSs, newMS, deployment)
174+
planner := newRolloutPlanner()
175+
planner.md = md
176+
planner.newMS = newMS
177+
planner.oldMSs = oldMSs
178+
179+
if err := planner.reconcileNewMachineSet(ctx); err != nil {
180+
return err
181+
}
182+
183+
// TODO(in-place): this should be changed as soon as rolloutPlanner support MS creation and adding/removing labels from MS
184+
scaleIntent := ptr.Deref(newMS.Spec.Replicas, 0)
185+
if v, ok := planner.scaleIntents[newMS.Name]; ok {
186+
scaleIntent = v
187+
}
188+
return r.scaleMachineSet(ctx, newMS, scaleIntent, md)
173189
}

0 commit comments

Comments
 (0)