From cae4137f1a7f94f91916f2affc4a2e3f7c02ce31 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Fri, 4 Jan 2019 18:18:34 -0500 Subject: [PATCH] Update ClusterVersion with a desired field and a history field The current field is not useful because it doesn't represent level driven flows well. Instead, track the version the cluster is working towards as `status.desired` and each time that target changes update the `status.history` field. The `status.history` field allows us to know what previous updates were made and what times those completed. Even though rollback is not desirable, we need to report a field that indicates what the last successfully applied version was (user starts at 4.0.0, then upgrades to 4.0.1, which fails, then a new 4.0.2 version is released that also fails, we need to report 4.0.0 as the version to go back to). --- config/v1/types_cluster_version.go | 58 +++++++++++++++++++++++++++--- config/v1/zz_generated.deepcopy.go | 34 +++++++++++++++++- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/config/v1/types_cluster_version.go b/config/v1/types_cluster_version.go index e012443ea26..a43b12a69a6 100644 --- a/config/v1/types_cluster_version.go +++ b/config/v1/types_cluster_version.go @@ -73,11 +73,19 @@ type ClusterVersionSpec struct { // progress, or is failing. // +k8s:deepcopy-gen=true type ClusterVersionStatus struct { - // current is the version that the cluster will be reconciled to. This - // value may be empty during cluster startup, and then will be set whenever - // a new update is being applied. Use the conditions array to know whether - // the update is complete. - Current Update `json:"current"` + // desired is the version that the cluster is reconciling towards. + // If the cluster is not yet fully initialized desired will be set + // with the information available, which may be a payload or a tag. + Desired Update `json:"desired"` + + // history contains a list of the most recent versions applied to the cluster. + // This value may be empty during cluster startup, and then will be updated + // when a new update is being applied. The newest update is first in the + // list and it is ordered by recency. Updates in the history have state + // Completed if the rollout completed - if an update was failing or halfway + // applied the state will be Partial. Only a limited amount of update history + // is preserved. + History []UpdateHistory `json:"history"` // generation reports which version of the spec is being processed. // If this value is not equal to metadata.generation, then the @@ -106,6 +114,46 @@ type ClusterVersionStatus struct { AvailableUpdates []Update `json:"availableUpdates"` } +// UpdateState is a constant representing whether an update was successfully +// applied to the cluster or not. +type UpdateState string + +const ( + // CompletedUpdate indicates an update was successfully applied + // to the cluster (all resource updates were successful). + CompletedUpdate UpdateState = "Completed" + // PartialUpdate indicates an update was never completely applied + // or is currently being applied. + PartialUpdate UpdateState = "Partial" +) + +// UpdateHistory is a single attempted update to the cluster. +type UpdateHistory struct { + // state reflects whether the update was fully applied. The Partial state + // indicates the update is not fully applied, while the Completed state + // indicates the update was successfully rolled out at least once (all + // parts of the update successfully applied). + State UpdateState `json:"state"` + + // startedTime is the time at which the update was started. + StartedTime metav1.Time `json:"startedTime"` + // completionTime, if set, is when the update was fully applied. The update + // that is currently being applied will have a null completion time. + // Completion time will always be set for entries that are not the current + // update (usually to the started time of the next update). + CompletionTime *metav1.Time `json:"completionTime"` + + // version is a semantic versioning identifying the update version. If the + // requested payload does not define a version, or if a failure occurs + // retrieving the payload, this value may be empty. + // + // +optional + Version string `json:"version"` + // payload is a container image location that contains the update. This value + // is always populated. + Payload string `json:"payload"` +} + // ClusterID is string RFC4122 uuid. type ClusterID string diff --git a/config/v1/zz_generated.deepcopy.go b/config/v1/zz_generated.deepcopy.go index c5d9add8cbb..f2768e4b886 100644 --- a/config/v1/zz_generated.deepcopy.go +++ b/config/v1/zz_generated.deepcopy.go @@ -575,7 +575,14 @@ func (in *ClusterVersionSpec) DeepCopy() *ClusterVersionSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterVersionStatus) DeepCopyInto(out *ClusterVersionStatus) { *out = *in - out.Current = in.Current + out.Desired = in.Desired + if in.History != nil { + in, out := &in.History, &out.History + *out = make([]UpdateHistory, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]ClusterOperatorStatusCondition, len(*in)) @@ -2435,6 +2442,31 @@ func (in *Update) DeepCopy() *Update { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpdateHistory) DeepCopyInto(out *UpdateHistory) { + *out = *in + in.StartedTime.DeepCopyInto(&out.StartedTime) + if in.CompletionTime != nil { + in, out := &in.CompletionTime, &out.CompletionTime + if *in == nil { + *out = nil + } else { + *out = (*in).DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateHistory. +func (in *UpdateHistory) DeepCopy() *UpdateHistory { + if in == nil { + return nil + } + out := new(UpdateHistory) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WebhookTokenAuthenticator) DeepCopyInto(out *WebhookTokenAuthenticator) { *out = *in