Skip to content
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 rolloutAfter for RollingUpdate strategy #4596

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 8 additions & 0 deletions api/v1alpha3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ func (src *MachineDeployment) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Strategy.RollingUpdate.DeletePolicy = restored.Spec.Strategy.RollingUpdate.DeletePolicy
}

if restored.Spec.RolloutAfter != nil {
dst.Spec.RolloutAfter = restored.Spec.RolloutAfter
}

dst.Status.Conditions = restored.Status.Conditions
return nil
}
Expand Down Expand Up @@ -312,3 +316,7 @@ func Convert_v1alpha3_MachineStatus_To_v1beta1_MachineStatus(in *MachineStatus,
// Status.version has been removed in v1beta1, thus requiring custom conversion function. the information will be dropped.
return autoConvert_v1alpha3_MachineStatus_To_v1beta1_MachineStatus(in, out, s)
}

func Convert_v1beta1_MachineDeploymentSpec_To_v1alpha3_MachineDeploymentSpec(in *v1beta1.MachineDeploymentSpec, out *MachineDeploymentSpec, s apiconversion.Scope) error {
return autoConvert_v1beta1_MachineDeploymentSpec_To_v1alpha3_MachineDeploymentSpec(in, out, s)
}
16 changes: 6 additions & 10 deletions api/v1alpha3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 31 additions & 2 deletions api/v1alpha4/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1alpha4
import (
apiconversion "k8s.io/apimachinery/pkg/conversion"
"sigs.k8s.io/cluster-api/api/v1beta1"
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
"sigs.k8s.io/controller-runtime/pkg/conversion"
)

Expand Down Expand Up @@ -121,15 +122,39 @@ func (dst *MachineSetList) ConvertFrom(srcRaw conversion.Hub) error {
func (src *MachineDeployment) ConvertTo(dstRaw conversion.Hub) error {
dst := dstRaw.(*v1beta1.MachineDeployment)

return Convert_v1alpha4_MachineDeployment_To_v1beta1_MachineDeployment(src, dst, nil)
if err := Convert_v1alpha4_MachineDeployment_To_v1beta1_MachineDeployment(src, dst, nil); err != nil {
return err
}

// Manually restore data.
restored := &v1beta1.MachineDeployment{}
if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
return err
}

if restored.Spec.RolloutAfter != nil {
dst.Spec.RolloutAfter = restored.Spec.RolloutAfter
}

return nil
}

func (dst *MachineDeployment) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*v1beta1.MachineDeployment)

return Convert_v1beta1_MachineDeployment_To_v1alpha4_MachineDeployment(src, dst, nil)
if err := Convert_v1beta1_MachineDeployment_To_v1alpha4_MachineDeployment(src, dst, nil); err != nil {
return err
}

// Preserve Hub data on down-conversion except for metadata
if err := utilconversion.MarshalData(src, dst); err != nil {
return err
}

return nil
}


func (src *MachineDeploymentList) ConvertTo(dstRaw conversion.Hub) error {
dst := dstRaw.(*v1beta1.MachineDeploymentList)

Expand Down Expand Up @@ -170,3 +195,7 @@ func Convert_v1alpha4_MachineStatus_To_v1beta1_MachineStatus(in *MachineStatus,
// Status.version has been removed in v1beta1, thus requiring custom conversion function. the information will be dropped.
return autoConvert_v1alpha4_MachineStatus_To_v1beta1_MachineStatus(in, out, s)
}

func Convert_v1beta1_MachineDeploymentSpec_To_v1alpha4_MachineDeploymentSpec(in *v1beta1.MachineDeploymentSpec, out *MachineDeploymentSpec, s apiconversion.Scope) error {
return autoConvert_v1beta1_MachineDeploymentSpec_To_v1alpha4_MachineDeploymentSpec(in, out, s)
}
40 changes: 28 additions & 12 deletions api/v1alpha4/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions api/v1beta1/machinedeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ const (
// MachineDeploymentUniqueLabel is the label applied to Machines
// in a MachineDeployment containing the hash of the template.
MachineDeploymentUniqueLabel = "machine-template-hash"

// LastRolloutAfterAnnotation is the last MachineDeployment.Spec.RolloutAfter that was met.
LastRolloutAfterAnnotation = "machinedeployment.clusters.x-k8s.io/lastRollout"
Comment on lines +61 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the annotation applied and stored only on MachineSets in this case?

)

// ANCHOR: MachineDeploymentSpec
Expand All @@ -67,6 +70,13 @@ type MachineDeploymentSpec struct {
// +kubebuilder:validation:MinLength=1
ClusterName string `json:"clusterName"`

// RolloutAfter performs a rollout of the MachineDeployment
// the first reconciliation loop where the given time is before now().
// After that moment the field has no effect until it's updated with a new time.
// Any changes to other fields of the spec are not affected by this field and will be rolled out as normal.
// +optional
RolloutAfter *metav1.Time `json:"rolloutAfter,omitempty"`

// Number of desired machines. Defaults to 1.
// This is a pointer to distinguish between explicit zero and not specified.
// +optional
Expand Down
4 changes: 4 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,14 @@ spec:
Defaults to 1.
format: int32
type: integer
rolloutAfter:
description: RolloutAfter performs a rollout of the MachineDeployment
the first reconciliation loop where the given time is before now().
After that moment the field has no effect until it's updated with
a new time. Any changes to other fields of the spec are not affected
by this field and will be rolled out as normal.
format: date-time
type: string
selector:
description: Label selector for machines. Existing MachineSets whose
machines are selected by this will be the ones affected by this
Expand Down
69 changes: 68 additions & 1 deletion controllers/internal/mdutil/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"sort"
"strconv"
"strings"
"time"

"github.com/davecgh/go-spew/spew"
"github.com/go-logr/logr"
Expand Down Expand Up @@ -392,6 +393,7 @@ func EqualMachineTemplate(template1, template2 *clusterv1.MachineTemplateSpec) b
}

// FindNewMachineSet returns the new MS this given deployment targets (the one with the same machine template).
// Deprecated: this does not for rolloutAfter. Use FindNewMachineSetWithRolloutAfter.
func FindNewMachineSet(deployment *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) *clusterv1.MachineSet {
Comment on lines +396 to 397
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably change these functions signature now instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't that be a breaking change? don't we need to target this to v1beta2 now since this is an API change?

Copy link
Member

@sbueringer sbueringer Oct 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure, but I think the idea is that we can have code breaking changes between v1.0 and v1.1, i.e. only the API types and their behavior must not have breaking changes.

Or from another point of view: v1beta1 guards our API types and corresponding implementation, the release version the code (+ we're doing breaking changes there on minor versions as we're not incrementing major, similar to Kubernetes)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense, though I'm not sure a new field can be added with out creating a subsequent API version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea was to allow new fields if they are non-breaking, but let's see if somebody else knows :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internal packages are not exposed APIs, so we can break even within patch releases given that only internal code might use them

Copy link
Member

@sbueringer sbueringer Oct 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but I was assuming we started talking at some point about the addition of the RolloutAfter field to the v1beta1 API type.

sort.Sort(MachineSetsByCreationTimestamp(msList))
for i := range msList {
Expand All @@ -403,14 +405,57 @@ func FindNewMachineSet(deployment *clusterv1.MachineDeployment, msList []*cluste
return msList[i]
}
}
// new MachineSet does not exist.
// New MachineSet does not exist.
return nil
}

// FindNewMachineSetWithRolloutAfter is as FindNewMachineSet but it also accounts for RolloutAfter.
func FindNewMachineSetWithRolloutAfter(now *metav1.Time, deployment *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) (*clusterv1.MachineSet, error) {
// In rare cases, such as after cluster upgrades, Deployment may end up with
// having more than one new MachineSets that have the same template,
// see https://github.com/kubernetes/kubernetes/issues/40415
// We deterministically choose the oldest new MachineSet with matching template hash.
sort.Sort(MachineSetsByCreationTimestamp(msList))

if deployment.Spec.RolloutAfter != nil {
if now.After(deployment.Spec.RolloutAfter.Time) {
// TODO (alberto): inlining the format statement within the annotation assigment here
// does not set the value. Why?
lastRolloutAfter := deployment.Spec.RolloutAfter.Time.UTC().Format(time.RFC3339)
deployment.Annotations[clusterv1.LastRolloutAfterAnnotation] = lastRolloutAfter
}
}

for i := range msList {
if EqualMachineTemplate(&msList[i].Spec.Template, &deployment.Spec.Template) {
// If a rolloutAfter should happen or has already happened
// we pick the oldest one with creationTimestamp after rolloutAfter.
if lastRolloutAfter, ok := deployment.Annotations[clusterv1.LastRolloutAfterAnnotation]; ok {
created := msList[i].CreationTimestamp
t, err := time.Parse(time.RFC3339, lastRolloutAfter)
if err != nil {
return nil, err
}
if created.After(t) {
return msList[i], nil
}
continue
}

// If a rolloutAfter never took place just pick the oldest one.
return msList[i], nil
}
}

// New MachineSet does not exist.
return nil, nil
}

// FindOldMachineSets returns the old machine sets targeted by the given Deployment, with the given slice of MSes.
// Returns two list of machine sets
// - the first contains all old machine sets with all non-zero replicas
// - the second contains all old machine sets
// Deprecated: this does not for rolloutAfter. Use FindOldMachineSetsWithRolloutAfter.
func FindOldMachineSets(deployment *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) ([]*clusterv1.MachineSet, []*clusterv1.MachineSet) {
var requiredMSs []*clusterv1.MachineSet
allMSs := make([]*clusterv1.MachineSet, 0, len(msList))
Expand All @@ -428,6 +473,28 @@ func FindOldMachineSets(deployment *clusterv1.MachineDeployment, msList []*clust
return requiredMSs, allMSs
}

// FindOldMachineSetsWithRolloutAfter is as FindOldMachineSets but also accounts for rolloutAfter.
func FindOldMachineSetsWithRolloutAfter(now *metav1.Time, deployment *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) ([]*clusterv1.MachineSet, []*clusterv1.MachineSet, error) {
var requiredMSs []*clusterv1.MachineSet
allMSs := make([]*clusterv1.MachineSet, 0, len(msList))
newMS, err := FindNewMachineSetWithRolloutAfter(now, deployment, msList)
if err != nil {
return nil, nil, err
}

for _, ms := range msList {
// Filter out new machine set
if newMS != nil && ms.UID == newMS.UID {
continue
}
allMSs = append(allMSs, ms)
if *(ms.Spec.Replicas) != 0 {
requiredMSs = append(requiredMSs, ms)
}
}
return requiredMSs, allMSs, nil
}

// GetReplicaCountForMachineSets returns the sum of Replicas of the given machine sets.
func GetReplicaCountForMachineSets(machineSets []*clusterv1.MachineSet) int32 {
totalReplicas := int32(0)
Expand Down
Loading