diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 1d085304fc..2109b52ada 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -114,6 +114,9 @@ type CanaryStrategy struct { // CanaryService holds the name of a service which selects pods with canary version and don't select any pods with stable version. // +optional CanaryService string `json:"canaryService,omitempty"` + // StableService holds the name of a service which selects pods with stable version and don't select any pods with canary version. + // +optional + StableService string `json:"stableService,omitempty"` // Steps define the order of phases to execute the canary deployment // +optional Steps []CanaryStep `json:"steps,omitempty"` diff --git a/rollout/canary.go b/rollout/canary.go index 25a3e14d1c..854788c57e 100644 --- a/rollout/canary.go +++ b/rollout/canary.go @@ -52,7 +52,7 @@ func (c *RolloutController) rolloutCanary(rollout *v1alpha1.Rollout, rsList []*a return err } - if err := c.reconcileCanaryService(roCtx); err != nil { + if err := c.reconcileStableAndCanaryService(roCtx); err != nil { return err } diff --git a/rollout/canary_test.go b/rollout/canary_test.go index 24cc2aca25..40cc9c4df7 100644 --- a/rollout/canary_test.go +++ b/rollout/canary_test.go @@ -1012,6 +1012,57 @@ func TestCanaryRolloutWithInvalidCanaryServiceName(t *testing.T) { assert.Equal(t, condition["reason"], conditions.ServiceNotFoundReason) } +func TestCanaryRolloutWithStableService(t *testing.T) { + f := newFixture(t) + defer f.Close() + + stableSvc := newService("stable", 80, nil) + rollout := newCanaryRollout("foo", 0, nil, nil, nil, intstr.FromInt(1), intstr.FromInt(0)) + rs := newReplicaSetWithStatus(rollout, 0, 0) + rollout.Spec.Strategy.Canary.StableService = stableSvc.Name + rollout.Status.Canary.StableRS = rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + f.rolloutLister = append(f.rolloutLister, rollout) + f.objects = append(f.objects, rollout) + f.kubeobjects = append(f.kubeobjects, stableSvc, rs) + f.serviceLister = append(f.serviceLister, stableSvc) + + _ = f.expectPatchServiceAction(stableSvc, rollout.Status.CurrentPodHash) + _ = f.expectPatchRolloutAction(rollout) + f.run(getKey(rollout, t)) +} + +func TestCanaryRolloutWithInvalidStableServiceName(t *testing.T) { + f := newFixture(t) + defer f.Close() + + rollout := newCanaryRollout("foo", 0, nil, nil, nil, intstr.FromInt(1), intstr.FromInt(0)) + rs := newReplicaSetWithStatus(rollout, 0, 0) + rollout.Spec.Strategy.Canary.StableService = "invalid-stable" + rollout.Status.Canary.StableRS = rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + f.rolloutLister = append(f.rolloutLister, rollout) + f.objects = append(f.objects, rollout) + f.kubeobjects = append(f.kubeobjects, rs) + + patchIndex := f.expectPatchRolloutAction(rollout) + f.runExpectError(getKey(rollout, t), true) + + patch := make(map[string]interface{}) + patchData := f.getPatchedRollout(patchIndex) + err := json.Unmarshal([]byte(patchData), &patch) + assert.NoError(t, err) + + c, ok, err := unstructured.NestedSlice(patch, "status", "conditions") + assert.NoError(t, err) + assert.True(t, ok) + assert.Len(t, c, 1) + + condition, ok := c[0].(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, condition["reason"], conditions.ServiceNotFoundReason) +} + func TestCanaryRolloutScaleWhilePaused(t *testing.T) { f := newFixture(t) defer f.Close() diff --git a/rollout/controller_test.go b/rollout/controller_test.go index eb5e48cb50..6898ca8e64 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -1164,7 +1164,7 @@ func TestComputeHashChangeTolerationCanary(t *testing.T) { // this should only update observedGeneration and nothing else // NOTE: This test will fail on every k8s library upgrade. // To fix it, update expectedPatch to match the new hash. - expectedPatch := `{"status":{"observedGeneration":"866857855d"}}` + expectedPatch := `{"status":{"observedGeneration":"66c6f797f8"}}` patch := f.getPatchedRollout(patchIndex) assert.Equal(t, expectedPatch, patch) } diff --git a/rollout/service.go b/rollout/service.go index 94190a40b5..e28b5020a8 100644 --- a/rollout/service.go +++ b/rollout/service.go @@ -117,17 +117,34 @@ func (c *RolloutController) getPreviewAndActiveServices(r *v1alpha1.Rollout) (*c return previewSvc, activeSvc, nil } -func (c *RolloutController) reconcileCanaryService(roCtx *canaryContext) error { +func (c *RolloutController) reconcileStableAndCanaryService(roCtx *canaryContext) error { r := roCtx.Rollout() newRS := roCtx.NewRS() - if r.Spec.Strategy.Canary == nil || r.Spec.Strategy.Canary.CanaryService == "" { + stableRS := roCtx.StableRS() + if r.Spec.Strategy.Canary == nil { return nil } + if r.Spec.Strategy.Canary.StableService != "" && stableRS != nil { + svc, err := c.getReferencedService(r, r.Spec.Strategy.Canary.StableService) + if err != nil { + return err + } - svc, err := c.getReferencedService(r, r.Spec.Strategy.Canary.CanaryService) - if err != nil { - return err + err = c.switchServiceSelector(svc, stableRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], r) + if err != nil { + return err + } } + if r.Spec.Strategy.Canary.CanaryService != "" && newRS != nil { + svc, err := c.getReferencedService(r, r.Spec.Strategy.Canary.CanaryService) + if err != nil { + return err + } - return c.switchServiceSelector(svc, newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], r) + err = c.switchServiceSelector(svc, newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], r) + if err != nil { + return err + } + } + return nil }