Repository: keikoproj/upgrade-manager
Pull request #82 (2020-03-30 02:04:15)

add support for conditions
conditions enable the usage of the `kubectl wait` command, allowing for
awaiting the state of a rolling upgrade.

this change additionally adds a Complete condition, which is set on
completion of execution, regardless of success or failure.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/keikoproj/upgrade-manager?pullRequest=82) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/keikoproj/upgrade-manager?pullRequest=82) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/keikoproj/upgrade-manager?pullRequest=82) it.</sub>
shrinandj: Awesome! This looks really interesting. Can you list some use case where `condition` would be used?
almonteb: @shrinandj, 

enabling automation to await the completion state (via `kubectl wait`) provides a standard interface for signaling job completion.

since the manager also has a maximum number of concurrent reconciles, there may be value in adding a started condition as well.

Patch:
diff --git a/README.md b/README.md
index b3a746a..0a63904 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ RollingUpgrade provides a Kubernetes native mechanism for doing rolling-updates
   - `startTime`: The RFC3339 timestamp when the rolling-update began. E.g. 2019-01-15T23:51:10Z
   - `endTime`: The RFC3339 timestamp when the rolling-update completed. E.g. 2019-01-15T00:35:10Z
   - `nodesProcessed`: The number of ec2 instances that were processed.
+  - `conditions`: Conditions describing the lifecycle of the rolling-update.
 
 ## Design
 
diff --git a/api/v1alpha1/rollingupgrade_types.go b/api/v1alpha1/rollingupgrade_types.go
index d3ab349..b9c8c96 100644
--- a/api/v1alpha1/rollingupgrade_types.go
+++ b/api/v1alpha1/rollingupgrade_types.go
@@ -16,6 +16,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
 )
@@ -56,6 +57,14 @@ type RollingUpgradeStatus struct {
 	TotalProcessingTime string `json:"totalProcessingTime,omitempty"`
 	NodesProcessed      int    `json:"nodesProcessed,omitempty"`
 	TotalNodes          int    `json:"totalNodes,omitempty"`
+
+	Conditions []RollingUpgradeCondition `json:"conditions,omitempty"`
+}
+
+// RollingUpgradeCondition describes the state of the RollingUpgrade
+type RollingUpgradeCondition struct {
+	Type   UpgradeConditionType   `json:"type,omitempty"`
+	Status corev1.ConditionStatus `json:"status,omitempty"`
 }
 
 // +kubebuilder:object:root=true
@@ -93,6 +102,8 @@ type UpdateStrategyType string
 
 type UpdateStrategyMode string
 
+type UpgradeConditionType string
+
 const (
 	// RandomUpdate strategy treats all the Azs as a single unit and picks random nodes for update
 	RandomUpdateStrategy UpdateStrategyType = "randomUpdate"
@@ -104,6 +115,8 @@ const (
 	UpdateStrategyModeEager UpdateStrategyMode = "eager"
 	// Other update strategies such as rolling update by Az or rolling update with a predifined instance list
 	// can be implemented in future by adding more update strategy types
+
+	UpgradeComplete UpgradeConditionType = "Complete"
 )
 
 func (c UpdateStrategyMode) String() string {
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index fb0cc32..72ab391 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -74,7 +74,7 @@ func (in *RollingUpgrade) DeepCopyInto(out *RollingUpgrade) {
 	out.TypeMeta = in.TypeMeta
 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
 	out.Spec = in.Spec
-	out.Status = in.Status
+	in.Status.DeepCopyInto(&out.Status)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollingUpgrade.
@@ -95,6 +95,21 @@ func (in *RollingUpgrade) DeepCopyObject() runtime.Object {
 	return nil
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RollingUpgradeCondition) DeepCopyInto(out *RollingUpgradeCondition) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollingUpgradeCondition.
+func (in *RollingUpgradeCondition) DeepCopy() *RollingUpgradeCondition {
+	if in == nil {
+		return nil
+	}
+	out := new(RollingUpgradeCondition)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RollingUpgradeList) DeepCopyInto(out *RollingUpgradeList) {
 	*out = *in
@@ -149,6 +164,11 @@ func (in *RollingUpgradeSpec) DeepCopy() *RollingUpgradeSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RollingUpgradeStatus) DeepCopyInto(out *RollingUpgradeStatus) {
 	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]RollingUpgradeCondition, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollingUpgradeStatus.
diff --git a/config/crd/bases/upgrademgr.keikoproj.io_rollingupgrades.yaml b/config/crd/bases/upgrademgr.keikoproj.io_rollingupgrades.yaml
index fe96623..3b6c5ab 100644
--- a/config/crd/bases/upgrademgr.keikoproj.io_rollingupgrades.yaml
+++ b/config/crd/bases/upgrademgr.keikoproj.io_rollingupgrades.yaml
@@ -113,6 +113,16 @@ spec:
         status:
           description: RollingUpgradeStatus defines the observed state of RollingUpgrade
           properties:
+            conditions:
+              items:
+                description: RollingUpgradeCondition describes the state of the RollingUpgrade
+                properties:
+                  status:
+                    type: string
+                  type:
+                    type: string
+                type: object
+              type: array
             currentStatus:
               type: string
             endTime:
diff --git a/controllers/rollingupgrade_controller.go b/controllers/rollingupgrade_controller.go
index 5f9a159..5b09cce 100644
--- a/controllers/rollingupgrade_controller.go
+++ b/controllers/rollingupgrade_controller.go
@@ -597,6 +597,12 @@ func (r *RollingUpgradeReconciler) finishExecution(finalStatus string, nodesProc
 	ruObj.Status.CurrentStatus = finalStatus
 	ruObj.Status.NodesProcessed = nodesProcessed
 
+	ruObj.Status.Conditions = append(ruObj.Status.Conditions,
+		upgrademgrv1alpha1.RollingUpgradeCondition{
+			Type:   upgrademgrv1alpha1.UpgradeComplete,
+			Status: corev1.ConditionTrue,
+		})
+
 	startTime, err := time.Parse(time.RFC3339, ruObj.Status.StartTime)
 	if err != nil {
 		r.info(ruObj, "Failed to calculate totalProcessingTime")


Test patch:
diff --git a/controllers/rollingupgrade_controller_test.go b/controllers/rollingupgrade_controller_test.go
index b887503..44e2e68 100644
--- a/controllers/rollingupgrade_controller_test.go
+++ b/controllers/rollingupgrade_controller_test.go
@@ -820,6 +820,14 @@ func TestFinishExecutionCompleted(t *testing.T) {
 	g.Expect(ruObj.Status.NodesProcessed).To(gomega.Equal(mockNodesProcessed))
 	g.Expect(ruObj.Status.EndTime).To(gomega.Not(gomega.BeNil()))
 	g.Expect(ruObj.Status.TotalProcessingTime).To(gomega.Not(gomega.BeNil()))
+	g.Expect(ruObj.Status.Conditions).To(gomega.Equal(
+		[]upgrademgrv1alpha1.RollingUpgradeCondition{
+			upgrademgrv1alpha1.RollingUpgradeCondition{
+				Type:   upgrademgrv1alpha1.UpgradeComplete,
+				Status: corev1.ConditionTrue,
+			},
+		},
+	))
 }
 
 func TestFinishExecutionError(t *testing.T) {
@@ -851,6 +859,14 @@ func TestFinishExecutionError(t *testing.T) {
 	g.Expect(ruObj.Status.NodesProcessed).To(gomega.Equal(mockNodesProcessed))
 	g.Expect(ruObj.Status.EndTime).To(gomega.Not(gomega.BeNil()))
 	g.Expect(ruObj.Status.TotalProcessingTime).To(gomega.Not(gomega.BeNil()))
+	g.Expect(ruObj.Status.Conditions).To(gomega.Equal(
+		[]upgrademgrv1alpha1.RollingUpgradeCondition{
+			upgrademgrv1alpha1.RollingUpgradeCondition{
+				Type:   upgrademgrv1alpha1.UpgradeComplete,
+				Status: corev1.ConditionTrue,
+			},
+		},
+	))
 }
 
 // RunRestack() goes through the entire process without errors