diff --git a/docs/features/specification.md b/docs/features/specification.md index 628320e47d..67c0d6b063 100644 --- a/docs/features/specification.md +++ b/docs/features/specification.md @@ -274,7 +274,7 @@ spec: virtualService: name: rollout-vsvc # required routes: - - primary # At least one route is required + - primary # optional if there is a single route in VirtualService, required otherwise # NGINX Ingress Controller routing configuration nginx: diff --git a/docs/features/traffic-management/istio.md b/docs/features/traffic-management/istio.md index 49a0a1b220..91c5d0110b 100644 --- a/docs/features/traffic-management/istio.md +++ b/docs/features/traffic-management/istio.md @@ -17,7 +17,7 @@ stable ReplicaSet. Istio provides two approaches for weighted traffic splitting, are available as options in Argo Rollouts: 1. [Host-level traffic splitting](#host-level-traffic-splitting) -2. [Subset-lvel traffic splitting](#subset-level-traffic-splitting) +2. [Subset-level traffic splitting](#subset-level-traffic-splitting) ## Host-level Traffic Splitting @@ -49,7 +49,7 @@ spec: virtualService: name: rollout-vsvc # required routes: - - primary # required + - primary # optional if there is a single route in VirtualService, required otherwise steps: - setWeight: 5 - pause: @@ -60,7 +60,7 @@ The VirtualService must contain an HTTP route with a name referenced in the Roll two route destinations with `host` values that match the `canaryService` and `stableService` referenced in the Rollout. If the VirtualService is defined in a different namespace than the rollout, its name should be `rollout-vsvc.`. Note that Istio requires that all weights add to -100, so the initial weights can be be 100% to stable, and 0% to canary. +100, so the initial weights can be 100% to stable, and 0% to canary. ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -85,7 +85,7 @@ spec: Finally, a canary and stable Service should be deployed. The selector of these Services will be modified by the Rollout during an update to target the canary and stable ReplicaSet pods. -Note that if virtualservice and destionation host reside in different namespaces (e.g., virtualservice and rollout are not in the same namespace), we should use FQDN as the destination host like `stable-svc.`. +Note that if the VirtualService and destination host resides in different namespaces (e.g., VirtualService and Rollout are not in the same namespace), the namespace should be included in the destination host (e.g. `stable-svc.`). ```yaml apiVersion: v1 @@ -127,7 +127,7 @@ During the lifecycle of a Rollout update, Argo Rollouts will continuously: !!! note - Rollout does not make any other assumptions about the fields within the Virtual Service or the Istio mesh. The user could specify additional configurations for the virtual service like URI rewrite rules on the primary route or any other route if desired. The user can also create specific destination rules for each of the services. + Rollout does not make any other assumptions about the fields within the VirtualService or the Istio mesh. The user could specify additional configurations for the VirtualService like URI rewrite rules on the primary route or any other route if desired. The user can also create specific DestinationRules for each of the services. ## Subset-level Traffic Splitting @@ -162,7 +162,7 @@ spec: virtualService: name: rollout-vsvc # required routes: - - primary # required + - primary # optional if there is a single route in VirtualService, required otherwise destinationRule: name: rollout-destrule # required canarySubsetName: canary # required @@ -173,7 +173,7 @@ spec: duration: 10m ``` -A single service should be defined, which target the Rollout pods. Note that unlike the first +A single service should be defined, which targets the Rollout pods. Note that unlike the first approach, where traffic splitting is against multiple Services which are modified to contain the rollout-pod-template-hash of the canary/stable ReplicaSets, this Service is not modified by the rollout controller. @@ -195,8 +195,8 @@ spec: The VirtualService must contain an HTTP route with a name referenced in the Rollout, containing two route destinations with `subset` values that match the `canarySubsetName` and `stableSubsetName` -referenced in the Rollout. Note that Istio require that all weights add to 100, so the initial -weights can be be 100% to stable, and 0% to canary. +referenced in the Rollout. Note that Istio requires that all weights add to 100, so the initial +weights can be 100% to stable, and 0% to canary. ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -254,11 +254,11 @@ splitting. ### DNS requirements With host-level splitting, the VirtualService requires different `host` values to split among the -two destinations. However, using two host values implies that there are different DNS names. For -north-south traffic, which reach the service through the Istio Gateway, having multiple DNS names to -reach the canary vs. stable pods may not matter. However, for east-west traffic that happen inside -the cluster, it forces microservice-to-microservice communication to choose whether to hit the -stable or the canary DNS name, go through the gateway, or add DNS entries for the virtualservices. +two destinations. However, using two host values implies the use of different DNS names (one for +the canary, the other for the stable). For north-south traffic, which reaches the Service through +the Istio Gateway, having multiple DNS names to reach the canary vs. stable pods may not matter. +However, for east-west or intra-cluster traffic, it forces microservice-to-microservice communication to choose whether to hit the +stable or the canary DNS name, go through the gateway, or add DNS entries for the VirtualServices. In this situation, the DestinationRule subset traffic splitting would be a better option for intra-cluster canarying. @@ -358,7 +358,7 @@ other controllers (e.g. Argo Rollouts) controller manage them instead. An early design alternative was that instead of the controller modifying a referenced VirtualService, the Rollout controller would create, manage, and own a Virtual Service. While this approach is GitOps friendly, it introduces other issues: -* To provide the same flexibility as referencing VirtualService within a Rollout, the Rollout needs to inline a large portion of the Istio spec. However, networking is outside the responsibility of the Rollout and makes the Rollout spec unnecessary complicated. +* To provide the same flexibility as referencing VirtualService within a Rollout, the Rollout needs to inline a large portion of the Istio spec. However, networking is outside the responsibility of the Rollout and makes the Rollout spec unnecessarily complicated. * If Istio introduces a feature, that feature will not be available in Argo Rollouts until implemented within Argo Rollouts. Both of these issues adds more complexity to the users and Argo Rollouts developers compared to referencing a Virtual Service. diff --git a/docs/getting-started/istio/index.md b/docs/getting-started/istio/index.md index 3afdbbc45e..d9a0bcc778 100644 --- a/docs/getting-started/istio/index.md +++ b/docs/getting-started/istio/index.md @@ -34,7 +34,7 @@ spec: # Reference to a VirtualService which the controller updates with canary weights name: rollouts-demo-vsvc routes: - - primary # At least one route is required + - primary # optional if there is a single route in VirtualService, required otherwise ... ``` diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 44e0c54d3b..94846d67d5 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -525,7 +525,6 @@ spec: type: array required: - name - - routes type: object required: - virtualService diff --git a/manifests/install.yaml b/manifests/install.yaml index e342e88d18..0f15119591 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -10214,7 +10214,6 @@ spec: type: array required: - name - - routes type: object required: - virtualService diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 9e49501cf2..15aa6988a0 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -10214,7 +10214,6 @@ spec: type: array required: - name - - routes type: object required: - virtualService diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index e2d5e077de..5f2c816d81 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -823,7 +823,7 @@ "items": { "type": "string" }, - "title": "Routes list of routes within VirtualService to edit" + "title": "Routes are list of routes within VirtualService to edit. If omitted, VirtualService must have a single route" } }, "title": "IstioVirtualService holds information on the virtual service the rollout needs to modify" diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index a3dd74eb63..6e6b1ef0dc 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -559,7 +559,7 @@ message IstioVirtualService { // Name holds the name of the VirtualService optional string name = 1; - // Routes list of routes within VirtualService to edit + // Routes are list of routes within VirtualService to edit. If omitted, VirtualService must have a single route repeated string routes = 2; } diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index e00f99b582..f354099836 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -1636,7 +1636,7 @@ func schema_pkg_apis_rollouts_v1alpha1_IstioVirtualService(ref common.ReferenceC }, "routes": { SchemaProps: spec.SchemaProps{ - Description: "Routes list of routes within VirtualService to edit", + Description: "Routes are list of routes within VirtualService to edit. If omitted, VirtualService must have a single route", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -1650,7 +1650,7 @@ func schema_pkg_apis_rollouts_v1alpha1_IstioVirtualService(ref common.ReferenceC }, }, }, - Required: []string{"name", "routes"}, + Required: []string{"name"}, }, }, } diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index e33ab9d223..b4b89ff9a3 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -360,8 +360,8 @@ type IstioTrafficRouting struct { type IstioVirtualService struct { // Name holds the name of the VirtualService Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // Routes list of routes within VirtualService to edit - Routes []string `json:"routes" protobuf:"bytes,2,rep,name=routes"` + // Routes are list of routes within VirtualService to edit. If omitted, VirtualService must have a single route + Routes []string `json:"routes,omitempty" protobuf:"bytes,2,rep,name=routes"` } // IstioDestinationRule is a reference to an Istio DestinationRule to modify and shape traffic diff --git a/pkg/apis/rollouts/validation/validation.go b/pkg/apis/rollouts/validation/validation.go index 0b93087aa5..0e4cf04d98 100644 --- a/pkg/apis/rollouts/validation/validation.go +++ b/pkg/apis/rollouts/validation/validation.go @@ -50,8 +50,6 @@ const ( ScaleDownLimitLargerThanRevisionLimit = "This rollout's revision history limit can not be smaller than the rollout's scale down limit" // InvalidTrafficRoutingMessage indicates that both canary and stable service must be set to use Traffic Routing InvalidTrafficRoutingMessage = "Canary service and Stable service must to be set to use Traffic Routing" - // InvalidIstioRoutesMessage indicates that rollout does not have a route specified for the istio Traffic Routing - InvalidIstioRoutesMessage = "Istio virtual service must have at least 1 route specified" // InvalidAnalysisArgsMessage indicates that arguments provided in analysis steps are refrencing un-supported metadatafield. //supported fields are "metadata.annotations", "metadata.labels", "metadata.name", "metadata.namespace", "metadata.uid" InvalidAnalysisArgsMessage = "Analyses arguments must refer to valid object metadata supported by downwardAPI" @@ -213,9 +211,6 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat allErrs = append(allErrs, field.Invalid(fldPath.Child("canaryService"), canary.CanaryService, InvalidTrafficRoutingMessage)) } } - if canary.TrafficRouting != nil && canary.TrafficRouting.Istio != nil && len(canary.TrafficRouting.Istio.VirtualService.Routes) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("trafficRouting").Child("istio").Child("virtualService").Child("routes"), "[]", InvalidIstioRoutesMessage)) - } if canary.ScaleDownDelaySeconds != nil && canary.TrafficRouting == nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("scaleDownDelaySeconds"), *canary.ScaleDownDelaySeconds, InvalidCanaryScaleDownDelay)) diff --git a/rollout/trafficrouting/istio/istio.go b/rollout/trafficrouting/istio/istio.go index 7224623f74..6119f20d88 100644 --- a/rollout/trafficrouting/istio/istio.go +++ b/rollout/trafficrouting/istio/istio.go @@ -83,10 +83,6 @@ func (patches virtualServicePatches) patchVirtualService(httpRoutes []interface{ func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHTTPRoute, desiredWeight int64) virtualServicePatches { canarySvc := r.rollout.Spec.Strategy.Canary.CanaryService stableSvc := r.rollout.Spec.Strategy.Canary.StableService - routes := map[string]bool{} - for _, r := range r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes { - routes[r] = true - } canarySubset := "" stableSubset := "" if r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule != nil { @@ -94,14 +90,14 @@ func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHT stableSubset = r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.StableSubsetName } + // err can be ignored because we already called ValidateHTTPRoutes earlier + routeIndexesToPatch, _ := getRouteIndexesToPatch(r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes, httpRoutes) + patches := virtualServicePatches{} - for i := range httpRoutes { - route := httpRoutes[i] - if !routes[route.Name] { - continue - } + for _, routeIdx := range routeIndexesToPatch { + route := httpRoutes[routeIdx] for j := range route.Route { - destination := httpRoutes[i].Route[j] + destination := route.Route[j] var host string if idx := strings.Index(destination.Destination.Host, "."); idx > 0 { @@ -115,7 +111,7 @@ func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHT if (host != "" && host == canarySvc) || (subset != "" && subset == canarySubset) { if weight != desiredWeight { patch := virtualServicePatch{ - routeIndex: i, + routeIndex: routeIdx, destinationIndex: j, weight: desiredWeight, } @@ -125,7 +121,7 @@ func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHT if (host != "" && host == stableSvc) || (subset != "" && subset == stableSubset) { if weight != 100-desiredWeight { patch := virtualServicePatch{ - routeIndex: i, + routeIndex: routeIdx, destinationIndex: j, weight: 100 - desiredWeight, } @@ -466,6 +462,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32) error { } _, err = client.Update(ctx, modifiedVsvc, metav1.UpdateOptions{}) if err == nil { + r.log.Debugf("UpdatedVirtualService: %s", modifiedVsvc) r.recorder.Eventf(r.rollout, record.EventOptions{EventReason: "UpdatedVirtualService"}, "VirtualService `%s` set to desiredWeight '%d'", vsvcName, desiredWeight) } return err @@ -475,34 +472,48 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { return true, nil } +// getRouteIndexesToPatch returns array indices of the httpRoutes which need to be patched when updating weights +func getRouteIndexesToPatch(routeNames []string, httpRoutes []VirtualServiceHTTPRoute) ([]int, error) { + var routeIndexesToPatch []int + if len(routeNames) == 0 { + if len(httpRoutes) != 1 { + return nil, fmt.Errorf("VirtualService spec.http[] must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.routes") + } + routeIndexesToPatch = append(routeIndexesToPatch, 0) + } else { + for _, routeName := range routeNames { + foundRoute := false + for i, route := range httpRoutes { + if route.Name == routeName { + routeIndexesToPatch = append(routeIndexesToPatch, i) + foundRoute = true + break + } + } + if !foundRoute { + return nil, fmt.Errorf("Route '%s' is not found", routeName) + } + } + } + return routeIndexesToPatch, nil +} + // validateHTTPRoutes ensures that all the routes in the rollout exist and they only have two destinations func ValidateHTTPRoutes(r *v1alpha1.Rollout, httpRoutes []VirtualServiceHTTPRoute) error { - routes := r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes stableSvc := r.Spec.Strategy.Canary.StableService canarySvc := r.Spec.Strategy.Canary.CanaryService - routesPatched := map[string]bool{} - for _, route := range routes { - routesPatched[route] = false - } - - for _, route := range httpRoutes { - // check if the httpRoute is in the list of routes from the rollout - if _, ok := routesPatched[route.Name]; ok { - routesPatched[route.Name] = true - err := validateVirtualServiceHTTPRouteDestinations(route, stableSvc, canarySvc, r.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule) - if err != nil { - return err - } - } + routeIndexesToPatch, err := getRouteIndexesToPatch(r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes, httpRoutes) + if err != nil { + return err } - - for i := range routesPatched { - if !routesPatched[i] { - return fmt.Errorf("Route '%s' is not found", i) + for _, routeIndex := range routeIndexesToPatch { + route := httpRoutes[routeIndex] + err := validateVirtualServiceHTTPRouteDestinations(route, stableSvc, canarySvc, r.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule) + if err != nil { + return err } } - return nil } diff --git a/rollout/trafficrouting/istio/istio_test.go b/rollout/trafficrouting/istio/istio_test.go index 01909bb158..9c416ad465 100644 --- a/rollout/trafficrouting/istio/istio_test.go +++ b/rollout/trafficrouting/istio/istio_test.go @@ -63,7 +63,10 @@ func rollout(stableSvc, canarySvc, vsvc string, routes []string) *v1alpha1.Rollo func checkDestination(t *testing.T, route map[string]interface{}, svc string, expectWeight int) { destinations := route["route"].([]interface{}) - routeName := route["name"].(string) + routeName := "" + if routeNameObj, ok := route["name"]; ok { + routeName = routeNameObj.(string) + } for _, elem := range destinations { destination := elem.(map[string]interface{}) if destination["destination"].(map[string]interface{})["host"] == svc { @@ -103,6 +106,25 @@ spec: host: canary weight: 0` +const singleRouteVsvc = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + http: + - route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + func TestReconcileWeightsBaseCase(t *testing.T) { r := &Reconciler{ rollout: rollout("stable", "canary", "vsvc", []string{"primary"}), @@ -171,6 +193,41 @@ func TestReconcileVirtualServiceNotFound(t *testing.T) { assert.True(t, k8serrors.IsNotFound(err)) } +// TestReconcileAmbiguousRoutes tests when we omit route names and there are multiple routes in the VirtualService +func TestReconcileAmbiguousRoutes(t *testing.T) { + obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) + client := testutil.NewFakeDynamicClient(obj) + ro := rollout("stable", "canary", "vsvc", nil) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + err := r.SetWeight(0) + assert.Equal(t, "VirtualService spec.http[] must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.routes", err.Error()) +} + +// TestReconcileInferredSingleRoute we can support case where we infer the only route in the VirtualService +func TestReconcileInferredSingleRoute(t *testing.T) { + obj := unstructuredutil.StrToUnstructuredUnsafe(singleRouteVsvc) + client := testutil.NewFakeDynamicClient(obj) + ro := rollout("stable", "canary", "vsvc", nil) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + err := r.SetWeight(10) + assert.NoError(t, err) + actions := client.Actions() + assert.Len(t, actions, 1) + assert.Equal(t, "update", actions[0].GetVerb()) + + // Verify we actually made the correct change + vsvcUn, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(ro.Namespace).Get(context.TODO(), "vsvc", metav1.GetOptions{}) + assert.NoError(t, err) + routes, _, _ := unstructured.NestedSlice(vsvcUn.Object, "spec", "http") + route := routes[0].(map[string]interface{}) + checkDestination(t, route, "stable", 90) + checkDestination(t, route, "canary", 10) +} + func TestType(t *testing.T) { client := testutil.NewFakeDynamicClient() ro := rollout("stable", "canary", "vsvc", []string{"primary"}) diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index 63863f5a24..b32e5353a1 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -1261,6 +1261,10 @@ spec: image: nginx:1.19-alpine ports: - containerPort: 80 + resources: + requests: + memory: 16Mi + cpu: 5m strategy: canary: steps: diff --git a/test/e2e/istio/istio-subset-split-single-route.yaml b/test/e2e/istio/istio-subset-split-single-route.yaml new file mode 100644 index 0000000000..842672a06b --- /dev/null +++ b/test/e2e/istio/istio-subset-split-single-route.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: Service +metadata: + name: istio-subset-split-single-route +spec: + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: istio-subset-split-single-route + +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: istio-subset-split-single-route-vsvc +spec: + hosts: + - istio-subset-split-single-route + http: + - route: + - destination: + host: istio-subset-split-single-route + subset: stable + weight: 100 + - destination: + host: istio-subset-split-single-route + subset: canary + weight: 0 + +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: istio-subset-split-single-route-destrule +spec: + host: istio-subset-split-single-route + subsets: + - name: stable + labels: + app: istio-subset-split-single-route + - name: canary + labels: + app: istio-subset-split-single-route + +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: istio-subset-split-single-route +spec: + strategy: + canary: + trafficRouting: + istio: + virtualService: + name: istio-subset-split-single-route-vsvc + destinationRule: + name: istio-subset-split-single-route-destrule + canarySubsetName: canary + stableSubsetName: stable + steps: + - setWeight: 10 + - pause: {} + selector: + matchLabels: + app: istio-subset-split-single-route + template: + metadata: + labels: + app: istio-subset-split-single-route + spec: + containers: + - name: istio-subset-split-single-route + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m diff --git a/test/e2e/istio_test.go b/test/e2e/istio_test.go index 2b3b5e5006..967abbbb03 100644 --- a/test/e2e/istio_test.go +++ b/test/e2e/istio_test.go @@ -147,3 +147,53 @@ spec: }). ExpectRevisionPodCount("1", 0) // since we moved back to basic canary, we should scale down older RSs } + +func (s *IstioSuite) TestIstioSubsetSplitSingleRoute() { + s.Given(). + RolloutObjects("@istio/istio-subset-split-single-route.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), int64(100), vsvc.Spec.HTTP[0].Route[0].Weight) + assert.Equal(s.T(), int64(0), vsvc.Spec.HTTP[0].Route[1].Weight) + + rs1 := t.GetReplicaSetByRevision("1") + destrule := t.GetDestinationRule() + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], destrule.Spec.Subsets[0].Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) // stable + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], destrule.Spec.Subsets[1].Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) // canary + }). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), int64(90), vsvc.Spec.HTTP[0].Route[0].Weight) + assert.Equal(s.T(), int64(10), vsvc.Spec.HTTP[0].Route[1].Weight) + + rs1 := t.GetReplicaSetByRevision("1") + rs2 := t.GetReplicaSetByRevision("2") + destrule := t.GetDestinationRule() + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], destrule.Spec.Subsets[0].Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) // stable + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], destrule.Spec.Subsets[1].Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) // canary + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1*time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), int64(100), vsvc.Spec.HTTP[0].Route[0].Weight) + assert.Equal(s.T(), int64(0), vsvc.Spec.HTTP[0].Route[1].Weight) + + rs2 := t.GetReplicaSetByRevision("2") + destrule := t.GetDestinationRule() + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], destrule.Spec.Subsets[0].Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) // stable + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], destrule.Spec.Subsets[1].Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) // canary + }). + ExpectRevisionPodCount("1", 1) // don't scale down old replicaset since it will be within scaleDownDelay +}