diff --git a/controller/bluegreen.go b/controller/bluegreen.go index 14da3b3ebd..2735e583cf 100644 --- a/controller/bluegreen.go +++ b/controller/bluegreen.go @@ -129,8 +129,13 @@ func (c *Controller) reconcileBlueGreenPause(activeSvc *corev1.Service, rollout if _, ok := activeSvc.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]; !ok { return false } + pauseStartTime := rollout.Status.PauseStartTime + autoPromoteActiveServiceDelaySeconds := rollout.Spec.Strategy.BlueGreenStrategy.AutoPromoteActiveServiceDelaySeconds + if autoPromoteActiveServiceDelaySeconds != nil && pauseStartTime != nil { + c.checkEnqueueRolloutDuringWait(rollout, *pauseStartTime, *autoPromoteActiveServiceDelaySeconds) + } - return rollout.Spec.Paused && rollout.Status.PauseStartTime != nil + return rollout.Spec.Paused && pauseStartTime != nil } // scaleDownOldReplicaSetsForBlueGreen scales down old replica sets when rollout strategy is "Blue Green". diff --git a/controller/bluegreen_test.go b/controller/bluegreen_test.go index b05b44ba42..7573232dc8 100644 --- a/controller/bluegreen_test.go +++ b/controller/bluegreen_test.go @@ -200,6 +200,87 @@ func TestBlueGreenHandlePause(t *testing.T) { assert.Equal(t, calculatePatch(r2, OnlyObservedGenerationPatch), patch) }) + t.Run("NoAutoPromoteBeforeDelayTimePasses", func(t *testing.T) { + f := newFixture(t) + + r1 := newBlueGreenRollout("foo", 1, nil, "active", "preview") + r2 := bumpVersion(r1) + r2.Spec.Strategy.BlueGreenStrategy.AutoPromoteActiveServiceDelaySeconds = pointer.Int32Ptr(10) + + rs1 := newReplicaSetWithStatus(r1, "foo-895c6c4f9", 1, 1) + rs2 := newReplicaSetWithStatus(r2, "foo-5f79b78d7f", 1, 1) + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, 2, 1, 1, true, true) + pausedCondition, _ := newProgressingCondition(conditions.PausedRolloutReason, r2.Name) + conditions.SetRolloutCondition(&r2.Status, pausedCondition) + + previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} + previewSvc := newService("preview", 80, previewSelector) + activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} + activeSvc := newService("active", 80, activeSelector) + + f.objects = append(f.objects, r2) + f.kubeobjects = append(f.kubeobjects, previewSvc, activeSvc, rs1, rs2) + f.rolloutLister = append(f.rolloutLister, r2) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + + f.expectGetServiceAction(activeSvc) + f.expectGetServiceAction(previewSvc) + patchIndex := f.expectPatchRolloutActionWithPatch(r2, OnlyObservedGenerationPatch) + f.run(getKey(r2, t)) + patch := f.getPatchedRollout(patchIndex) + assert.Equal(t, calculatePatch(r2, OnlyObservedGenerationPatch), patch) + }) + + t.Run("AutoPromoteAfterDelayTimePasses", func(t *testing.T) { + f := newFixture(t) + + r1 := newBlueGreenRollout("foo", 1, nil, "active", "preview") + r2 := bumpVersion(r1) + r2.Spec.Strategy.BlueGreenStrategy.AutoPromoteActiveServiceDelaySeconds = pointer.Int32Ptr(10) + + rs1 := newReplicaSetWithStatus(r1, "foo-895c6c4f9", 1, 1) + rs2 := newReplicaSetWithStatus(r2, "foo-5f79b78d7f", 1, 1) + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, 2, 1, 1, true, true) + now := metav1.Now() + before := metav1.NewTime(now.Add(-1 * time.Minute)) + r2.Status.PauseStartTime = &before + pausedCondition, _ := newProgressingCondition(conditions.PausedRolloutReason, rs2.Name) + conditions.SetRolloutCondition(&r2.Status, pausedCondition) + + activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} + activeSvc := newService("active", 80, activeSelector) + previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} + previewSvc := newService("preview", 80, previewSelector) + + f.objects = append(f.objects, r2) + f.kubeobjects = append(f.kubeobjects, activeSvc, previewSvc, rs1, rs2) + f.rolloutLister = append(f.rolloutLister, r2) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + + f.expectGetServiceAction(activeSvc) + f.expectGetServiceAction(previewSvc) + expectedPatchWithoutSubs := `{ + "spec": { + "paused": null + }, + "status": { + "pauseStartTime": null + } + }` + expectedPatch := calculatePatch(r2, expectedPatchWithoutSubs) + patchRolloutIndex := f.expectPatchRolloutActionWithPatch(r2, expectedPatch) + f.run(getKey(r2, t)) + + rolloutPatch := f.getPatchedRollout(patchRolloutIndex) + assert.Equal(t, expectedPatch, rolloutPatch) + }) + t.Run("SkipWhenNoPreviewSpecified", func(t *testing.T) { f := newFixture(t) diff --git a/controller/pause.go b/controller/pause.go index 2d577f332d..6e8a5e2ac2 100644 --- a/controller/pause.go +++ b/controller/pause.go @@ -50,6 +50,7 @@ func calculatePauseStatus(rollout *v1alpha1.Rollout, addPause bool) (*metav1.Tim if !paused { pauseStartTime = nil } + if addPause { if pauseStartTime == nil { now := metav1.Now() @@ -58,5 +59,19 @@ func calculatePauseStatus(rollout *v1alpha1.Rollout, addPause bool) (*metav1.Tim paused = true } } + + if paused && rollout.Spec.Strategy.BlueGreenStrategy != nil { + if pauseStartTime != nil && rollout.Spec.Strategy.BlueGreenStrategy.AutoPromoteActiveServiceDelaySeconds != nil { + now := metav1.Now() + autoPromoteActiveServiceDelaySeconds := *rollout.Spec.Strategy.BlueGreenStrategy.AutoPromoteActiveServiceDelaySeconds + switchDeadline := pauseStartTime.Add(time.Duration(autoPromoteActiveServiceDelaySeconds) * time.Second) + if now.After(switchDeadline) { + return nil, false + } + return pauseStartTime, true + + } + + } return pauseStartTime, paused } diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index f4e755e3ce..53cea5008a 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -75,6 +75,12 @@ spec: description: Name of the service that the rollout modifies as the active service. type: string + autoPromoteActiveServiceDelaySeconds: + description: AutoPromoteActiveServiceDelaySeconds add a delay + before automatically promoting the ReplicaSet under the preview + service to the active service. + format: int32 + type: integer previewReplicaCount: description: PreviewReplica the number of replicas to run under the preview service before the switchover. Once the rollout diff --git a/manifests/install.yaml b/manifests/install.yaml index 2728ad8420..3f20ae4270 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -76,6 +76,12 @@ spec: description: Name of the service that the rollout modifies as the active service. type: string + autoPromoteActiveServiceDelaySeconds: + description: AutoPromoteActiveServiceDelaySeconds add a delay + before automatically promoting the ReplicaSet under the preview + service to the active service. + format: int32 + type: integer previewReplicaCount: description: PreviewReplica the number of replicas to run under the preview service before the switchover. Once the rollout diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 37466eb499..9f325212b2 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -76,6 +76,12 @@ spec: description: Name of the service that the rollout modifies as the active service. type: string + autoPromoteActiveServiceDelaySeconds: + description: AutoPromoteActiveServiceDelaySeconds add a delay + before automatically promoting the ReplicaSet under the preview + service to the active service. + format: int32 + type: integer previewReplicaCount: description: PreviewReplica the number of replicas to run under the preview service before the switchover. Once the rollout diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index ff3ec74f0a..ac9c28cfb8 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -112,6 +112,13 @@ func schema_pkg_apis_rollouts_v1alpha1_BlueGreenStrategy(ref common.ReferenceCal Format: "int32", }, }, + "autoPromoteActiveServiceDelaySeconds": { + SchemaProps: spec.SchemaProps{ + Description: "AutoPromoteActiveServiceDelaySeconds add a delay before automatically promoting the ReplicaSet under the preview service to the active service.", + Type: []string{"integer"}, + Format: "int32", + }, + }, "scaleDownDelaySeconds": { SchemaProps: spec.SchemaProps{ Description: "ScaleDownDelaySeconds adds a delay before scaling down the previous replicaset. See https://github.com/argoproj/argo-rollouts/issues/19#issuecomment-476329960 for more information", diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 8d3fde909c..f5aea5a94e 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -81,6 +81,9 @@ type BlueGreenStrategy struct { // resumed the new replicaset will be full scaled up before the switch occurs // +optional PreviewReplicaCount *int32 `json:"previewReplicaCount,omitempty"` + // AutoPromoteActiveServiceDelaySeconds add a delay before automatically promoting the ReplicaSet under the preview + // service to the active service. + AutoPromoteActiveServiceDelaySeconds *int32 `json:"autoPromoteActiveServiceDelaySeconds,omitempty"` // ScaleDownDelaySeconds adds a delay before scaling down the previous replicaset. See // https://github.com/argoproj/argo-rollouts/issues/19#issuecomment-476329960 for more information // +optional diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index b0553a3a64..defe3afc37 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -54,6 +54,11 @@ func (in *BlueGreenStrategy) DeepCopyInto(out *BlueGreenStrategy) { *out = new(int32) **out = **in } + if in.AutoPromoteActiveServiceDelaySeconds != nil { + in, out := &in.AutoPromoteActiveServiceDelaySeconds, &out.AutoPromoteActiveServiceDelaySeconds + *out = new(int32) + **out = **in + } if in.ScaleDownDelaySeconds != nil { in, out := &in.ScaleDownDelaySeconds, &out.ScaleDownDelaySeconds *out = new(int32)