-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Add support for in-place upgrades in MachineDeployments #29
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package machinedeployment | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/pkg/errors" | ||
kerrors "k8s.io/apimachinery/pkg/util/errors" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
|
||
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" | ||
"sigs.k8s.io/cluster-api/util/annotations" | ||
) | ||
|
||
func (r *Reconciler) rolloutInPlace(ctx context.Context, md *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) (reterr error) { | ||
log := ctrl.LoggerFrom(ctx) | ||
|
||
// For in-place upgrade, we shouldn't try to create a new MachineSet as that would trigger a rollout. | ||
// Instead, we should try to get latest MachineSet that matches the MachineDeployment.Spec.Template/ | ||
// If no such MachineSet exists yet, this means the MachineSet hasn't been in-place upgraded yet. | ||
// The external in-place upgrade implementer is responsible for updating the latest MachineSet's template | ||
// after in-place upgrade of all worker nodes belonging to the MD is complete. | ||
// Once the MachineSet is updated, this function will return the latest MachineSet that matches the | ||
// MachineDeployment template and thus we can deduce that the in-place upgrade is complete. | ||
newMachineSet, oldMachineSets, err := r.getAllMachineSetsAndSyncRevision(ctx, md, msList, false) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer func() { | ||
allMSs := append(oldMachineSets, newMachineSet) | ||
|
||
// Always attempt to sync the status | ||
err := r.syncDeploymentStatus(allMSs, newMachineSet, md) | ||
reterr = kerrors.NewAggregate([]error{reterr, err}) | ||
}() | ||
|
||
if newMachineSet == nil { | ||
log.Info("Changes detected, InPlace upgrade strategy detected, adding the annotation") | ||
annotations.AddAnnotations(md, map[string]string{clusterv1.MachineDeploymentInPlaceUpgradeAnnotation: "true"}) | ||
return errors.New("new MachineSet not found. This most likely means that the in-place upgrade hasn't finished yet") | ||
} | ||
|
||
return r.sync(ctx, md, msList) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package machinedeployment | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/gomega" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/tools/record" | ||
"k8s.io/utils/pointer" | ||
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
) | ||
|
||
const ( | ||
mdName = "my-md" | ||
msName = "my-ms" | ||
version128 = "v1.28.0" | ||
version129 = "v1.29.0" | ||
) | ||
|
||
func getMachineDeployment(name string, version string, replicas int32) *clusterv1.MachineDeployment { | ||
return &clusterv1.MachineDeployment{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Spec: clusterv1.MachineDeploymentSpec{ | ||
Strategy: &clusterv1.MachineDeploymentStrategy{ | ||
Type: clusterv1.InPlaceMachineDeploymentStrategyType, | ||
}, | ||
Replicas: pointer.Int32(replicas), | ||
Template: clusterv1.MachineTemplateSpec{ | ||
Spec: clusterv1.MachineSpec{ | ||
ClusterName: "my-cluster", | ||
Version: pointer.String(version), | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func getMachineSet(name string, version string, replicas int32) *clusterv1.MachineSet { | ||
return &clusterv1.MachineSet{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Spec: clusterv1.MachineSetSpec{ | ||
Replicas: pointer.Int32(replicas), | ||
Template: clusterv1.MachineTemplateSpec{ | ||
Spec: clusterv1.MachineSpec{ | ||
ClusterName: "my-cluster", | ||
Version: pointer.String(version), | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func TestRolloutInPlace(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
machineDeployment *clusterv1.MachineDeployment | ||
msList []*clusterv1.MachineSet | ||
annotationExpected bool | ||
expectErr bool | ||
}{ | ||
{ | ||
name: "MD template matches MS template", | ||
machineDeployment: getMachineDeployment(mdName, version128, 2), | ||
msList: []*clusterv1.MachineSet{getMachineSet(msName, version128, 2)}, | ||
annotationExpected: false, | ||
expectErr: false, | ||
}, | ||
{ | ||
name: "MD template doesn't MS template", | ||
machineDeployment: getMachineDeployment(mdName, version128, 2), | ||
msList: []*clusterv1.MachineSet{getMachineSet(msName, version129, 2)}, | ||
annotationExpected: true, | ||
expectErr: true, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
g := NewWithT(t) | ||
|
||
resources := []client.Object{ | ||
tc.machineDeployment, | ||
} | ||
|
||
for key := range tc.msList { | ||
resources = append(resources, tc.msList[key]) | ||
} | ||
|
||
r := &Reconciler{ | ||
Client: fake.NewClientBuilder().WithObjects(resources...).Build(), | ||
recorder: record.NewFakeRecorder(32), | ||
} | ||
|
||
err := r.rolloutInPlace(ctx, tc.machineDeployment, tc.msList) | ||
if tc.expectErr { | ||
g.Expect(err).To(HaveOccurred()) | ||
} | ||
|
||
_, ok := tc.machineDeployment.Annotations[clusterv1.MachineDeploymentInPlaceUpgradeAnnotation] | ||
g.Expect(ok).To(Equal(tc.annotationExpected)) | ||
}) | ||
} | ||
|
||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to add any special handling to re-queue in case the in-place scaling operation hasn't completed yet? Or is that already handled elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r.sync
handles the logic for that. It tries to scale up the machine set if needed, if it can't then it errors out.Once the machineSet gets scaled, the MS and machines controller picks it up and handles the reconcile