From 750393ec08ac6c8460522a59a28ce2d19ce3c5f4 Mon Sep 17 00:00:00 2001 From: Michael Pleshakov Date: Wed, 25 Sep 2019 11:22:51 -0700 Subject: [PATCH 1/2] Improve routing rules This commit improves the routing rules in VirtualServer and VirtualServerRoute: - Introduce actions: implement actions of the type pass. - Get rid of rules field. Instead, add matches field. - Improve conditions - allow conditions to include matched values. - Improve matches - allow splits in matches. The improvements are not backward compatible. --- docs/virtualserver-and-virtualserverroute.md | 134 ++-- .../advanced-routing/cafe-virtual-server.yaml | 28 +- .../cafe-virtual-server.yaml | 6 +- .../coffee-virtual-server-route.yaml | 3 +- .../tea-virtual-server-route.yaml | 3 +- .../cafe-virtual-server.yaml | 6 +- internal/configs/virtualserver.go | 197 ++--- internal/configs/virtualserver_test.go | 691 ++++++++++++++---- pkg/apis/configuration/v1alpha1/types.go | 34 +- .../v1alpha1/zz_generated.deepcopy.go | 80 +- .../configuration/validation/validation.go | 85 ++- .../validation/validation_test.go | 606 +++++++-------- 12 files changed, 1176 insertions(+), 697 deletions(-) diff --git a/docs/virtualserver-and-virtualserverroute.md b/docs/virtualserver-and-virtualserverroute.md index 55e6f67659..fb88ef7cba 100644 --- a/docs/virtualserver-and-virtualserverroute.md +++ b/docs/virtualserver-and-virtualserverroute.md @@ -24,8 +24,8 @@ This document is the reference documentation for the resources. To see additiona - [Upstream.Healthcheck](#upstreamhealthcheck) - [Upstream.SessionCookie](#upstreamsessioncookie) - [Header](#header) + - [Action](#action) - [Split](#split) - - [Rules](#rules) - [Condition](#condition) - [Match](#match) - [Using VirtualServer and VirtualServerRoute](#using-virtualserver-and-virtualserverroute) @@ -57,9 +57,11 @@ spec: port: 80 routes: - path: /tea - upstream: tea + action: + pass: tea - path: /coffee - upstream: coffee + action: + pass: coffee ``` | Field | Description | Type | Required | @@ -83,21 +85,22 @@ secret: cafe-secret ### VirtualServer.Route -The route defines rules for routing requests to one or multiple upstreams. For example: +The route defines rules for matching client requests to actions like passing a request to an upstream. For example: ```yaml -path: /tea -upstream: tea + path: /tea + action: + pass: tea ``` | Field | Description | Type | Required | | ----- | ----------- | ---- | -------- | | `path` | The path of the route. NGINX will match it against the URI of a request. The path must start with `/` and must not include any whitespace characters, `{`, `}` or `;`. For example, `/`, `/path` are valid. The path must be unique among the paths of all routes of the VirtualServer. | `string` | Yes | -| `upstream` | The name of an upstream. The upstream with that name must be defined in the VirtualServer. | `string` | No* | -| `splits` | The splits configuration for traffic splitting. Must include at least 2 splits. | [`[]split`](#Split) | No* | -| `rules` | The rules configuration for advanced content-based routing. |[`rules`](#Rules) | No* | +| `action` | The default action to perform for a request. | [`action`](#Action) | No* | +| `splits` | The default splits configuration for traffic splitting. Must include at least 2 splits. | [`[]split`](#Split) | No* | +| `matches` | The matching rules for advanced content-based routing. Requires the default `action` or `splits`. Unmatched requests will be handled by the default `action` or `splits`. |[`matches`](#Match) | No | | `route` | The name of a VirtualServerRoute resource that defines this route. If the VirtualServerRoute belongs to a different namespace than the VirtualServer, you need to include the namespace. For example, `tea-namespace/tea`. | `string` | No* | -\* -- a route must include exactly one of the following: `upstream`, `splits`, `rules` or `route`. +\* -- a route must include exactly one of the following: `action`, `splits`, or `route`. ## VirtualServerRoute Specification @@ -121,7 +124,8 @@ spec: port: 80 routes: - path: /tea - upstream: tea + action: + pass: tea - path: /coffee route: coffee-ns/coffee ``` @@ -144,9 +148,11 @@ spec: port: 80 subroutes: - path: /coffee/latte - upstream: latte + action: + pass: latte - path: /coffee/espresso - upstream: espresso + action: + pass: espresso ``` Note that each subroute must have a `path` that starts with the same prefix (here `/coffee`), which is defined in the route of the VirtualServer. Additionally, the `host` in the VirtualServerRoute must be the same as the `host` of the VirtualServer. @@ -159,20 +165,21 @@ Note that each subroute must have a `path` that starts with the same prefix (her ### VirtualServerRoute.Subroute -The subroute defines rules for routing requests to one or multiple upstreams. For example: +The subroute defines rules for matching client requests to actions like passing a request to an upstream. For example: ```yaml path: /coffee -upstream: coffee +action: + pass: coffee ``` | Field | Description | Type | Required | | ----- | ----------- | ---- | -------- | | `path` | The path of the subroute. NGINX will match it against the URI of a request. The path must start with the same path as the path of the route of the VirtualServer that references this resource. It must not include any whitespace characters, `{`, `}` or `;`. The path must be unique among the paths of all subroutes of the VirtualServerRoute. | `string` | Yes | -| `upstream` | The name of an upstream. The upstream with that name must be defined in the VirtualServerRoute. | `string` | No* | -| `splits` | The splits configuration for traffic splitting. Must include at least 2 splits. | [`[]splits`](#Split) | No* | -| `rules` | The rules configuration advanced content-based routing. |[`rules`](#Rules) | No* | +| `action` | The default action to perform for a request. | [`action`](#Action) | No* | +| `splits` | The default splits configuration for traffic splitting. Must include at least 2 splits. | [`[]split`](#Split) | No* | +| `matches` | The matching rules for advanced content-based routing. Requires the default `action` or `splits`. Unmatched requests will be handled by the default `action` or `splits`. |[`matches`](#Match) | No | -\* -- a subroute must include exactly one of the following: `upstream`, `splits` or `rules`. +\* -- a subroute must include exactly one of the following: `action` or `splits`. ## Common Parts of the VirtualServer and VirtualServerRoute @@ -355,27 +362,45 @@ value: example.com | `name` | The name of the header. | `string` | Yes | | `value` | The value of the header. | `string` | No | +### Action + +The action defines an action to perform for a request. + +In the example below, client requests are passed to an upstream `coffee`: +```yaml + path: /coffee + action: + pass: coffee +``` + +| Field | Description | Type | Required | +| ----- | ----------- | ---- | -------- | +| `pass` | Passes requests to an upstream. The upstream with that name must be defined in the resource. | `string` | Yes | + + ### Split -The split defines a weight for an upstream as part of the splits configuration. +The split defines a weight for an action as part of the splits configuration. -In the example below NGINX routes 80% of requests to the upstream `coffee-v1` and the remaining 20% to `coffee-v2`: +In the example below NGINX passes 80% of requests to the upstream `coffee-v1` and the remaining 20% to `coffee-v2`: ```yaml splits: - weight: 80 - upstream: coffee-v1 + action: + pass: coffee-v1 - weight: 20 - upstream: coffee-v2 + action: + pass: coffee-v2 ``` | Field | Description | Type | Required | | ----- | ----------- | ---- | -------- | -| `weight` | The weight of an upstream. Must fall into the range `1..99`. The sum of the weights of all splits must be equal to `100`. | `int` | Yes | -| `upstream` | The name of an upstream. Must be defined in the resource. | `string` | Yes | +| `weight` | The weight of an action. Must fall into the range `1..99`. The sum of the weights of all splits must be equal to `100`. | `int` | Yes | +| `action` | The action to perform for a request. | [`action`](#Action) | Yes | -### Rules +### Match -The rules defines a set of content-based routing rules in a route or subroute. +The match defines a match between conditions and an action or splits. In the example below, NGINX routes requests with the path `/coffee` to different upstreams based on the value of the cookie `user`: * `user=john` -> `coffee-future` @@ -384,17 +409,19 @@ In the example below, NGINX routes requests with the path `/coffee` to different ```yaml path: /coffee -rules: - conditions: +matches: +- conditions: - cookie: user - matches: - - values: - - john - upstream: coffee-future - - values: - - bob - upstream: coffee-deprecated - defaultUpstream: coffee-stable + value: john + action: + pass: coffee-future +- conditions: + - cookie: user + value: bob + action: + pass: coffee-deprecated +action: + pass: coffee-stable ``` In the next example, NGINX routes requests based on the value of the built-in [`$request_method` variable](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_method), which represents the HTTP method of a request: @@ -403,25 +430,27 @@ In the next example, NGINX routes requests based on the value of the built-in [` ```yaml path: /coffee -rules: - conditions: +matches: +- conditions: - variable: $request_method - matches: - - values: - - POST - upstream: coffee-post - defaultUpstream: coffee + value: POST + action: + pass: coffee-post +action: + pass: coffee ``` | Field | Description | Type | Required | | ----- | ----------- | ---- | -------- | | `conditions` | A list of conditions. Must include at least 1 condition. | [`[]condition`](#Condition) | Yes | -| `matches` | A list of matches. Must include at least 1 match. | [`[]match`](#Match) | Yes | -| `defaultUpstream` | The name of the default upstream. NGINX will route requests to the default upstream if it cannot find a successful match in matches. The upstream must be defined in the resource. | `string` | Yes | +| `action` | The action to perform for a request. | [`action`](#Action) | No* | +| `splits` | The splits configuration for traffic splitting. Must include at least 2 splits. | [`[]split`](#Split) | No* | + +\* -- a match must include exactly one of the following: `action` or `splits`. ### Condition -The condition defines a condition in rules. +The condition defines a condition in a match. | Field | Description | Type | Required | | ----- | ----------- | ---- | -------- | @@ -429,21 +458,12 @@ The condition defines a condition in rules. | `cookie` | The name of a cookie. Must consist of alphanumeric characters or `_`. | `string` | No* | | `argument` | The name of an argument. Must consist of alphanumeric characters or `_`. | `string` | No* | | `variable` | The name of an NGINX variable. Must start with `$`. See the list of the supported variables below the table. | `string` | No* | +| `value` | The value to match the condition against. How to define a value is shown below the table. | `string` | Yes | \* -- a condition must include exactly one of the following: `header`, `cookie`, `argument` or `variable`. Supported NGINX variables: `$args`, `$http2`, `$https`, `$remote_addr`, `$remote_port`, `$query_string`, `$request`, `$request_body`, `$request_uri`, `$request_method`, `$scheme`. Find the documentation for each variable [here](https://nginx.org/en/docs/varindex.html). -### Match - -The match defines a match that corresponds to conditions. - - -| Field | Description | Type | Required | -| ----- | ----------- | ---- | -------- | -| `values` | A list of matched values. Must include a value for each condition defined in the rules. How to define a value is shown below the table. | `[]string` | Yes | -| `upstream` | The name of an upstream. Must be defined in the resource. | `string` | Yes | - The value supports two kinds of matching: * *Case-insensitive string comparison*. For example: * `john` -- case-insensitive matching that succeeds for strings, such as `john`, `John`, `JOHN`. diff --git a/examples-of-custom-resources/advanced-routing/cafe-virtual-server.yaml b/examples-of-custom-resources/advanced-routing/cafe-virtual-server.yaml index da7689ce93..bbe1cf8ad7 100644 --- a/examples-of-custom-resources/advanced-routing/cafe-virtual-server.yaml +++ b/examples-of-custom-resources/advanced-routing/cafe-virtual-server.yaml @@ -19,20 +19,20 @@ spec: port: 80 routes: - path: /tea - rules: - conditions: + matches: + - conditions: - variable: $request_method - matches: - - values: - - POST - upstream: tea-post - defaultUpstream: tea + value: POST + action: + pass: tea-post + action: + pass: tea - path: /coffee - rules: - conditions: + matches: + - conditions: - cookie: version - matches: - - values: - - v2 - upstream: coffee-v2 - defaultUpstream: coffee-v1 \ No newline at end of file + value: v2 + action: + pass: coffee-v2 + action: + pass: coffee-v1 \ No newline at end of file diff --git a/examples-of-custom-resources/basic-configuration/cafe-virtual-server.yaml b/examples-of-custom-resources/basic-configuration/cafe-virtual-server.yaml index 192d3123a5..551f2dc32e 100644 --- a/examples-of-custom-resources/basic-configuration/cafe-virtual-server.yaml +++ b/examples-of-custom-resources/basic-configuration/cafe-virtual-server.yaml @@ -15,6 +15,8 @@ spec: port: 80 routes: - path: /tea - upstream: tea + action: + pass: tea - path: /coffee - upstream: coffee + action: + pass: coffee \ No newline at end of file diff --git a/examples-of-custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml b/examples-of-custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml index 88a71d7ea3..4134119240 100644 --- a/examples-of-custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml +++ b/examples-of-custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml @@ -11,4 +11,5 @@ spec: port: 80 subroutes: - path: /coffee - upstream: coffee \ No newline at end of file + action: + pass: coffee \ No newline at end of file diff --git a/examples-of-custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml b/examples-of-custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml index 2f63026eeb..08dfcc003c 100644 --- a/examples-of-custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml +++ b/examples-of-custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml @@ -11,4 +11,5 @@ spec: port: 80 subroutes: - path: /tea - upstream: tea \ No newline at end of file + action: + pass: tea \ No newline at end of file diff --git a/examples-of-custom-resources/traffic-splitting/cafe-virtual-server.yaml b/examples-of-custom-resources/traffic-splitting/cafe-virtual-server.yaml index d9719c425f..b87b329986 100644 --- a/examples-of-custom-resources/traffic-splitting/cafe-virtual-server.yaml +++ b/examples-of-custom-resources/traffic-splitting/cafe-virtual-server.yaml @@ -15,6 +15,8 @@ spec: - path: /coffee splits: - weight: 90 - upstream: coffee-v1 + action: + pass: coffee-v1 - weight: 10 - upstream: coffee-v2 + action: + pass: coffee-v2 diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index e4f78f78f7..b7fcb75780 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -89,12 +89,12 @@ func (namer *variableNamer) GetNameForSplitClientVariable(index int) string { return fmt.Sprintf("$vs_%s_splits_%d", namer.safeNsName, index) } -func (namer *variableNamer) GetNameForVariableForRulesRouteMap(rulesIndex int, matchIndex int, conditionIndex int) string { - return fmt.Sprintf("$vs_%s_rules_%d_match_%d_cond_%d", namer.safeNsName, rulesIndex, matchIndex, conditionIndex) +func (namer *variableNamer) GetNameForVariableForMatchesRouteMap(matchesIndex int, matchIndex int, conditionIndex int) string { + return fmt.Sprintf("$vs_%s_matches_%d_match_%d_cond_%d", namer.safeNsName, matchesIndex, matchIndex, conditionIndex) } -func (namer *variableNamer) GetNameForVariableForRulesRouteMainMap(rulesIndex int) string { - return fmt.Sprintf("$vs_%s_rules_%d", namer.safeNsName, rulesIndex) +func (namer *variableNamer) GetNameForVariableForMatchesRouteMainMap(matchesIndex int) string { + return fmt.Sprintf("$vs_%s_matches_%d", namer.safeNsName, matchesIndex) } func newHealthCheckWithDefaults(upstream conf_v1alpha1.Upstream, upstreamName string, cfgParams *ConfigParams) *version2.HealthCheck { @@ -220,7 +220,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE var splitClients []version2.SplitClient var maps []version2.Map - rulesRoutes := 0 + matchesRoutes := 0 variableNamer := newVariableNamer(virtualServerEx.VirtualServer) @@ -231,22 +231,23 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE continue } - if len(r.Splits) > 0 { - splitCfg := generateSplitRouteConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams) + if len(r.Matches) > 0 { + cfg := generateMatchesConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients), vsc.cfgParams) - splitClients = append(splitClients, splitCfg.SplitClient) - locations = append(locations, splitCfg.Locations...) - internalRedirectLocations = append(internalRedirectLocations, splitCfg.InternalRedirectLocation) - } else if r.Rules != nil { - rulesRouteCfg := generateRulesRouteConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, rulesRoutes, vsc.cfgParams) + maps = append(maps, cfg.Maps...) + locations = append(locations, cfg.Locations...) + internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) + splitClients = append(splitClients, cfg.SplitClients...) - maps = append(maps, rulesRouteCfg.Maps...) - locations = append(locations, rulesRouteCfg.Locations...) - internalRedirectLocations = append(internalRedirectLocations, rulesRouteCfg.InternalRedirectLocation) + matchesRoutes++ + } else if len(r.Splits) > 0 { + cfg := generateDefaultSplitsConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams) - rulesRoutes++ + splitClients = append(splitClients, cfg.SplitClients...) + locations = append(locations, cfg.Locations...) + internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) } else { - upstreamName := virtualServerUpstreamNamer.GetNameForUpstream(r.Upstream) + upstreamName := virtualServerUpstreamNamer.GetNameForUpstream(r.Action.Pass) upstream := crUpstreams[upstreamName] loc := generateLocation(r.Path, upstreamName, upstream, vsc.cfgParams) locations = append(locations, loc) @@ -258,22 +259,23 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE for _, vsr := range virtualServerEx.VirtualServerRoutes { upstreamNamer := newUpstreamNamerForVirtualServerRoute(virtualServerEx.VirtualServer, vsr) for _, r := range vsr.Spec.Subroutes { - if len(r.Splits) > 0 { - splitCfg := generateSplitRouteConfig(r, upstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams) + if len(r.Matches) > 0 { + cfg := generateMatchesConfig(r, upstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients), vsc.cfgParams) - splitClients = append(splitClients, splitCfg.SplitClient) - locations = append(locations, splitCfg.Locations...) - internalRedirectLocations = append(internalRedirectLocations, splitCfg.InternalRedirectLocation) - } else if r.Rules != nil { - rulesRouteCfg := generateRulesRouteConfig(r, upstreamNamer, crUpstreams, variableNamer, rulesRoutes, vsc.cfgParams) + maps = append(maps, cfg.Maps...) + locations = append(locations, cfg.Locations...) + internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) + splitClients = append(splitClients, cfg.SplitClients...) - maps = append(maps, rulesRouteCfg.Maps...) - locations = append(locations, rulesRouteCfg.Locations...) - internalRedirectLocations = append(internalRedirectLocations, rulesRouteCfg.InternalRedirectLocation) + matchesRoutes++ + } else if len(r.Splits) > 0 { + cfg := generateDefaultSplitsConfig(r, upstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams) - rulesRoutes++ + splitClients = append(splitClients, cfg.SplitClients...) + locations = append(locations, cfg.Locations...) + internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) } else { - upstreamName := upstreamNamer.GetNameForUpstream(r.Upstream) + upstreamName := upstreamNamer.GetNameForUpstream(r.Action.Pass) upstream := crUpstreams[upstreamName] loc := generateLocation(r.Path, upstreamName, upstream, vsc.cfgParams) locations = append(locations, loc) @@ -516,77 +518,75 @@ func generateLocation(path string, upstreamName string, upstream conf_v1alpha1.U } } -type splitRouteCfg struct { - SplitClient version2.SplitClient +type routingCfg struct { + Maps []version2.Map + SplitClients []version2.SplitClient Locations []version2.Location InternalRedirectLocation version2.InternalRedirectLocation } -func generateSplitRouteConfig(route conf_v1alpha1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1alpha1.Upstream, variableNamer *variableNamer, index int, cfgParams *ConfigParams) splitRouteCfg { - splitClientVarName := variableNamer.GetNameForSplitClientVariable(index) - - // Generate a SplitClient +func generateSplits(splits []conf_v1alpha1.Split, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1alpha1.Upstream, variableNamer *variableNamer, scIndex int, cfgParams *ConfigParams) (version2.SplitClient, []version2.Location) { var distributions []version2.Distribution - for i, s := range route.Splits { + for i, s := range splits { d := version2.Distribution{ Weight: fmt.Sprintf("%d%%", s.Weight), - Value: fmt.Sprintf("@splits_%d_split_%d", index, i), + Value: fmt.Sprintf("@splits_%d_split_%d", scIndex, i), } distributions = append(distributions, d) } splitClient := version2.SplitClient{ Source: "$request_id", - Variable: splitClientVarName, + Variable: variableNamer.GetNameForSplitClientVariable(scIndex), Distributions: distributions, } - // Generate locations var locations []version2.Location - for i, s := range route.Splits { - path := fmt.Sprintf("@splits_%d_split_%d", index, i) - upstreamName := upstreamNamer.GetNameForUpstream(s.Upstream) + for i, s := range splits { + path := fmt.Sprintf("@splits_%d_split_%d", scIndex, i) + upstreamName := upstreamNamer.GetNameForUpstream(s.Action.Pass) upstream := crUpstreams[upstreamName] loc := generateLocation(path, upstreamName, upstream, cfgParams) locations = append(locations, loc) } - // Generate an InternalRedirectLocation + return splitClient, locations +} + +func generateDefaultSplitsConfig(route conf_v1alpha1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1alpha1.Upstream, variableNamer *variableNamer, scIndex int, cfgParams *ConfigParams) routingCfg { + sc, locs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex, cfgParams) + + splitClientVarName := variableNamer.GetNameForSplitClientVariable(scIndex) + irl := version2.InternalRedirectLocation{ Path: route.Path, Destination: splitClientVarName, } - return splitRouteCfg{ - SplitClient: splitClient, - Locations: locations, + return routingCfg{ + SplitClients: []version2.SplitClient{sc}, + Locations: locs, InternalRedirectLocation: irl, } } -type rulesRouteCfg struct { - Maps []version2.Map - Locations []version2.Location - InternalRedirectLocation version2.InternalRedirectLocation -} - -func generateRulesRouteConfig(route conf_v1alpha1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1alpha1.Upstream, - variableNamer *variableNamer, index int, cfgParams *ConfigParams) rulesRouteCfg { +func generateMatchesConfig(route conf_v1alpha1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1alpha1.Upstream, + variableNamer *variableNamer, index int, scIndex int, cfgParams *ConfigParams) routingCfg { // Generate maps var maps []version2.Map - for i, m := range route.Rules.Matches { - for j, c := range route.Rules.Conditions { - source := getNameForSourceForRulesRouteMapFromCondition(c) - variable := variableNamer.GetNameForVariableForRulesRouteMap(index, i, j) + for i, m := range route.Matches { + for j, c := range m.Conditions { + source := getNameForSourceForMatchesRouteMapFromCondition(c) + variable := variableNamer.GetNameForVariableForMatchesRouteMap(index, i, j) successfulResult := "1" - if j < len(m.Values)-1 { - successfulResult = variableNamer.GetNameForVariableForRulesRouteMap(index, i, j+1) + if j < len(m.Conditions)-1 { + successfulResult = variableNamer.GetNameForVariableForMatchesRouteMap(index, i, j+1) } - params := generateParametersForRulesRouteMap(m.Values[j], successfulResult) + params := generateParametersForMatchesRouteMap(c.Value, successfulResult) matchMap := version2.Map{ Source: source, @@ -597,26 +597,40 @@ func generateRulesRouteConfig(route conf_v1alpha1.Route, upstreamNamer *upstream } } + scLocalIndex := 0 + // Generate the main map source := "" var params []version2.Parameter - for i := range route.Rules.Matches { - source += variableNamer.GetNameForVariableForRulesRouteMap(index, i, 0) + for i, m := range route.Matches { + source += variableNamer.GetNameForVariableForMatchesRouteMap(index, i, 0) + + v := fmt.Sprintf("~^%s1", strings.Repeat("0", i)) + r := fmt.Sprintf("@matches_%d_match_%d", index, i) + if len(m.Splits) > 0 { + r = variableNamer.GetNameForSplitClientVariable(scIndex + scLocalIndex) + scLocalIndex++ + } p := version2.Parameter{ - Value: fmt.Sprintf("~^%s1", strings.Repeat("0", i)), - Result: fmt.Sprintf("@rules_%d_match_%d", index, i), + Value: v, + Result: r, } params = append(params, p) } + defaultResult := fmt.Sprintf("@matches_%d_default", index) + if len(route.Splits) > 0 { + defaultResult = variableNamer.GetNameForSplitClientVariable(scIndex + scLocalIndex) + } + defaultParam := version2.Parameter{ Value: "default", - Result: fmt.Sprintf("@rules_%d_default", index), + Result: defaultResult, } params = append(params, defaultParam) - variable := variableNamer.GetNameForVariableForRulesRouteMainMap(index) + variable := variableNamer.GetNameForVariableForMatchesRouteMainMap(index) mainMap := version2.Map{ Source: source, @@ -625,34 +639,51 @@ func generateRulesRouteConfig(route conf_v1alpha1.Route, upstreamNamer *upstream } maps = append(maps, mainMap) - // Generate locations for each match + // Generate locations for each match and split client var locations []version2.Location + var splitClients []version2.SplitClient + scLocalIndex = 0 - for i, m := range route.Rules.Matches { - path := fmt.Sprintf("@rules_%d_match_%d", index, i) - upstreamName := upstreamNamer.GetNameForUpstream(m.Upstream) + for i, m := range route.Matches { + if len(m.Splits) > 0 { + sc, locs := generateSplits(m.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex+scLocalIndex, cfgParams) + scLocalIndex++ + + splitClients = append(splitClients, sc) + locations = append(locations, locs...) + } else { + path := fmt.Sprintf("@matches_%d_match_%d", index, i) + upstreamName := upstreamNamer.GetNameForUpstream(m.Action.Pass) + upstream := crUpstreams[upstreamName] + loc := generateLocation(path, upstreamName, upstream, cfgParams) + locations = append(locations, loc) + } + } + + // Generate default splits or default action + if len(route.Splits) > 0 { + sc, locs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex+scLocalIndex, cfgParams) + splitClients = append(splitClients, sc) + locations = append(locations, locs...) + } else { + path := fmt.Sprintf("@matches_%d_default", index) + upstreamName := upstreamNamer.GetNameForUpstream(route.Action.Pass) upstream := crUpstreams[upstreamName] loc := generateLocation(path, upstreamName, upstream, cfgParams) locations = append(locations, loc) } - // Generate defaultUpstream location - path := fmt.Sprintf("@rules_%d_default", index) - upstreamName := upstreamNamer.GetNameForUpstream(route.Rules.DefaultUpstream) - upstream := crUpstreams[upstreamName] - loc := generateLocation(path, upstreamName, upstream, cfgParams) - locations = append(locations, loc) - // Generate an InternalRedirectLocation to the location defined by the main map variable irl := version2.InternalRedirectLocation{ Path: route.Path, Destination: variable, } - return rulesRouteCfg{ + return routingCfg{ Maps: maps, Locations: locations, InternalRedirectLocation: irl, + SplitClients: splitClients, } } @@ -663,7 +694,7 @@ var specialMapParameters = map[string]bool{ "volatile": true, } -func generateValueForRulesRouteMap(matchedValue string) (value string, isNegative bool) { +func generateValueForMatchesRouteMap(matchedValue string) (value string, isNegative bool) { if len(matchedValue) == 0 { return `""`, false } @@ -680,8 +711,8 @@ func generateValueForRulesRouteMap(matchedValue string) (value string, isNegativ return fmt.Sprintf(`"%s"`, matchedValue), isNegative } -func generateParametersForRulesRouteMap(matchedValue string, successfulResult string) []version2.Parameter { - value, isNegative := generateValueForRulesRouteMap(matchedValue) +func generateParametersForMatchesRouteMap(matchedValue string, successfulResult string) []version2.Parameter { + value, isNegative := generateValueForMatchesRouteMap(matchedValue) valueResult := successfulResult defaultResult := "0" @@ -704,7 +735,7 @@ func generateParametersForRulesRouteMap(matchedValue string, successfulResult st return params } -func getNameForSourceForRulesRouteMapFromCondition(condition conf_v1alpha1.Condition) string { +func getNameForSourceForMatchesRouteMapFromCondition(condition conf_v1alpha1.Condition) string { if condition.Header != "" { return fmt.Sprintf("$http_%s", strings.ReplaceAll(condition.Header, "-", "_")) } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index d7ad431aab..49d956ae1d 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -151,26 +151,26 @@ func TestVariableNamer(t *testing.T) { t.Errorf("GetNameForSplitClientVariable() returned %q but expected %q", result, expected) } - // GetNameForVariableForRulesRouteMap() - rulesIndex := 1 + // GetNameForVariableForMatchesRouteMap() + matchesIndex := 1 matchIndex := 2 conditionIndex := 3 - expected = "$vs_default_cafe_rules_1_match_2_cond_3" + expected = "$vs_default_cafe_matches_1_match_2_cond_3" - result = variableNamer.GetNameForVariableForRulesRouteMap(rulesIndex, matchIndex, conditionIndex) + result = variableNamer.GetNameForVariableForMatchesRouteMap(matchesIndex, matchIndex, conditionIndex) if result != expected { - t.Errorf("GetNameForVariableForRulesRouteMap() returned %q but expected %q", result, expected) + t.Errorf("GetNameForVariableForMatchesRouteMap() returned %q but expected %q", result, expected) } - // GetNameForVariableForRulesRouteMainMap() - rulesIndex = 2 + // GetNameForVariableForMatchesRouteMainMap() + matchesIndex = 2 - expected = "$vs_default_cafe_rules_2" + expected = "$vs_default_cafe_matches_2" - result = variableNamer.GetNameForVariableForRulesRouteMainMap(rulesIndex) + result = variableNamer.GetNameForVariableForMatchesRouteMainMap(matchesIndex) if result != expected { - t.Errorf("GetNameForVariableForRulesRouteMainMap() returned %q but expected %q", result, expected) + t.Errorf("GetNameForVariableForMatchesRouteMainMap() returned %q but expected %q", result, expected) } } @@ -198,12 +198,16 @@ func TestGenerateVirtualServerConfig(t *testing.T) { }, Routes: []conf_v1alpha1.Route{ { - Path: "/tea", - Upstream: "tea", + Path: "/tea", + Action: &conf_v1alpha1.Action{ + Pass: "tea", + }, }, { - Path: "/tea-latest", - Upstream: "tea-latest", + Path: "/tea-latest", + Action: &conf_v1alpha1.Action{ + Pass: "tea-latest", + }, }, { Path: "/coffee", @@ -247,8 +251,10 @@ func TestGenerateVirtualServerConfig(t *testing.T) { }, Subroutes: []conf_v1alpha1.Route{ { - Path: "/coffee", - Upstream: "coffee", + Path: "/coffee", + Action: &conf_v1alpha1.Action{ + Pass: "coffee", + }, }, }, }, @@ -270,8 +276,10 @@ func TestGenerateVirtualServerConfig(t *testing.T) { }, Subroutes: []conf_v1alpha1.Route{ { - Path: "/subtea", - Upstream: "subtea", + Path: "/subtea", + Action: &conf_v1alpha1.Action{ + Pass: "subtea", + }, }, }, }, @@ -415,12 +423,16 @@ func TestGenerateVirtualServerConfigForVirtualServerWithSplits(t *testing.T) { Path: "/tea", Splits: []conf_v1alpha1.Split{ { - Weight: 90, - Upstream: "tea-v1", + Weight: 90, + Action: &conf_v1alpha1.Action{ + Pass: "tea-v1", + }, }, { - Weight: 10, - Upstream: "tea-v2", + Weight: 10, + Action: &conf_v1alpha1.Action{ + Pass: "tea-v2", + }, }, }, }, @@ -470,12 +482,16 @@ func TestGenerateVirtualServerConfigForVirtualServerWithSplits(t *testing.T) { Path: "/coffee", Splits: []conf_v1alpha1.Split{ { - Weight: 40, - Upstream: "coffee-v1", + Weight: 40, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, }, { - Weight: 60, - Upstream: "coffee-v2", + Weight: 60, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, }, }, }, @@ -612,7 +628,7 @@ func TestGenerateVirtualServerConfigForVirtualServerWithSplits(t *testing.T) { } } -func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { +func TestGenerateVirtualServerConfigForVirtualServerWithMatches(t *testing.T) { virtualServerEx := VirtualServerEx{ VirtualServer: &conf_v1alpha1.VirtualServer{ ObjectMeta: meta_v1.ObjectMeta{ @@ -636,21 +652,21 @@ func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { Routes: []conf_v1alpha1.Route{ { Path: "/tea", - Rules: &conf_v1alpha1.Rules{ - Conditions: []conf_v1alpha1.Condition{ - { - Header: "x-version", - }, - }, - Matches: []conf_v1alpha1.Match{ - { - Values: []string{ - "v2", + Matches: []conf_v1alpha1.Match{ + { + Conditions: []conf_v1alpha1.Condition{ + { + Header: "x-version", + Value: "v2", }, - Upstream: "tea-v2", + }, + Action: &conf_v1alpha1.Action{ + Pass: "tea-v2", }, }, - DefaultUpstream: "tea-v1", + }, + Action: &conf_v1alpha1.Action{ + Pass: "tea-v1", }, }, { @@ -697,21 +713,21 @@ func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { Subroutes: []conf_v1alpha1.Route{ { Path: "/coffee", - Rules: &conf_v1alpha1.Rules{ - Conditions: []conf_v1alpha1.Condition{ - { - Argument: "version", - }, - }, - Matches: []conf_v1alpha1.Match{ - { - Values: []string{ - "v2", + Matches: []conf_v1alpha1.Match{ + { + Conditions: []conf_v1alpha1.Condition{ + { + Argument: "version", + Value: "v2", }, - Upstream: "coffee-v2", + }, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", }, }, - DefaultUpstream: "coffee-v1", + }, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", }, }, }, @@ -760,7 +776,7 @@ func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { Maps: []version2.Map{ { Source: "$http_x_version", - Variable: "$vs_default_cafe_rules_0_match_0_cond_0", + Variable: "$vs_default_cafe_matches_0_match_0_cond_0", Parameters: []version2.Parameter{ { Value: `"v2"`, @@ -773,22 +789,22 @@ func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { }, }, { - Source: "$vs_default_cafe_rules_0_match_0_cond_0", - Variable: "$vs_default_cafe_rules_0", + Source: "$vs_default_cafe_matches_0_match_0_cond_0", + Variable: "$vs_default_cafe_matches_0", Parameters: []version2.Parameter{ { Value: "~^1", - Result: "@rules_0_match_0", + Result: "@matches_0_match_0", }, { Value: "default", - Result: "@rules_0_default", + Result: "@matches_0_default", }, }, }, { Source: "$arg_version", - Variable: "$vs_default_cafe_rules_1_match_0_cond_0", + Variable: "$vs_default_cafe_matches_1_match_0_cond_0", Parameters: []version2.Parameter{ { Value: `"v2"`, @@ -801,16 +817,16 @@ func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { }, }, { - Source: "$vs_default_cafe_rules_1_match_0_cond_0", - Variable: "$vs_default_cafe_rules_1", + Source: "$vs_default_cafe_matches_1_match_0_cond_0", + Variable: "$vs_default_cafe_matches_1", Parameters: []version2.Parameter{ { Value: "~^1", - Result: "@rules_1_match_0", + Result: "@matches_1_match_0", }, { Value: "default", - Result: "@rules_1_default", + Result: "@matches_1_default", }, }, }, @@ -821,37 +837,37 @@ func TestGenerateVirtualServerConfigForVirtualServerWithRules(t *testing.T) { InternalRedirectLocations: []version2.InternalRedirectLocation{ { Path: "/tea", - Destination: "$vs_default_cafe_rules_0", + Destination: "$vs_default_cafe_matches_0", }, { Path: "/coffee", - Destination: "$vs_default_cafe_rules_1", + Destination: "$vs_default_cafe_matches_1", }, }, Locations: []version2.Location{ { - Path: "@rules_0_match_0", + Path: "@matches_0_match_0", ProxyPass: "http://vs_default_cafe_tea-v2", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", ProxyNextUpstreamTries: 0, }, { - Path: "@rules_0_default", + Path: "@matches_0_default", ProxyPass: "http://vs_default_cafe_tea-v1", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", ProxyNextUpstreamTries: 0, }, { - Path: "@rules_1_match_0", + Path: "@matches_1_match_0", ProxyPass: "http://vs_default_cafe_vsr_default_coffee_coffee-v2", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", ProxyNextUpstreamTries: 0, }, { - Path: "@rules_1_default", + Path: "@matches_1_default", ProxyPass: "http://vs_default_cafe_vsr_default_coffee_coffee-v1", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", @@ -1240,16 +1256,20 @@ func TestCreateUpstreamsForPlus(t *testing.T) { }, Routes: []conf_v1alpha1.Route{ { - Path: "/tea", - Upstream: "tea", + Path: "/tea", + Action: &conf_v1alpha1.Action{ + Pass: "tea", + }, }, { Path: "/coffee", Route: "default/coffee", }, { - Path: "/external", - Upstream: "external", + Path: "/external", + Action: &conf_v1alpha1.Action{ + Pass: "external", + }, }, }, }, @@ -1298,12 +1318,16 @@ func TestCreateUpstreamsForPlus(t *testing.T) { }, Subroutes: []conf_v1alpha1.Route{ { - Path: "/coffee", - Upstream: "coffee", + Path: "/coffee", + Action: &conf_v1alpha1.Action{ + Pass: "coffee", + }, }, { - Path: "/coffee/sub", - Upstream: "subselector-test", + Path: "/coffee/sub", + Action: &conf_v1alpha1.Action{ + Pass: "subselector-test", + }, }, }, }, @@ -1392,17 +1416,89 @@ func TestCreateUpstreamServersConfigForPlusNoUpstreams(t *testing.T) { } } -func TestGenerateSplitRouteConfig(t *testing.T) { +func TestGenerateSplits(t *testing.T) { + splits := []conf_v1alpha1.Split{ + { + Weight: 90, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, + }, + { + Weight: 10, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, + }, + } + + virtualServer := conf_v1alpha1.VirtualServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "cafe", + Namespace: "default", + }, + } + upstreamNamer := newUpstreamNamerForVirtualServer(&virtualServer) + variableNamer := newVariableNamer(&virtualServer) + scIndex := 1 + cfgParams := ConfigParams{} + crUpstreams := make(map[string]conf_v1alpha1.Upstream) + + expectedSplitClient := version2.SplitClient{ + Source: "$request_id", + Variable: "$vs_default_cafe_splits_1", + Distributions: []version2.Distribution{ + { + Weight: "90%", + Value: "@splits_1_split_0", + }, + { + Weight: "10%", + Value: "@splits_1_split_1", + }, + }, + } + expectedLocations := []version2.Location{ + { + Path: "@splits_1_split_0", + ProxyPass: "http://vs_default_cafe_coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_1_split_1", + ProxyPass: "http://vs_default_cafe_coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + } + + resultSplitClient, resultLocations := generateSplits(splits, upstreamNamer, crUpstreams, variableNamer, scIndex, &cfgParams) + if !reflect.DeepEqual(resultSplitClient, expectedSplitClient) { + t.Errorf("generateSplits() returned %v but expected %v", resultSplitClient, expectedSplitClient) + } + if !reflect.DeepEqual(resultLocations, expectedLocations) { + t.Errorf("generateSplits() returned %v but expected %v", resultLocations, expectedLocations) + } +} + +func TestGenerateDefaultSplitsConfig(t *testing.T) { route := conf_v1alpha1.Route{ Path: "/", Splits: []conf_v1alpha1.Split{ { - Weight: 90, - Upstream: "coffee-v1", + Weight: 90, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, }, { - Weight: 10, - Upstream: "coffee-v2", + Weight: 10, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, }, }, } @@ -1416,18 +1512,20 @@ func TestGenerateSplitRouteConfig(t *testing.T) { variableNamer := newVariableNamer(&virtualServer) index := 1 - expected := splitRouteCfg{ - SplitClient: version2.SplitClient{ - Source: "$request_id", - Variable: "$vs_default_cafe_splits_1", - Distributions: []version2.Distribution{ - { - Weight: "90%", - Value: "@splits_1_split_0", - }, - { - Weight: "10%", - Value: "@splits_1_split_1", + expected := routingCfg{ + SplitClients: []version2.SplitClient{ + { + Source: "$request_id", + Variable: "$vs_default_cafe_splits_1", + Distributions: []version2.Distribution{ + { + Weight: "90%", + Value: "@splits_1_split_0", + }, + { + Weight: "10%", + Value: "@splits_1_split_1", + }, }, }, }, @@ -1455,51 +1553,76 @@ func TestGenerateSplitRouteConfig(t *testing.T) { cfgParams := ConfigParams{} - result := generateSplitRouteConfig(route, upstreamNamer, map[string]conf_v1alpha1.Upstream{}, variableNamer, index, &cfgParams) + result := generateDefaultSplitsConfig(route, upstreamNamer, map[string]conf_v1alpha1.Upstream{}, variableNamer, index, &cfgParams) if !reflect.DeepEqual(result, expected) { - t.Errorf("generateSplitRouteConfig() returned %v but expected %v", result, expected) + t.Errorf("generateDefaultSplitsConfig() returned %v but expected %v", result, expected) } } -func TestGenerateRulesRouteConfig(t *testing.T) { +func TestGenerateMatchesConfig(t *testing.T) { route := conf_v1alpha1.Route{ Path: "/", - Rules: &conf_v1alpha1.Rules{ - Conditions: []conf_v1alpha1.Condition{ - { - Header: "x-version", - }, - { - Cookie: "user", - }, - { - Argument: "answer", + Matches: []conf_v1alpha1.Match{ + { + Conditions: []conf_v1alpha1.Condition{ + { + Header: "x-version", + Value: "v1", + }, + { + Cookie: "user", + Value: "john", + }, + { + Argument: "answer", + Value: "yes", + }, + { + Variable: "$request_method", + Value: "GET", + }, }, - { - Variable: "$request_method", + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", }, }, - Matches: []conf_v1alpha1.Match{ - { - Values: []string{ - "v1", - "john", - "yes", - "GET", + { + Conditions: []conf_v1alpha1.Condition{ + { + Header: "x-version", + Value: "v2", + }, + { + Cookie: "user", + Value: "paul", + }, + { + Argument: "answer", + Value: "no", + }, + { + Variable: "$request_method", + Value: "POST", }, - Upstream: "coffee-v1", }, - { - Values: []string{ - "v2", - "paul", - "no", - "POST", + Splits: []conf_v1alpha1.Split{ + { + Weight: 90, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, + }, + { + Weight: 10, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, }, - Upstream: "coffee-v2", }, }, - DefaultUpstream: "tea", + }, + Action: &conf_v1alpha1.Action{ + Pass: "tea", }, } virtualServer := conf_v1alpha1.VirtualServer{ @@ -1511,16 +1634,17 @@ func TestGenerateRulesRouteConfig(t *testing.T) { upstreamNamer := newUpstreamNamerForVirtualServer(&virtualServer) variableNamer := newVariableNamer(&virtualServer) index := 1 + scIndex := 2 - expected := rulesRouteCfg{ + expected := routingCfg{ Maps: []version2.Map{ { Source: "$http_x_version", - Variable: "$vs_default_cafe_rules_1_match_0_cond_0", + Variable: "$vs_default_cafe_matches_1_match_0_cond_0", Parameters: []version2.Parameter{ { Value: `"v1"`, - Result: "$vs_default_cafe_rules_1_match_0_cond_1", + Result: "$vs_default_cafe_matches_1_match_0_cond_1", }, { Value: "default", @@ -1530,11 +1654,11 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$cookie_user", - Variable: "$vs_default_cafe_rules_1_match_0_cond_1", + Variable: "$vs_default_cafe_matches_1_match_0_cond_1", Parameters: []version2.Parameter{ { Value: `"john"`, - Result: "$vs_default_cafe_rules_1_match_0_cond_2", + Result: "$vs_default_cafe_matches_1_match_0_cond_2", }, { Value: "default", @@ -1544,11 +1668,11 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$arg_answer", - Variable: "$vs_default_cafe_rules_1_match_0_cond_2", + Variable: "$vs_default_cafe_matches_1_match_0_cond_2", Parameters: []version2.Parameter{ { Value: `"yes"`, - Result: "$vs_default_cafe_rules_1_match_0_cond_3", + Result: "$vs_default_cafe_matches_1_match_0_cond_3", }, { Value: "default", @@ -1558,7 +1682,7 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$request_method", - Variable: "$vs_default_cafe_rules_1_match_0_cond_3", + Variable: "$vs_default_cafe_matches_1_match_0_cond_3", Parameters: []version2.Parameter{ { Value: `"GET"`, @@ -1572,11 +1696,11 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$http_x_version", - Variable: "$vs_default_cafe_rules_1_match_1_cond_0", + Variable: "$vs_default_cafe_matches_1_match_1_cond_0", Parameters: []version2.Parameter{ { Value: `"v2"`, - Result: "$vs_default_cafe_rules_1_match_1_cond_1", + Result: "$vs_default_cafe_matches_1_match_1_cond_1", }, { Value: "default", @@ -1586,11 +1710,11 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$cookie_user", - Variable: "$vs_default_cafe_rules_1_match_1_cond_1", + Variable: "$vs_default_cafe_matches_1_match_1_cond_1", Parameters: []version2.Parameter{ { Value: `"paul"`, - Result: "$vs_default_cafe_rules_1_match_1_cond_2", + Result: "$vs_default_cafe_matches_1_match_1_cond_2", }, { Value: "default", @@ -1600,11 +1724,11 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$arg_answer", - Variable: "$vs_default_cafe_rules_1_match_1_cond_2", + Variable: "$vs_default_cafe_matches_1_match_1_cond_2", Parameters: []version2.Parameter{ { Value: `"no"`, - Result: "$vs_default_cafe_rules_1_match_1_cond_3", + Result: "$vs_default_cafe_matches_1_match_1_cond_3", }, { Value: "default", @@ -1614,7 +1738,7 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, { Source: "$request_method", - Variable: "$vs_default_cafe_rules_1_match_1_cond_3", + Variable: "$vs_default_cafe_matches_1_match_1_cond_3", Parameters: []version2.Parameter{ { Value: `"POST"`, @@ -1627,41 +1751,48 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, }, { - Source: "$vs_default_cafe_rules_1_match_0_cond_0$vs_default_cafe_rules_1_match_1_cond_0", - Variable: "$vs_default_cafe_rules_1", + Source: "$vs_default_cafe_matches_1_match_0_cond_0$vs_default_cafe_matches_1_match_1_cond_0", + Variable: "$vs_default_cafe_matches_1", Parameters: []version2.Parameter{ { Value: "~^1", - Result: "@rules_1_match_0", + Result: "@matches_1_match_0", }, { Value: "~^01", - Result: "@rules_1_match_1", + Result: "$vs_default_cafe_splits_2", }, { Value: "default", - Result: "@rules_1_default", + Result: "@matches_1_default", }, }, }, }, Locations: []version2.Location{ { - Path: "@rules_1_match_0", + Path: "@matches_1_match_0", + ProxyPass: "http://vs_default_cafe_coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_2_split_0", ProxyPass: "http://vs_default_cafe_coffee-v1", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", ProxyNextUpstreamTries: 0, }, { - Path: "@rules_1_match_1", + Path: "@splits_2_split_1", ProxyPass: "http://vs_default_cafe_coffee-v2", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", ProxyNextUpstreamTries: 0, }, { - Path: "@rules_1_default", + Path: "@matches_1_default", ProxyPass: "http://vs_default_cafe_tea", ProxyNextUpstream: "error timeout", ProxyNextUpstreamTimeout: "0s", @@ -1670,19 +1801,261 @@ func TestGenerateRulesRouteConfig(t *testing.T) { }, InternalRedirectLocation: version2.InternalRedirectLocation{ Path: "/", - Destination: "$vs_default_cafe_rules_1", + Destination: "$vs_default_cafe_matches_1", + }, + SplitClients: []version2.SplitClient{ + { + Source: "$request_id", + Variable: "$vs_default_cafe_splits_2", + Distributions: []version2.Distribution{ + { + Weight: "90%", + Value: "@splits_2_split_0", + }, + { + Weight: "10%", + Value: "@splits_2_split_1", + }, + }, + }, + }, + } + + cfgParams := ConfigParams{} + + result := generateMatchesConfig(route, upstreamNamer, map[string]conf_v1alpha1.Upstream{}, variableNamer, index, scIndex, &cfgParams) + if !reflect.DeepEqual(result, expected) { + t.Errorf("generateMatchesConfig() returned \n%v but expected \n%v", result, expected) + } +} + +func TestGenerateMatchesConfigWithMultipleSplits(t *testing.T) { + route := conf_v1alpha1.Route{ + Path: "/", + Matches: []conf_v1alpha1.Match{ + { + Conditions: []conf_v1alpha1.Condition{ + { + Header: "x-version", + Value: "v1", + }, + }, + Splits: []conf_v1alpha1.Split{ + { + Weight: 30, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, + }, + { + Weight: 70, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, + }, + }, + }, + { + Conditions: []conf_v1alpha1.Condition{ + { + Header: "x-version", + Value: "v2", + }, + }, + Splits: []conf_v1alpha1.Split{ + { + Weight: 90, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, + }, + { + Weight: 10, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, + }, + }, + }, + }, + Splits: []conf_v1alpha1.Split{ + { + Weight: 99, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v1", + }, + }, + { + Weight: 1, + Action: &conf_v1alpha1.Action{ + Pass: "coffee-v2", + }, + }, + }, + } + virtualServer := conf_v1alpha1.VirtualServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "cafe", + Namespace: "default", + }, + } + upstreamNamer := newUpstreamNamerForVirtualServer(&virtualServer) + variableNamer := newVariableNamer(&virtualServer) + index := 1 + scIndex := 2 + + expected := routingCfg{ + Maps: []version2.Map{ + { + Source: "$http_x_version", + Variable: "$vs_default_cafe_matches_1_match_0_cond_0", + Parameters: []version2.Parameter{ + { + Value: `"v1"`, + Result: "1", + }, + { + Value: "default", + Result: "0", + }, + }, + }, + { + Source: "$http_x_version", + Variable: "$vs_default_cafe_matches_1_match_1_cond_0", + Parameters: []version2.Parameter{ + { + Value: `"v2"`, + Result: "1", + }, + { + Value: "default", + Result: "0", + }, + }, + }, + { + Source: "$vs_default_cafe_matches_1_match_0_cond_0$vs_default_cafe_matches_1_match_1_cond_0", + Variable: "$vs_default_cafe_matches_1", + Parameters: []version2.Parameter{ + { + Value: "~^1", + Result: "$vs_default_cafe_splits_2", + }, + { + Value: "~^01", + Result: "$vs_default_cafe_splits_3", + }, + { + Value: "default", + Result: "$vs_default_cafe_splits_4", + }, + }, + }, + }, + Locations: []version2.Location{ + { + Path: "@splits_2_split_0", + ProxyPass: "http://vs_default_cafe_coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_2_split_1", + ProxyPass: "http://vs_default_cafe_coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_3_split_0", + ProxyPass: "http://vs_default_cafe_coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_3_split_1", + ProxyPass: "http://vs_default_cafe_coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_4_split_0", + ProxyPass: "http://vs_default_cafe_coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + { + Path: "@splits_4_split_1", + ProxyPass: "http://vs_default_cafe_coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "0s", + ProxyNextUpstreamTries: 0, + }, + }, + InternalRedirectLocation: version2.InternalRedirectLocation{ + Path: "/", + Destination: "$vs_default_cafe_matches_1", + }, + SplitClients: []version2.SplitClient{ + { + Source: "$request_id", + Variable: "$vs_default_cafe_splits_2", + Distributions: []version2.Distribution{ + { + Weight: "30%", + Value: "@splits_2_split_0", + }, + { + Weight: "70%", + Value: "@splits_2_split_1", + }, + }, + }, + { + Source: "$request_id", + Variable: "$vs_default_cafe_splits_3", + Distributions: []version2.Distribution{ + { + Weight: "90%", + Value: "@splits_3_split_0", + }, + { + Weight: "10%", + Value: "@splits_3_split_1", + }, + }, + }, + { + Source: "$request_id", + Variable: "$vs_default_cafe_splits_4", + Distributions: []version2.Distribution{ + { + Weight: "99%", + Value: "@splits_4_split_0", + }, + { + Weight: "1%", + Value: "@splits_4_split_1", + }, + }, + }, }, } cfgParams := ConfigParams{} - result := generateRulesRouteConfig(route, upstreamNamer, map[string]conf_v1alpha1.Upstream{}, variableNamer, index, &cfgParams) + result := generateMatchesConfig(route, upstreamNamer, map[string]conf_v1alpha1.Upstream{}, variableNamer, index, scIndex, &cfgParams) if !reflect.DeepEqual(result, expected) { - t.Errorf("generateRulesRouteConfig() returned \n%v but expected \n%v", result, expected) + t.Errorf("generateMatchesConfig() returned \n%v but expected \n%v", result, expected) } } -func TestGenerateValueForRulesRouteMap(t *testing.T) { +func TestGenerateValueForMatchesRouteMap(t *testing.T) { tests := []struct { input string expectedValue string @@ -1736,17 +2109,17 @@ func TestGenerateValueForRulesRouteMap(t *testing.T) { } for _, test := range tests { - resultValue, resultIsNegative := generateValueForRulesRouteMap(test.input) + resultValue, resultIsNegative := generateValueForMatchesRouteMap(test.input) if resultValue != test.expectedValue { - t.Errorf("generateValueForRulesRouteMap(%q) returned %q but expected %q as the value", test.input, resultValue, test.expectedValue) + t.Errorf("generateValueForMatchesRouteMap(%q) returned %q but expected %q as the value", test.input, resultValue, test.expectedValue) } if resultIsNegative != test.expectedIsNegative { - t.Errorf("generateValueForRulesRouteMap(%q) returned %v but expected %v as the isNegative", test.input, resultIsNegative, test.expectedIsNegative) + t.Errorf("generateValueForMatchesRouteMap(%q) returned %v but expected %v as the isNegative", test.input, resultIsNegative, test.expectedIsNegative) } } } -func TestGenerateParametersForRulesRouteMap(t *testing.T) { +func TestGenerateParametersForMatchesRouteMap(t *testing.T) { tests := []struct { inputMatchedValue string inputSuccessfulResult string @@ -1783,14 +2156,14 @@ func TestGenerateParametersForRulesRouteMap(t *testing.T) { } for _, test := range tests { - result := generateParametersForRulesRouteMap(test.inputMatchedValue, test.inputSuccessfulResult) + result := generateParametersForMatchesRouteMap(test.inputMatchedValue, test.inputSuccessfulResult) if !reflect.DeepEqual(result, test.expected) { - t.Errorf("generateParametersForRulesRouteMap(%q, %q) returned %v but expected %v", test.inputMatchedValue, test.inputSuccessfulResult, result, test.expected) + t.Errorf("generateParametersForMatchesRouteMap(%q, %q) returned %v but expected %v", test.inputMatchedValue, test.inputSuccessfulResult, result, test.expected) } } } -func TestGetNameForSourceForRulesRouteMapFromCondition(t *testing.T) { +func TestGetNameForSourceForMatchesRouteMapFromCondition(t *testing.T) { tests := []struct { input conf_v1alpha1.Condition expected string @@ -1822,9 +2195,9 @@ func TestGetNameForSourceForRulesRouteMapFromCondition(t *testing.T) { } for _, test := range tests { - result := getNameForSourceForRulesRouteMapFromCondition(test.input) + result := getNameForSourceForMatchesRouteMapFromCondition(test.input) if result != test.expected { - t.Errorf("getNameForSourceForRulesRouteMapFromCondition() returned %q but expected %q for input %v", result, test.expected, test.input) + t.Errorf("getNameForSourceForMatchesRouteMapFromCondition() returned %q but expected %q for input %v", result, test.expected, test.input) } } } diff --git a/pkg/apis/configuration/v1alpha1/types.go b/pkg/apis/configuration/v1alpha1/types.go index 71d7ae62a4..a996cf924f 100644 --- a/pkg/apis/configuration/v1alpha1/types.go +++ b/pkg/apis/configuration/v1alpha1/types.go @@ -98,24 +98,22 @@ type SessionCookie struct { // Route defines a route. type Route struct { - Path string `json:"path"` - Upstream string `json:"upstream"` - Splits []Split `json:"splits"` - Rules *Rules `json:"rules"` - Route string `json:"route"` + Path string `json:"path"` + Route string `json:"route"` + Action *Action `json:"action"` + Splits []Split `json:"splits"` + Matches []Match `json:"matches"` } -// Split defines a split. -type Split struct { - Weight int `json:"weight"` - Upstream string `json:"upstream"` +// Action defines an action. +type Action struct { + Pass string `json:"pass"` } -// Rules defines rules. -type Rules struct { - Conditions []Condition `json:"conditions"` - Matches []Match `json:"matches"` - DefaultUpstream string `json:"defaultUpstream"` +// Split defines a split. +type Split struct { + Weight int `json:"weight"` + Action *Action `json:"action"` } // Condition defines a condition in a MatchRule. @@ -124,12 +122,14 @@ type Condition struct { Cookie string `json:"cookie"` Argument string `json:"argument"` Variable string `json:"variable"` + Value string `json:"value"` } -// Match defines a match in a MatchRule. +// Match defines a match. type Match struct { - Values []string `json:"values"` - Upstream string `json:"upstream"` + Conditions []Condition `json:"conditions"` + Action *Action `json:"action"` + Splits []Split `json:"splits"` } // TLS defines TLS configuration for a VirtualServer. diff --git a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go index 4a822acadf..ce7c32c819 100644 --- a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go @@ -8,6 +8,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Action) DeepCopyInto(out *Action) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Action. +func (in *Action) DeepCopy() *Action { + if in == nil { + return nil + } + out := new(Action) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -69,11 +85,23 @@ func (in *HealthCheck) DeepCopy() *HealthCheck { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Match) DeepCopyInto(out *Match) { *out = *in - if in.Values != nil { - in, out := &in.Values, &out.Values - *out = make([]string, len(*in)) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) copy(*out, *in) } + if in.Action != nil { + in, out := &in.Action, &out.Action + *out = new(Action) + **out = **in + } + if in.Splits != nil { + in, out := &in.Splits, &out.Splits + *out = make([]Split, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -90,36 +118,17 @@ func (in *Match) DeepCopy() *Match { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Route) DeepCopyInto(out *Route) { *out = *in + if in.Action != nil { + in, out := &in.Action, &out.Action + *out = new(Action) + **out = **in + } if in.Splits != nil { in, out := &in.Splits, &out.Splits *out = make([]Split, len(*in)) - copy(*out, *in) - } - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = new(Rules) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route. -func (in *Route) DeepCopy() *Route { - if in == nil { - return nil - } - out := new(Route) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Rules) DeepCopyInto(out *Rules) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]Condition, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.Matches != nil { in, out := &in.Matches, &out.Matches @@ -131,12 +140,12 @@ func (in *Rules) DeepCopyInto(out *Rules) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rules. -func (in *Rules) DeepCopy() *Rules { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route. +func (in *Route) DeepCopy() *Route { if in == nil { return nil } - out := new(Rules) + out := new(Route) in.DeepCopyInto(out) return out } @@ -160,6 +169,11 @@ func (in *SessionCookie) DeepCopy() *SessionCookie { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Split) DeepCopyInto(out *Split) { *out = *in + if in.Action != nil { + in, out := &in.Action, &out.Action + *out = new(Action) + **out = **in + } return } diff --git a/pkg/apis/configuration/validation/validation.go b/pkg/apis/configuration/validation/validation.go index 5290fbdebb..f6b0816b49 100644 --- a/pkg/apis/configuration/validation/validation.go +++ b/pkg/apis/configuration/validation/validation.go @@ -513,8 +513,8 @@ func validateRoute(route v1alpha1.Route, fieldPath *field.Path, upstreamNames se fieldCount := 0 - if route.Upstream != "" { - allErrs = append(allErrs, validateReferencedUpstream(route.Upstream, fieldPath.Child("upstream"), upstreamNames)...) + if route.Action != nil { + allErrs = append(allErrs, validateAction(route.Action, fieldPath.Child("action"), upstreamNames)...) fieldCount++ } @@ -523,9 +523,11 @@ func validateRoute(route v1alpha1.Route, fieldPath *field.Path, upstreamNames se fieldCount++ } - if route.Rules != nil { - allErrs = append(allErrs, validateRules(route.Rules, fieldPath.Child("rules"), upstreamNames)...) - fieldCount++ + // Matches are optional. that's why we don't do fieldCount++ + if len(route.Matches) > 0 { + for i, m := range route.Matches { + allErrs = append(allErrs, validateMatch(m, fieldPath.Child("matches").Index(i), upstreamNames)...) + } } if route.Route != "" { @@ -538,9 +540,9 @@ func validateRoute(route v1alpha1.Route, fieldPath *field.Path, upstreamNames se } if fieldCount != 1 { - msg := "must specify exactly one of: `upstream`, `splits`, `rules` or `route`" - if isRouteFieldForbidden { - msg = "must specify exactly one of: `upstream`, `splits` or `rules`" + msg := "must specify exactly one of `action`, `splits` or `route`" + if isRouteFieldForbidden || len(route.Matches) > 0 { + msg = "must specify exactly one of `action` or `splits`" } allErrs = append(allErrs, field.Invalid(fieldPath, "", msg)) @@ -549,6 +551,18 @@ func validateRoute(route v1alpha1.Route, fieldPath *field.Path, upstreamNames se return allErrs } +func validateAction(action *v1alpha1.Action, fieldPath *field.Path, upstreamNames sets.String) field.ErrorList { + allErrs := field.ErrorList{} + + if action.Pass == "" { + return append(allErrs, field.Required(fieldPath.Child("pass"), "")) + } + + allErrs = append(allErrs, validateReferencedUpstream(action.Pass, fieldPath.Child("pass"), upstreamNames)...) + + return allErrs +} + func validateRouteField(value string, fieldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -588,7 +602,11 @@ func validateSplits(splits []v1alpha1.Split, fieldPath *field.Path, upstreamName allErrs = append(allErrs, field.Invalid(idxPath.Child("weight"), s.Weight, msg)) } - allErrs = append(allErrs, validateReferencedUpstream(s.Upstream, idxPath.Child("upstream"), upstreamNames)...) + if s.Action == nil { + allErrs = append(allErrs, field.Required(idxPath.Child("action"), "")) + } else { + allErrs = append(allErrs, validateAction(s.Action, idxPath.Child("action"), upstreamNames)...) + } totalWeight += s.Weight } @@ -621,26 +639,32 @@ func validatePath(path string, fieldPath *field.Path) field.ErrorList { return allErrs } -func validateRules(rules *v1alpha1.Rules, fieldPath *field.Path, upstreamNames sets.String) field.ErrorList { +func validateMatch(match v1alpha1.Match, fieldPath *field.Path, upstreamNames sets.String) field.ErrorList { allErrs := field.ErrorList{} - if len(rules.Conditions) == 0 { + if len(match.Conditions) == 0 { allErrs = append(allErrs, field.Required(fieldPath.Child("conditions"), "must specify at least one condition")) } else { - for i, c := range rules.Conditions { + for i, c := range match.Conditions { allErrs = append(allErrs, validateCondition(c, fieldPath.Child("conditions").Index(i))...) } } - if len(rules.Matches) == 0 { - allErrs = append(allErrs, field.Required(fieldPath.Child("matches"), "must specify at least one match")) - } else { - for i, m := range rules.Matches { - allErrs = append(allErrs, validateMatch(m, fieldPath.Child("matches").Index(i), len(rules.Conditions), upstreamNames)...) - } + fieldCount := 0 + + if match.Action != nil { + allErrs = append(allErrs, validateAction(match.Action, fieldPath.Child("action"), upstreamNames)...) + fieldCount++ } - allErrs = append(allErrs, validateReferencedUpstream(rules.DefaultUpstream, fieldPath.Child("defaultUpstream"), upstreamNames)...) + if len(match.Splits) > 0 { + allErrs = append(allErrs, validateSplits(match.Splits, fieldPath.Child("splits"), upstreamNames)...) + fieldCount++ + } + + if fieldCount != 1 { + allErrs = append(allErrs, field.Invalid(fieldPath, "", "must specify exactly one of `action` or `splits`")) + } return allErrs } @@ -680,6 +704,10 @@ func validateCondition(condition v1alpha1.Condition, fieldPath *field.Path) fiel allErrs = append(allErrs, field.Invalid(fieldPath, "", "must specify exactly one of: `header`, `cookie`, `argument` or `variable`")) } + for _, msg := range isValidMatchValue(condition.Value) { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("value"), condition.Value, msg)) + } + return allErrs } @@ -737,25 +765,6 @@ func validateVariableName(name string, fieldPath *field.Path) field.ErrorList { return allErrs } -func validateMatch(match v1alpha1.Match, fieldPath *field.Path, conditionsCount int, upstreamNames sets.String) field.ErrorList { - allErrs := field.ErrorList{} - - if len(match.Values) != conditionsCount { - msg := fmt.Sprintf("must specify %d values (same as the number of conditions)", conditionsCount) - allErrs = append(allErrs, field.Invalid(fieldPath.Child("values"), "", msg)) - } - - for i, v := range match.Values { - for _, msg := range isValidMatchValue(v) { - allErrs = append(allErrs, field.Invalid(fieldPath.Child("values").Index(i), v, msg)) - } - } - - allErrs = append(allErrs, validateReferencedUpstream(match.Upstream, fieldPath.Child("upstream"), upstreamNames)...) - - return allErrs -} - const matchValueFmt string = `([^"\\]|\\.)*` const matchValueErrMsg string = `a valid value must have all '"' (double quotes) escaped and must not end with an unescaped '\' (backslash)` diff --git a/pkg/apis/configuration/validation/validation_test.go b/pkg/apis/configuration/validation/validation_test.go index a754420e4f..55289b8f5f 100644 --- a/pkg/apis/configuration/validation/validation_test.go +++ b/pkg/apis/configuration/validation/validation_test.go @@ -38,12 +38,16 @@ func TestValidateVirtualServer(t *testing.T) { }, Routes: []v1alpha1.Route{ { - Path: "/first", - Upstream: "first", + Path: "/first", + Action: &v1alpha1.Action{ + Pass: "first", + }, }, { - Path: "/second", - Upstream: "second", + Path: "/second", + Action: &v1alpha1.Action{ + Pass: "second", + }, }, }, }, @@ -475,8 +479,10 @@ func TestValidateVirtualServerRoutes(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "/", - Upstream: "test", + Path: "/", + Action: &v1alpha1.Action{ + Pass: "test", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -503,12 +509,16 @@ func TestValidateVirtualServerRoutesFails(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "/test", - Upstream: "test-1", + Path: "/test", + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Path: "/test", - Upstream: "test-2", + Path: "/test", + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -521,8 +531,8 @@ func TestValidateVirtualServerRoutesFails(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "", - Upstream: "", + Path: "", + Action: nil, }, }, upstreamNames: map[string]sets.Empty{}, @@ -548,8 +558,10 @@ func TestValidateRoute(t *testing.T) { { route: v1alpha1.Route{ - Path: "/", - Upstream: "test", + Path: "/", + Action: &v1alpha1.Action{ + Pass: "test", + }, }, upstreamNames: map[string]sets.Empty{ "test": {}, @@ -562,12 +574,16 @@ func TestValidateRoute(t *testing.T) { Path: "/", Splits: []v1alpha1.Split{ { - Weight: 90, - Upstream: "test-1", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Weight: 10, - Upstream: "test-2", + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, }, @@ -581,21 +597,21 @@ func TestValidateRoute(t *testing.T) { { route: v1alpha1.Route{ Path: "/", - Rules: &v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", - }, - }, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", + Matches: []v1alpha1.Match{ + { + Conditions: []v1alpha1.Condition{ + { + Header: "x-version", + Value: "test-1", }, - Upstream: "test-1", + }, + Action: &v1alpha1.Action{ + Pass: "test-1", }, }, - DefaultUpstream: "test-2", + }, + Action: &v1alpha1.Action{ + Pass: "test-2", }, }, upstreamNames: map[string]sets.Empty{ @@ -603,7 +619,7 @@ func TestValidateRoute(t *testing.T) { "test-2": {}, }, isRouteFieldForbidden: false, - msg: "valid upstream with rules", + msg: "valid action with matches", }, { route: v1alpha1.Route{ @@ -634,8 +650,10 @@ func TestValidateRouteFails(t *testing.T) { }{ { route: v1alpha1.Route{ - Path: "", - Upstream: "test", + Path: "", + Action: &v1alpha1.Action{ + Pass: "test", + }, }, upstreamNames: map[string]sets.Empty{ "test": {}, @@ -645,64 +663,45 @@ func TestValidateRouteFails(t *testing.T) { }, { route: v1alpha1.Route{ - Path: "/test", - Upstream: "-test", + Path: "/test", + Action: &v1alpha1.Action{ + Pass: "-test", + }, }, upstreamNames: sets.String{}, isRouteFieldForbidden: false, - msg: "invalid upstream", + msg: "invalid pass action", }, { route: v1alpha1.Route{ - Path: "/", - Upstream: "test", + Path: "/", + Action: &v1alpha1.Action{ + Pass: "test", + }, }, upstreamNames: sets.String{}, isRouteFieldForbidden: false, - msg: "non-existing upstream", + msg: "non-existing upstream in pass action", }, { route: v1alpha1.Route{ - Path: "/", - Upstream: "test", + Path: "/", + Action: &v1alpha1.Action{ + Pass: "test", + }, Splits: []v1alpha1.Split{ { - Weight: 90, - Upstream: "test-1", - }, - { - Weight: 10, - Upstream: "test-2", - }, - }, - }, - upstreamNames: map[string]sets.Empty{ - "test": {}, - "test-1": {}, - "test-2": {}, - }, - isRouteFieldForbidden: false, - msg: "both upstream and splits exist", - }, - { - route: v1alpha1.Route{ - Path: "/", - Upstream: "test", - Rules: &v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", }, }, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", - }, - Upstream: "test-1", + { + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", }, }, - DefaultUpstream: "test-2", }, }, upstreamNames: map[string]sets.Empty{ @@ -711,36 +710,40 @@ func TestValidateRouteFails(t *testing.T) { "test-2": {}, }, isRouteFieldForbidden: false, - msg: "both upstream and rules exist", + msg: "both action and splits exist", }, { route: v1alpha1.Route{ Path: "/", Splits: []v1alpha1.Split{ { - Weight: 90, - Upstream: "test-1", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Weight: 10, - Upstream: "test-2", - }, - }, - Rules: &v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", }, }, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", + }, + Matches: []v1alpha1.Match{ + { + Conditions: []v1alpha1.Condition{ + { + Header: "x-version", + Value: "test-1", }, - Upstream: "test-1", + }, + Action: &v1alpha1.Action{ + Pass: "test-1", }, }, - DefaultUpstream: "test-2", + }, + Action: &v1alpha1.Action{ + Pass: "test-2", }, }, upstreamNames: map[string]sets.Empty{ @@ -748,7 +751,7 @@ func TestValidateRouteFails(t *testing.T) { "test-2": {}, }, isRouteFieldForbidden: false, - msg: "both splits and rules exist", + msg: "both splits and matches exist", }, { route: v1alpha1.Route{ @@ -769,6 +772,30 @@ func TestValidateRouteFails(t *testing.T) { } } +func TestValidateAction(t *testing.T) { + action := &v1alpha1.Action{ + Pass: "test", + } + upstreamNames := map[string]sets.Empty{ + "test": {}, + } + + allErrs := validateAction(action, field.NewPath("action"), upstreamNames) + if len(allErrs) > 0 { + t.Errorf("validateAction() returned errors %v for valid input", allErrs) + } +} + +func TestValidateActionFails(t *testing.T) { + action := &v1alpha1.Action{} + upstreamNames := map[string]sets.Empty{} + + allErrs := validateAction(action, field.NewPath("action"), upstreamNames) + if len(allErrs) == 0 { + t.Error("validateAction() returned no errors for invalid input") + } +} + func TestValidateRouteField(t *testing.T) { validRouteFields := []string{ "coffee", @@ -873,12 +900,16 @@ func TestValidatePath(t *testing.T) { func TestValidateSplits(t *testing.T) { splits := []v1alpha1.Split{ { - Weight: 90, - Upstream: "test-1", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Weight: 10, - Upstream: "test-2", + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, } upstreamNames := map[string]sets.Empty{ @@ -901,8 +932,10 @@ func TestValidateSplitsFails(t *testing.T) { { splits: []v1alpha1.Split{ { - Weight: 90, - Upstream: "test-1", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -913,12 +946,16 @@ func TestValidateSplitsFails(t *testing.T) { { splits: []v1alpha1.Split{ { - Weight: 123, - Upstream: "test-1", + Weight: 123, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Weight: 10, - Upstream: "test-2", + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -930,12 +967,16 @@ func TestValidateSplitsFails(t *testing.T) { { splits: []v1alpha1.Split{ { - Weight: 99, - Upstream: "test-1", + Weight: 99, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Weight: 99, - Upstream: "test-2", + Weight: 99, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -947,36 +988,44 @@ func TestValidateSplitsFails(t *testing.T) { { splits: []v1alpha1.Split{ { - Weight: 90, - Upstream: "", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "", + }, }, { - Weight: 10, - Upstream: "test-2", + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, upstreamNames: map[string]sets.Empty{ "test-1": {}, "test-2": {}, }, - msg: "invalid upstream", + msg: "invalid action", }, { splits: []v1alpha1.Split{ { - Weight: 90, - Upstream: "some-upstream", + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "some-upstream", + }, }, { - Weight: 10, - Upstream: "test-2", + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, upstreamNames: map[string]sets.Empty{ "test-1": {}, "test-2": {}, }, - msg: "non-existing upstream", + msg: "invalid action with non-existing upstream", }, } @@ -988,131 +1037,6 @@ func TestValidateSplitsFails(t *testing.T) { } } -func TestValidateRules(t *testing.T) { - rules := v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", - }, - }, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", - }, - Upstream: "test-1", - }, - }, - DefaultUpstream: "test-2", - } - - upstreamNames := map[string]sets.Empty{ - "test-1": {}, - "test-2": {}, - } - - allErrs := validateRules(&rules, field.NewPath("rules"), upstreamNames) - if len(allErrs) > 0 { - t.Errorf("validateRules() returned errors %v for valid input", allErrs) - } -} - -func TestValidateRulesFails(t *testing.T) { - tests := []struct { - rules v1alpha1.Rules - upstreamNames sets.String - msg string - }{ - { - rules: v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{}, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", - }, - Upstream: "test-1", - }, - }, - DefaultUpstream: "test-2", - }, - upstreamNames: map[string]sets.Empty{ - "test-1": {}, - "test-2": {}, - }, - msg: "no conditions", - }, - { - rules: v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", - }, - }, - Matches: []v1alpha1.Match{}, - DefaultUpstream: "test-2", - }, - upstreamNames: map[string]sets.Empty{ - "test-2": {}, - }, - msg: "no matches", - }, - { - rules: v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", - }, - }, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", - }, - Upstream: "test-1", - }, - }, - DefaultUpstream: "", - }, - upstreamNames: map[string]sets.Empty{ - "test-1": {}, - }, - msg: "no default upstream", - }, - { - rules: v1alpha1.Rules{ - Conditions: []v1alpha1.Condition{ - { - Header: "x-version", - Cookie: "user", - }, - }, - Matches: []v1alpha1.Match{ - { - Values: []string{ - "test-1", - }, - Upstream: "test-1", - }, - }, - DefaultUpstream: "test", - }, - upstreamNames: map[string]sets.Empty{ - "test-1": {}, - "test": {}, - }, - msg: "invalid values in a match", - }, - } - - for _, test := range tests { - allErrs := validateRules(&test.rules, field.NewPath("rules"), test.upstreamNames) - if len(allErrs) == 0 { - t.Errorf("validateRules() returned no errors for invalid input for the case of %s", test.msg) - } - } -} - func TestValidateCondition(t *testing.T) { tests := []struct { condition v1alpha1.Condition @@ -1121,24 +1045,28 @@ func TestValidateCondition(t *testing.T) { { condition: v1alpha1.Condition{ Header: "x-version", + Value: "v1", }, msg: "valid header", }, { condition: v1alpha1.Condition{ Cookie: "my_cookie", + Value: "", }, msg: "valid cookie", }, { condition: v1alpha1.Condition{ Argument: "arg", + Value: "yes", }, msg: "valid argument", }, { condition: v1alpha1.Condition{ Variable: "$request_method", + Value: "POST", }, msg: "valid variable", }, @@ -1167,6 +1095,7 @@ func TestValidateConditionFails(t *testing.T) { Cookie: "user", Argument: "answer", Variable: "$request_method", + Value: "something", }, msg: "invalid condition", }, @@ -1284,70 +1213,151 @@ func TestValidateVariableName(t *testing.T) { } func TestValidateMatch(t *testing.T) { - match := v1alpha1.Match{ - Values: []string{ - "value1", - "value2", + tests := []struct { + match v1alpha1.Match + upstreamNames sets.String + msg string + }{ + { + match: v1alpha1.Match{ + Conditions: []v1alpha1.Condition{ + { + Cookie: "version", + Value: "v1", + }, + }, + Action: &v1alpha1.Action{ + Pass: "test", + }, + }, + upstreamNames: map[string]sets.Empty{ + "test": {}, + }, + msg: "valid match with action", + }, + { + match: v1alpha1.Match{ + Conditions: []v1alpha1.Condition{ + { + Cookie: "version", + Value: "v1", + }, + }, + Splits: []v1alpha1.Split{ + { + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, + }, + { + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, + }, + }, + }, + upstreamNames: map[string]sets.Empty{ + "test-1": {}, + "test-2": {}, + }, + msg: "valid match with splits", }, - Upstream: "test", - } - conditionsCount := 2 - upstreamNames := map[string]sets.Empty{ - "test": {}, } - allErrs := validateMatch(match, field.NewPath("match"), conditionsCount, upstreamNames) - if len(allErrs) > 0 { - t.Errorf("validateMatch() returned errors %v for valid input", allErrs) + for _, test := range tests { + allErrs := validateMatch(test.match, field.NewPath("match"), test.upstreamNames) + if len(allErrs) > 0 { + t.Errorf("validateMatch() returned errors %v for valid input for the case of %s", allErrs, test.msg) + } } } func TestValidateMatchFails(t *testing.T) { tests := []struct { - match v1alpha1.Match - conditionsCount int - upstreamNames sets.String - msg string + match v1alpha1.Match + upstreamNames sets.String + msg string }{ { match: v1alpha1.Match{ - Values: []string{}, - Upstream: "test", + Conditions: []v1alpha1.Condition{}, + Action: &v1alpha1.Action{ + Pass: "test", + }, }, - conditionsCount: 1, upstreamNames: map[string]sets.Empty{ "test": {}, }, - msg: "invalid number of values", + msg: "invalid number of conditions", }, { match: v1alpha1.Match{ - Values: []string{ - `abc"`, + Conditions: []v1alpha1.Condition{ + { + Cookie: "version", + Value: `v1"`, + }, + }, + Action: &v1alpha1.Action{ + Pass: "test", }, - Upstream: "test", }, - conditionsCount: 1, upstreamNames: map[string]sets.Empty{ "test": {}, }, - msg: "invalid value", + msg: "invalid condition", }, { match: v1alpha1.Match{ - Values: []string{ - "value", + Conditions: []v1alpha1.Condition{ + { + Cookie: "version", + Value: "v1", + }, }, - Upstream: "-invalid", + Action: &v1alpha1.Action{}, }, - conditionsCount: 1, - upstreamNames: map[string]sets.Empty{}, - msg: "invalid upstream", + upstreamNames: map[string]sets.Empty{}, + msg: "invalid action", + }, + { + match: v1alpha1.Match{ + Conditions: []v1alpha1.Condition{ + { + Cookie: "version", + Value: "v1", + }, + }, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, + Splits: []v1alpha1.Split{ + { + Weight: 90, + Action: &v1alpha1.Action{ + Pass: "test-1", + }, + }, + { + Weight: 10, + Action: &v1alpha1.Action{ + Pass: "test-2", + }, + }, + }, + }, + upstreamNames: map[string]sets.Empty{ + "test-1": {}, + "test-2": {}, + }, + msg: "both splits and action are set", }, } for _, test := range tests { - allErrs := validateMatch(test.match, field.NewPath("match"), test.conditionsCount, test.upstreamNames) + allErrs := validateMatch(test.match, field.NewPath("match"), test.upstreamNames) if len(allErrs) == 0 { t.Errorf("validateMatch() returned no errors for invalid input for the case of %s", test.msg) } @@ -1408,12 +1418,16 @@ func TestValidateVirtualServerRoute(t *testing.T) { }, Subroutes: []v1alpha1.Route{ { - Path: "/test/first", - Upstream: "first", + Path: "/test/first", + Action: &v1alpha1.Action{ + Pass: "first", + }, }, { - Path: "/test/second", - Upstream: "second", + Path: "/test/second", + Action: &v1alpha1.Action{ + Pass: "second", + }, }, }, }, @@ -1447,12 +1461,16 @@ func TestValidateVirtualServerRouteForVirtualServer(t *testing.T) { }, Subroutes: []v1alpha1.Route{ { - Path: "/test/first", - Upstream: "first", + Path: "/test/first", + Action: &v1alpha1.Action{ + Pass: "first", + }, }, { - Path: "/test/second", - Upstream: "second", + Path: "/test/second", + Action: &v1alpha1.Action{ + Pass: "second", + }, }, }, }, @@ -1501,8 +1519,10 @@ func TestValidateVirtualServerRouteSubroutes(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "/", - Upstream: "test", + Path: "/", + Action: &v1alpha1.Action{ + Pass: "test", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -1531,12 +1551,16 @@ func TestValidateVirtualServerRouteSubroutesFails(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "/test", - Upstream: "test-1", + Path: "/test", + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, { - Path: "/test", - Upstream: "test-2", + Path: "/test", + Action: &v1alpha1.Action{ + Pass: "test-2", + }, }, }, upstreamNames: map[string]sets.Empty{ @@ -1549,8 +1573,8 @@ func TestValidateVirtualServerRouteSubroutesFails(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "", - Upstream: "", + Path: "", + Action: nil, }, }, upstreamNames: map[string]sets.Empty{}, @@ -1560,8 +1584,10 @@ func TestValidateVirtualServerRouteSubroutesFails(t *testing.T) { { routes: []v1alpha1.Route{ { - Path: "/", - Upstream: "test-1", + Path: "/", + Action: &v1alpha1.Action{ + Pass: "test-1", + }, }, }, upstreamNames: map[string]sets.Empty{ From 103022480b48a98a287926d9e3374f46238a1bff Mon Sep 17 00:00:00 2001 From: tellet Date: Wed, 23 Oct 2019 13:48:14 +0100 Subject: [PATCH 2/2] Update tests according to the improved routing rules. Add tests for VSR split traffic and advanced routing. Add tests for focused canary deployment. --- .../standard/virtual-server.yaml | 25 +-- .../virtual-server-argument.yaml | 25 +-- .../virtual-server-complex.yaml | 40 +++-- .../virtual-server-cookie.yaml | 25 +-- .../virtual-server-variable.yaml | 25 +-- .../standard/virtual-server.yaml | 6 +- .../standard/virtual-server.yaml | 6 +- .../standard/virtual-server.yaml | 6 +- .../standard/virtual-server.yaml | 31 ++++ .../standard/virtual-server.yaml | 9 + .../virtual-server-route-argument.yaml | 37 ++++ .../virtual-server-route-complex.yaml | 49 ++++++ .../virtual-server-route-cookie.yaml | 37 ++++ .../virtual-server-route-header.yaml | 37 ++++ .../virtual-server-route-variable.yaml | 37 ++++ .../route-multiple.yaml | 6 +- .../route-single.yaml | 3 +- .../route-single.yaml | 3 +- .../standard/virtual-server.yaml | 9 + .../virtual-server-route.yaml | 28 +++ .../route-multiple.yaml | 25 +++ .../route-single.yaml | 14 ++ .../standard/virtual-server.yaml | 11 ++ .../plus-route-m-invalid-keys.yaml | 6 +- .../plus-route-s-invalid-keys.yaml | 3 +- .../route-multiple-invalid-keys.yaml | 6 +- .../route-multiple.yaml | 6 +- .../route-single-invalid-keys.yaml | 3 +- .../route-single.yaml | 3 +- .../route-multiple.yaml | 6 +- .../route-single-disable-tls.yaml | 3 +- .../route-single-invalid.yaml | 3 +- .../route-single.yaml | 3 +- .../route-multiple-updated.yaml | 6 +- .../virtual-server-route/route-multiple.yaml | 6 +- .../virtual-server-route/route-orphan.yaml | 3 +- .../route-single-duplicate-path.yaml | 6 +- .../route-single-invalid-host.yaml | 3 +- .../virtual-server-route/route-single.yaml | 3 +- .../standard/virtual-server.yaml | 9 +- .../standard/virtual-server.yaml | 6 +- ...plus-virtual-server-with-invalid-keys.yaml | 6 +- .../standard/virtual-server.yaml | 6 +- .../virtual-server-with-invalid-keys.yaml | 6 +- .../standard/virtual-server.yaml | 6 +- .../virtual-server-disable-tls.yaml | 6 +- .../virtual-server-invalid.yaml | 6 +- .../standard/virtual-server.yaml | 25 +-- ...aml => virtual-server-invalid-cookie.yaml} | 25 +-- ... => virtual-server-no-default-action.yaml} | 25 +-- .../standard/virtual-server-updated.yaml | 6 +- .../standard/virtual-server.yaml | 6 +- .../suite/test_v_s_route_advanced_routing.py | 162 ++++++++++++++++++ tests/suite/test_v_s_route_focused_canary.py | 138 +++++++++++++++ tests/suite/test_v_s_route_split_traffic.py | 68 ++++++++ .../test_virtual_server_focused_canary.py | 66 +++++++ .../test_virtual_server_split_traffic.py | 2 +- tests/suite/test_virtual_server_validation.py | 12 +- 58 files changed, 995 insertions(+), 154 deletions(-) create mode 100644 tests/data/virtual-server-focused-canary/standard/virtual-server.yaml create mode 100644 tests/data/virtual-server-route-advanced-routing/standard/virtual-server.yaml create mode 100644 tests/data/virtual-server-route-advanced-routing/virtual-server-route-argument.yaml create mode 100644 tests/data/virtual-server-route-advanced-routing/virtual-server-route-complex.yaml create mode 100644 tests/data/virtual-server-route-advanced-routing/virtual-server-route-cookie.yaml create mode 100644 tests/data/virtual-server-route-advanced-routing/virtual-server-route-header.yaml create mode 100644 tests/data/virtual-server-route-advanced-routing/virtual-server-route-variable.yaml create mode 100644 tests/data/virtual-server-route-focused-canary/standard/virtual-server.yaml create mode 100644 tests/data/virtual-server-route-focused-canary/virtual-server-route.yaml create mode 100644 tests/data/virtual-server-route-split-traffic/route-multiple.yaml create mode 100644 tests/data/virtual-server-route-split-traffic/route-single.yaml create mode 100644 tests/data/virtual-server-route-split-traffic/standard/virtual-server.yaml rename tests/data/virtual-server-validation/{virtual-server-invalid-2.yaml => virtual-server-invalid-cookie.yaml} (64%) rename tests/data/virtual-server-validation/{virtual-server-invalid.yaml => virtual-server-no-default-action.yaml} (64%) create mode 100644 tests/suite/test_v_s_route_advanced_routing.py create mode 100644 tests/suite/test_v_s_route_focused_canary.py create mode 100644 tests/suite/test_v_s_route_split_traffic.py create mode 100644 tests/suite/test_virtual_server_focused_canary.py diff --git a/tests/data/virtual-server-advanced-routing/standard/virtual-server.yaml b/tests/data/virtual-server-advanced-routing/standard/virtual-server.yaml index 43e43da3d9..542db97018 100644 --- a/tests/data/virtual-server-advanced-routing/standard/virtual-server.yaml +++ b/tests/data/virtual-server-advanced-routing/standard/virtual-server.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - header: x-version - matches: - - values: - - future - upstream: backend1-future - - values: - - deprecated - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: future + action: + pass: backend1-future + - conditions: + - header: x-version + value: deprecated + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-advanced-routing/virtual-server-argument.yaml b/tests/data/virtual-server-advanced-routing/virtual-server-argument.yaml index 4d628bfa8d..36ca771173 100644 --- a/tests/data/virtual-server-advanced-routing/virtual-server-argument.yaml +++ b/tests/data/virtual-server-advanced-routing/virtual-server-argument.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - argument: arg1 - matches: - - values: - - v1 - upstream: backend1-future - - values: - - v2 - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: v1 + action: + pass: backend1-future + - conditions: + - argument: arg1 + value: v2 + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-advanced-routing/virtual-server-complex.yaml b/tests/data/virtual-server-advanced-routing/virtual-server-complex.yaml index 627328215c..0b060b046d 100644 --- a/tests/data/virtual-server-advanced-routing/virtual-server-complex.yaml +++ b/tests/data/virtual-server-advanced-routing/virtual-server-complex.yaml @@ -19,25 +19,31 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - header: x-version + value: future - cookie: user + value: some - argument: arg1 + value: v1 - variable: $request_method - matches: - - values: - - future - - some - - v1 - - get - upstream: backend1-future - - values: - - deprecated - - bad - - v2 - - post - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: get + action: + pass: backend1-future + - conditions: + - header: x-version + value: deprecated + - cookie: user + value: bad + - argument: arg1 + value: v2 + - variable: $request_method + value: post + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-advanced-routing/virtual-server-cookie.yaml b/tests/data/virtual-server-advanced-routing/virtual-server-cookie.yaml index 3c15e94be7..9c5bce1768 100644 --- a/tests/data/virtual-server-advanced-routing/virtual-server-cookie.yaml +++ b/tests/data/virtual-server-advanced-routing/virtual-server-cookie.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - cookie: user - matches: - - values: - - some - upstream: backend1-future - - values: - - bad - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: some + action: + pass: backend1-future + - conditions: + - cookie: user + value: bad + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-advanced-routing/virtual-server-variable.yaml b/tests/data/virtual-server-advanced-routing/virtual-server-variable.yaml index a0fcaffcdb..b180c84eea 100644 --- a/tests/data/virtual-server-advanced-routing/virtual-server-variable.yaml +++ b/tests/data/virtual-server-advanced-routing/virtual-server-variable.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - variable: $request_method - matches: - - values: - - get - upstream: backend1-future - - values: - - post - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: get + action: + pass: backend1-future + - conditions: + - variable: $request_method + value: post + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-configmap-keys/standard/virtual-server.yaml b/tests/data/virtual-server-configmap-keys/standard/virtual-server.yaml index b90218e07b..80c3f5cf39 100644 --- a/tests/data/virtual-server-configmap-keys/standard/virtual-server.yaml +++ b/tests/data/virtual-server-configmap-keys/standard/virtual-server.yaml @@ -13,6 +13,8 @@ spec: port: 80 routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-dynamic-configuration/standard/virtual-server.yaml b/tests/data/virtual-server-dynamic-configuration/standard/virtual-server.yaml index b595c605e3..209d3899ea 100644 --- a/tests/data/virtual-server-dynamic-configuration/standard/virtual-server.yaml +++ b/tests/data/virtual-server-dynamic-configuration/standard/virtual-server.yaml @@ -18,6 +18,8 @@ spec: port: 80 routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-externalname/standard/virtual-server.yaml b/tests/data/virtual-server-externalname/standard/virtual-server.yaml index ccc42af474..1e91c126d3 100644 --- a/tests/data/virtual-server-externalname/standard/virtual-server.yaml +++ b/tests/data/virtual-server-externalname/standard/virtual-server.yaml @@ -13,6 +13,8 @@ spec: port: 80 routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-focused-canary/standard/virtual-server.yaml b/tests/data/virtual-server-focused-canary/standard/virtual-server.yaml new file mode 100644 index 0000000000..aa7fa65410 --- /dev/null +++ b/tests/data/virtual-server-focused-canary/standard/virtual-server.yaml @@ -0,0 +1,31 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServer +metadata: + name: virtual-server-canary +spec: + host: virtual-server-canary.example.com + upstreams: + - name: backend1 + service: backend1-svc + port: 80 + - name: backend2 + service: backend2-svc + port: 80 + routes: + - path: "/backends/focused-canary" + matches: + - conditions: + - header: x-version + value: canary + splits: + - weight: 90 + action: + pass: backend1 + - weight: 10 + action: + pass: backend2 + action: + pass: backend1 + - path: "/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-advanced-routing/standard/virtual-server.yaml b/tests/data/virtual-server-route-advanced-routing/standard/virtual-server.yaml new file mode 100644 index 0000000000..0137551aa7 --- /dev/null +++ b/tests/data/virtual-server-route-advanced-routing/standard/virtual-server.yaml @@ -0,0 +1,9 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServer +metadata: + name: virtual-server-route +spec: + host: virtual-server-route.example.com + routes: + - path: "/backends" + route: backends-namespace/backends \ No newline at end of file diff --git a/tests/data/virtual-server-route-advanced-routing/virtual-server-route-argument.yaml b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-argument.yaml new file mode 100644 index 0000000000..456bbcfe5b --- /dev/null +++ b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-argument.yaml @@ -0,0 +1,37 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + - name: backend4-stable + service: backend4-stable-svc + port: 80 + - name: backend1-future + service: backend1-future-svc + port: 80 + - name: backend3-deprecated + service: backend3-deprecated-svc + port: 80 + subroutes: + - path: "/backends/backend1" + matches: + - conditions: + - argument: arg1 + value: v1 + action: + pass: backend1-future + - conditions: + - argument: arg1 + value: v2 + action: + pass: backend3-deprecated + action: + pass: backend4-stable + - path: "/backends/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-advanced-routing/virtual-server-route-complex.yaml b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-complex.yaml new file mode 100644 index 0000000000..4e820dcfbc --- /dev/null +++ b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-complex.yaml @@ -0,0 +1,49 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + - name: backend4-stable + service: backend4-stable-svc + port: 80 + - name: backend1-future + service: backend1-future-svc + port: 80 + - name: backend3-deprecated + service: backend3-deprecated-svc + port: 80 + subroutes: + - path: "/backends/backend1" + matches: + - conditions: + - header: x-version + value: future + - cookie: user + value: some + - argument: arg1 + value: v1 + - variable: $request_method + value: get + action: + pass: backend1-future + - conditions: + - header: x-version + value: deprecated + - cookie: user + value: bad + - argument: arg1 + value: v2 + - variable: $request_method + value: post + action: + pass: backend3-deprecated + action: + pass: backend4-stable + - path: "/backends/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-advanced-routing/virtual-server-route-cookie.yaml b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-cookie.yaml new file mode 100644 index 0000000000..9c1c167a97 --- /dev/null +++ b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-cookie.yaml @@ -0,0 +1,37 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + - name: backend4-stable + service: backend4-stable-svc + port: 80 + - name: backend1-future + service: backend1-future-svc + port: 80 + - name: backend3-deprecated + service: backend3-deprecated-svc + port: 80 + subroutes: + - path: "/backends/backend1" + matches: + - conditions: + - cookie: user + value: some + action: + pass: backend1-future + - conditions: + - cookie: user + value: bad + action: + pass: backend3-deprecated + action: + pass: backend4-stable + - path: "/backends/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-advanced-routing/virtual-server-route-header.yaml b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-header.yaml new file mode 100644 index 0000000000..3535b8ab01 --- /dev/null +++ b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-header.yaml @@ -0,0 +1,37 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + - name: backend4-stable + service: backend4-stable-svc + port: 80 + - name: backend1-future + service: backend1-future-svc + port: 80 + - name: backend3-deprecated + service: backend3-deprecated-svc + port: 80 + subroutes: + - path: "/backends/backend1" + matches: + - conditions: + - header: x-version + value: future + action: + pass: backend1-future + - conditions: + - header: x-version + value: deprecated + action: + pass: backend3-deprecated + action: + pass: backend4-stable + - path: "/backends/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-advanced-routing/virtual-server-route-variable.yaml b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-variable.yaml new file mode 100644 index 0000000000..225e13d610 --- /dev/null +++ b/tests/data/virtual-server-route-advanced-routing/virtual-server-route-variable.yaml @@ -0,0 +1,37 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + - name: backend4-stable + service: backend4-stable-svc + port: 80 + - name: backend1-future + service: backend1-future-svc + port: 80 + - name: backend3-deprecated + service: backend3-deprecated-svc + port: 80 + subroutes: + - path: "/backends/backend1" + matches: + - conditions: + - variable: $request_method + value: get + action: + pass: backend1-future + - conditions: + - variable: $request_method + value: post + action: + pass: backend3-deprecated + action: + pass: backend4-stable + - path: "/backends/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-dynamic-configuration/route-multiple.yaml b/tests/data/virtual-server-route-dynamic-configuration/route-multiple.yaml index 8cd8102b66..38a4977a40 100644 --- a/tests/data/virtual-server-route-dynamic-configuration/route-multiple.yaml +++ b/tests/data/virtual-server-route-dynamic-configuration/route-multiple.yaml @@ -18,6 +18,8 @@ spec: port: 80 subroutes: - path: "/backends/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route-dynamic-configuration/route-single.yaml b/tests/data/virtual-server-route-dynamic-configuration/route-single.yaml index 3f209dd1c6..0c9b89fb28 100644 --- a/tests/data/virtual-server-route-dynamic-configuration/route-single.yaml +++ b/tests/data/virtual-server-route-dynamic-configuration/route-single.yaml @@ -15,4 +15,5 @@ spec: slow-start: 10s subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-externalname/route-single.yaml b/tests/data/virtual-server-route-externalname/route-single.yaml index 7d73880b8b..0760e66194 100644 --- a/tests/data/virtual-server-route-externalname/route-single.yaml +++ b/tests/data/virtual-server-route-externalname/route-single.yaml @@ -10,4 +10,5 @@ spec: port: 80 subroutes: - path: "/external-backend" - upstream: ext-backend \ No newline at end of file + action: + pass: ext-backend \ No newline at end of file diff --git a/tests/data/virtual-server-route-focused-canary/standard/virtual-server.yaml b/tests/data/virtual-server-route-focused-canary/standard/virtual-server.yaml new file mode 100644 index 0000000000..0137551aa7 --- /dev/null +++ b/tests/data/virtual-server-route-focused-canary/standard/virtual-server.yaml @@ -0,0 +1,9 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServer +metadata: + name: virtual-server-route +spec: + host: virtual-server-route.example.com + routes: + - path: "/backends" + route: backends-namespace/backends \ No newline at end of file diff --git a/tests/data/virtual-server-route-focused-canary/virtual-server-route.yaml b/tests/data/virtual-server-route-focused-canary/virtual-server-route.yaml new file mode 100644 index 0000000000..c04f1c3972 --- /dev/null +++ b/tests/data/virtual-server-route-focused-canary/virtual-server-route.yaml @@ -0,0 +1,28 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend1 + service: backend1-svc + port: 80 + - name: backend2 + service: backend2-svc + port: 80 + subroutes: + - path: "/backends/focused-canary" + matches: + - conditions: + - header: x-version + value: canary + splits: + - weight: 90 + action: + pass: backend1 + - weight: 10 + action: + pass: backend2 + action: + pass: backend1 \ No newline at end of file diff --git a/tests/data/virtual-server-route-split-traffic/route-multiple.yaml b/tests/data/virtual-server-route-split-traffic/route-multiple.yaml new file mode 100644 index 0000000000..0544686ea0 --- /dev/null +++ b/tests/data/virtual-server-route-split-traffic/route-multiple.yaml @@ -0,0 +1,25 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backends +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend1 + service: backend1-svc + port: 80 + - name: backend3 + service: backend3-svc + port: 80 + subroutes: + - path: "/backends/backend1" + splits: + - weight: 90 + action: + pass: backend1 + - weight: 10 + action: + pass: backend3 + - path: "/backends/backend3" + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route-split-traffic/route-single.yaml b/tests/data/virtual-server-route-split-traffic/route-single.yaml new file mode 100644 index 0000000000..d33fd5f52f --- /dev/null +++ b/tests/data/virtual-server-route-split-traffic/route-single.yaml @@ -0,0 +1,14 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServerRoute +metadata: + name: backend2 +spec: + host: virtual-server-route.example.com + upstreams: + - name: backend2 + service: backend2-svc + port: 80 + subroutes: + - path: "/backend2" + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-split-traffic/standard/virtual-server.yaml b/tests/data/virtual-server-route-split-traffic/standard/virtual-server.yaml new file mode 100644 index 0000000000..36a354f3a6 --- /dev/null +++ b/tests/data/virtual-server-route-split-traffic/standard/virtual-server.yaml @@ -0,0 +1,11 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: VirtualServer +metadata: + name: virtual-server-route +spec: + host: virtual-server-route.example.com + routes: + - path: "/backends" + route: backends-namespace/backends + - path: "/backend2" + route: backend2-namespace/backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-options/plus-route-m-invalid-keys.yaml b/tests/data/virtual-server-route-upstream-options/plus-route-m-invalid-keys.yaml index 868028574a..2a4d0dadd1 100644 --- a/tests/data/virtual-server-route-upstream-options/plus-route-m-invalid-keys.yaml +++ b/tests/data/virtual-server-route-upstream-options/plus-route-m-invalid-keys.yaml @@ -60,6 +60,8 @@ spec: timeout: "hour" subroutes: - path: "/backends/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-options/plus-route-s-invalid-keys.yaml b/tests/data/virtual-server-route-upstream-options/plus-route-s-invalid-keys.yaml index 3a0275458f..26b8c6aa27 100644 --- a/tests/data/virtual-server-route-upstream-options/plus-route-s-invalid-keys.yaml +++ b/tests/data/virtual-server-route-upstream-options/plus-route-s-invalid-keys.yaml @@ -34,4 +34,5 @@ spec: timeout: "hour" subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-options/route-multiple-invalid-keys.yaml b/tests/data/virtual-server-route-upstream-options/route-multiple-invalid-keys.yaml index 06ddb14372..cd8dd77423 100644 --- a/tests/data/virtual-server-route-upstream-options/route-multiple-invalid-keys.yaml +++ b/tests/data/virtual-server-route-upstream-options/route-multiple-invalid-keys.yaml @@ -45,6 +45,8 @@ spec: buffer-size: "k" subroutes: - path: "/backends/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-options/route-multiple.yaml b/tests/data/virtual-server-route-upstream-options/route-multiple.yaml index 4e5c296b37..5b17305b46 100644 --- a/tests/data/virtual-server-route-upstream-options/route-multiple.yaml +++ b/tests/data/virtual-server-route-upstream-options/route-multiple.yaml @@ -13,6 +13,8 @@ spec: port: 80 subroutes: - path: "/backends/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-options/route-single-invalid-keys.yaml b/tests/data/virtual-server-route-upstream-options/route-single-invalid-keys.yaml index 57819fe327..87402a01f4 100644 --- a/tests/data/virtual-server-route-upstream-options/route-single-invalid-keys.yaml +++ b/tests/data/virtual-server-route-upstream-options/route-single-invalid-keys.yaml @@ -26,4 +26,5 @@ spec: buffer-size: "one" subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-options/route-single.yaml b/tests/data/virtual-server-route-upstream-options/route-single.yaml index 346498922b..d33fd5f52f 100644 --- a/tests/data/virtual-server-route-upstream-options/route-single.yaml +++ b/tests/data/virtual-server-route-upstream-options/route-single.yaml @@ -10,4 +10,5 @@ spec: port: 80 subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-tls/route-multiple.yaml b/tests/data/virtual-server-route-upstream-tls/route-multiple.yaml index 1b877482c7..64ce605246 100644 --- a/tests/data/virtual-server-route-upstream-tls/route-multiple.yaml +++ b/tests/data/virtual-server-route-upstream-tls/route-multiple.yaml @@ -17,6 +17,8 @@ spec: enable: False subroutes: - path: "/backends/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-tls/route-single-disable-tls.yaml b/tests/data/virtual-server-route-upstream-tls/route-single-disable-tls.yaml index 90cb1c608f..f35907978b 100644 --- a/tests/data/virtual-server-route-upstream-tls/route-single-disable-tls.yaml +++ b/tests/data/virtual-server-route-upstream-tls/route-single-disable-tls.yaml @@ -12,4 +12,5 @@ spec: enable: subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-tls/route-single-invalid.yaml b/tests/data/virtual-server-route-upstream-tls/route-single-invalid.yaml index dd9d5a704c..50e6d975ff 100644 --- a/tests/data/virtual-server-route-upstream-tls/route-single-invalid.yaml +++ b/tests/data/virtual-server-route-upstream-tls/route-single-invalid.yaml @@ -12,4 +12,5 @@ spec: enable: "" subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route-upstream-tls/route-single.yaml b/tests/data/virtual-server-route-upstream-tls/route-single.yaml index 462920b194..8d0b8f53ca 100644 --- a/tests/data/virtual-server-route-upstream-tls/route-single.yaml +++ b/tests/data/virtual-server-route-upstream-tls/route-single.yaml @@ -12,4 +12,5 @@ spec: enable: True subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route/route-multiple-updated.yaml b/tests/data/virtual-server-route/route-multiple-updated.yaml index 2875b3ec78..f89b076e24 100644 --- a/tests/data/virtual-server-route/route-multiple-updated.yaml +++ b/tests/data/virtual-server-route/route-multiple-updated.yaml @@ -13,6 +13,8 @@ spec: port: 80 subroutes: - path: "/backends/updated-backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/updated-backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route/route-multiple.yaml b/tests/data/virtual-server-route/route-multiple.yaml index 4e5c296b37..5b17305b46 100644 --- a/tests/data/virtual-server-route/route-multiple.yaml +++ b/tests/data/virtual-server-route/route-multiple.yaml @@ -13,6 +13,8 @@ spec: port: 80 subroutes: - path: "/backends/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backends/backend3" - upstream: backend3 \ No newline at end of file + action: + pass: backend3 \ No newline at end of file diff --git a/tests/data/virtual-server-route/route-orphan.yaml b/tests/data/virtual-server-route/route-orphan.yaml index 422f07c251..04155326c9 100644 --- a/tests/data/virtual-server-route/route-orphan.yaml +++ b/tests/data/virtual-server-route/route-orphan.yaml @@ -10,4 +10,5 @@ spec: port: 80 subroutes: - path: "/alone-backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route/route-single-duplicate-path.yaml b/tests/data/virtual-server-route/route-single-duplicate-path.yaml index 3d4216693e..be867c84b0 100644 --- a/tests/data/virtual-server-route/route-single-duplicate-path.yaml +++ b/tests/data/virtual-server-route/route-single-duplicate-path.yaml @@ -10,6 +10,8 @@ spec: port: 80 subroutes: - path: "/backend2" - upstream: backend2 + action: + pass: backend2 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route/route-single-invalid-host.yaml b/tests/data/virtual-server-route/route-single-invalid-host.yaml index 6922943658..e56860da42 100644 --- a/tests/data/virtual-server-route/route-single-invalid-host.yaml +++ b/tests/data/virtual-server-route/route-single-invalid-host.yaml @@ -10,4 +10,5 @@ spec: port: 80 subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-route/route-single.yaml b/tests/data/virtual-server-route/route-single.yaml index 346498922b..d33fd5f52f 100644 --- a/tests/data/virtual-server-route/route-single.yaml +++ b/tests/data/virtual-server-route/route-single.yaml @@ -10,4 +10,5 @@ spec: port: 80 subroutes: - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-split-traffic/standard/virtual-server.yaml b/tests/data/virtual-server-split-traffic/standard/virtual-server.yaml index 34a4e39dc5..e1de6fea5a 100644 --- a/tests/data/virtual-server-split-traffic/standard/virtual-server.yaml +++ b/tests/data/virtual-server-split-traffic/standard/virtual-server.yaml @@ -18,8 +18,11 @@ spec: - path: "/backends" splits: - weight: 90 - upstream: backend1-v1 + action: + pass: backend1-v1 - weight: 10 - upstream: backend1-v2 + action: + pass: backend1-v2 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-tls/standard/virtual-server.yaml b/tests/data/virtual-server-tls/standard/virtual-server.yaml index d845945a43..b130f2e6dc 100644 --- a/tests/data/virtual-server-tls/standard/virtual-server.yaml +++ b/tests/data/virtual-server-tls/standard/virtual-server.yaml @@ -15,6 +15,8 @@ spec: port: 80 routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-upstream-options/plus-virtual-server-with-invalid-keys.yaml b/tests/data/virtual-server-upstream-options/plus-virtual-server-with-invalid-keys.yaml index c0a3f71047..69b07c7898 100644 --- a/tests/data/virtual-server-upstream-options/plus-virtual-server-with-invalid-keys.yaml +++ b/tests/data/virtual-server-upstream-options/plus-virtual-server-with-invalid-keys.yaml @@ -61,6 +61,8 @@ spec: timeout: "hour" routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-upstream-options/standard/virtual-server.yaml b/tests/data/virtual-server-upstream-options/standard/virtual-server.yaml index ffe0c20315..04048c4696 100644 --- a/tests/data/virtual-server-upstream-options/standard/virtual-server.yaml +++ b/tests/data/virtual-server-upstream-options/standard/virtual-server.yaml @@ -13,6 +13,8 @@ spec: port: 80 routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-upstream-options/virtual-server-with-invalid-keys.yaml b/tests/data/virtual-server-upstream-options/virtual-server-with-invalid-keys.yaml index 3412f53e82..85d9153e48 100644 --- a/tests/data/virtual-server-upstream-options/virtual-server-with-invalid-keys.yaml +++ b/tests/data/virtual-server-upstream-options/virtual-server-with-invalid-keys.yaml @@ -45,6 +45,8 @@ spec: buffer-size: "k" routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-upstream-tls/standard/virtual-server.yaml b/tests/data/virtual-server-upstream-tls/standard/virtual-server.yaml index 130e636c8f..7dad641bf9 100644 --- a/tests/data/virtual-server-upstream-tls/standard/virtual-server.yaml +++ b/tests/data/virtual-server-upstream-tls/standard/virtual-server.yaml @@ -17,6 +17,8 @@ spec: enable: False routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-upstream-tls/virtual-server-disable-tls.yaml b/tests/data/virtual-server-upstream-tls/virtual-server-disable-tls.yaml index 63750305f4..fc580d9af4 100644 --- a/tests/data/virtual-server-upstream-tls/virtual-server-disable-tls.yaml +++ b/tests/data/virtual-server-upstream-tls/virtual-server-disable-tls.yaml @@ -17,6 +17,8 @@ spec: enable: False routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-upstream-tls/virtual-server-invalid.yaml b/tests/data/virtual-server-upstream-tls/virtual-server-invalid.yaml index bcb0a772a8..c4f4094973 100644 --- a/tests/data/virtual-server-upstream-tls/virtual-server-invalid.yaml +++ b/tests/data/virtual-server-upstream-tls/virtual-server-invalid.yaml @@ -17,6 +17,8 @@ spec: enable: False routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-validation/standard/virtual-server.yaml b/tests/data/virtual-server-validation/standard/virtual-server.yaml index 41d4b20060..0347ee97b3 100644 --- a/tests/data/virtual-server-validation/standard/virtual-server.yaml +++ b/tests/data/virtual-server-validation/standard/virtual-server.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - header: x-version - matches: - - values: - - future - upstream: backend1-future - - values: - - deprecated - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: future + action: + pass: backend1-future + - conditions: + - header: x-version + value: deprecated + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-validation/virtual-server-invalid-2.yaml b/tests/data/virtual-server-validation/virtual-server-invalid-cookie.yaml similarity index 64% rename from tests/data/virtual-server-validation/virtual-server-invalid-2.yaml rename to tests/data/virtual-server-validation/virtual-server-invalid-cookie.yaml index c15599e575..16721d833f 100644 --- a/tests/data/virtual-server-validation/virtual-server-invalid-2.yaml +++ b/tests/data/virtual-server-validation/virtual-server-invalid-cookie.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: + - cookie: x-version + value: future + action: + pass: backend1-future + - conditions: - header: x-version - matches: - - values: - - future - upstream: backend1-future - - values: - - deprecated - upstream: backend3-deprecated -# defaultUpstream: backend1-stable + value: deprecated + action: + pass: backend3-deprecated + action: + pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server-validation/virtual-server-invalid.yaml b/tests/data/virtual-server-validation/virtual-server-no-default-action.yaml similarity index 64% rename from tests/data/virtual-server-validation/virtual-server-invalid.yaml rename to tests/data/virtual-server-validation/virtual-server-no-default-action.yaml index 20033f10f7..cbfb16c86f 100644 --- a/tests/data/virtual-server-validation/virtual-server-invalid.yaml +++ b/tests/data/virtual-server-validation/virtual-server-no-default-action.yaml @@ -19,16 +19,19 @@ spec: port: 80 routes: - path: "/backends" - rules: - conditions: + matches: + - conditions: - header: x-version - matches: - - values: -# - future - upstream: backend1-future - - values: - - deprecated - upstream: backend3-deprecated - defaultUpstream: backend4-stable + value: future + action: + pass: backend1-future + - conditions: + - header: x-version + value: deprecated + action: + pass: backend3-deprecated +# action: +# pass: backend4-stable - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server/standard/virtual-server-updated.yaml b/tests/data/virtual-server/standard/virtual-server-updated.yaml index 96f1e3adb4..0383fe0e29 100644 --- a/tests/data/virtual-server/standard/virtual-server-updated.yaml +++ b/tests/data/virtual-server/standard/virtual-server-updated.yaml @@ -13,6 +13,8 @@ spec: port: 80 routes: - path: "/updated-backend2" - upstream: backend2 + action: + pass: backend1 - path: "/updated-backend1" - upstream: backend1 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/data/virtual-server/standard/virtual-server.yaml b/tests/data/virtual-server/standard/virtual-server.yaml index ffe0c20315..04048c4696 100644 --- a/tests/data/virtual-server/standard/virtual-server.yaml +++ b/tests/data/virtual-server/standard/virtual-server.yaml @@ -13,6 +13,8 @@ spec: port: 80 routes: - path: "/backend1" - upstream: backend1 + action: + pass: backend1 - path: "/backend2" - upstream: backend2 \ No newline at end of file + action: + pass: backend2 \ No newline at end of file diff --git a/tests/suite/test_v_s_route_advanced_routing.py b/tests/suite/test_v_s_route_advanced_routing.py new file mode 100644 index 0000000000..12bc7b0de1 --- /dev/null +++ b/tests/suite/test_v_s_route_advanced_routing.py @@ -0,0 +1,162 @@ +import pytest +import requests + +from settings import TEST_DATA +from suite.custom_resources_utils import create_virtual_server_from_yaml, create_v_s_route_from_yaml, \ + patch_v_s_route_from_yaml +from suite.fixtures import VirtualServerRoute +from suite.resources_utils import wait_before_test, ensure_response_from_backend, create_example_app, \ + wait_until_all_pods_are_ready, create_namespace_with_name_from_yaml, delete_namespace +from suite.yaml_utils import get_paths_from_vsr_yaml, get_first_vs_host_from_yaml, get_route_namespace_from_vs_yaml + + +def execute_assertions(resp_1, resp_2, resp_3): + assert resp_1.status_code == 200 + assert "Server name: backend1-" in resp_1.text, "Expected response from backend1" + assert resp_2.status_code == 200 + assert "Server name: backend3-" in resp_2.text, "Expected response from backend3" + assert resp_3.status_code == 200 + assert "Server name: backend4-" in resp_3.text, "Expected response from backend4" + + +class VSRAdvancedRoutingSetup: + """ + Encapsulate advanced routing VSR example details. + + Attributes: + namespace (str): + vs_host (str): + vs_name (str): + route (VirtualServerRoute): + backends_url (str): backend url + """ + + def __init__(self, namespace, vs_host, vs_name, route: VirtualServerRoute, backends_url): + self.namespace = namespace + self.vs_host = vs_host + self.vs_name = vs_name + self.route = route + self.backends_url = backends_url + + +@pytest.fixture(scope="class") +def vsr_adv_routing_setup(request, kube_apis, + ingress_controller_prerequisites, ingress_controller_endpoint) -> VSRAdvancedRoutingSetup: + """ + Prepare an example app for advanced routing VSR. + + Single namespace with VS+VSR and advanced-routing app. + + :param request: internal pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: + :param ingress_controller_prerequisites: + :return: + """ + vs_routes_ns = get_route_namespace_from_vs_yaml( + f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml") + ns_1 = create_namespace_with_name_from_yaml(kube_apis.v1, + vs_routes_ns[0], + f"{TEST_DATA}/common/ns.yaml") + print("------------------------- Deploy Virtual Server -----------------------------------") + vs_name = create_virtual_server_from_yaml(kube_apis.custom_objects, + f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml", + ns_1) + vs_host = get_first_vs_host_from_yaml(f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml") + + print("------------------------- Deploy Virtual Server Route -----------------------------------") + vsr_name = create_v_s_route_from_yaml(kube_apis.custom_objects, + f"{TEST_DATA}/{request.param['example']}/virtual-server-route-header.yaml", + ns_1) + vsr_paths = get_paths_from_vsr_yaml(f"{TEST_DATA}/{request.param['example']}/virtual-server-route-header.yaml") + route = VirtualServerRoute(ns_1, vsr_name, vsr_paths) + backends_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}{vsr_paths[0]}" + + print("---------------------- Deploy advanced-routing app ----------------------------") + create_example_app(kube_apis, "advanced-routing", ns_1) + wait_until_all_pods_are_ready(kube_apis.v1, ns_1) + + def fin(): + print("Delete test namespace") + delete_namespace(kube_apis.v1, ns_1) + + request.addfinalizer(fin) + + return VSRAdvancedRoutingSetup(ns_1, vs_host, vs_name, route, backends_url) + + +@pytest.mark.parametrize('crd_ingress_controller, vsr_adv_routing_setup', + [({"type": "complete", "extra_args": [f"-enable-custom-resources"]}, + {"example": "virtual-server-route-advanced-routing"})], + indirect=True) +class TestVSRAdvancedRouting: + def test_flow_with_header(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): + ensure_response_from_backend(vsr_adv_routing_setup.backends_url, vsr_adv_routing_setup.vs_host) + + resp_1 = requests.get(vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "future"}) + resp_2 = requests.get(vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}) + resp_3 = requests.get(vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host, "x-version-invalid": "deprecated"}) + execute_assertions(resp_1, resp_2, resp_3) + + def test_flow_with_argument(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): + patch_v_s_route_from_yaml(kube_apis.custom_objects, + vsr_adv_routing_setup.route.name, + f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-argument.yaml", + vsr_adv_routing_setup.namespace) + wait_before_test(1) + + resp_1 = requests.get(vsr_adv_routing_setup.backends_url + "?arg1=v1", + headers={"host": vsr_adv_routing_setup.vs_host}) + resp_2 = requests.get(vsr_adv_routing_setup.backends_url + "?arg1=v2", + headers={"host": vsr_adv_routing_setup.vs_host}) + resp_3 = requests.get(vsr_adv_routing_setup.backends_url + "?argument1=v1", + headers={"host": vsr_adv_routing_setup.vs_host}) + execute_assertions(resp_1, resp_2, resp_3) + + def test_flow_with_cookie(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): + patch_v_s_route_from_yaml(kube_apis.custom_objects, + vsr_adv_routing_setup.route.name, + f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-cookie.yaml", + vsr_adv_routing_setup.namespace) + wait_before_test(1) + + resp_1 = requests.get(vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host}, cookies={"user": "some"}) + resp_2 = requests.get(vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host}, cookies={"user": "bad"}) + resp_3 = requests.get(vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host}, cookies={"user": "anonymous"}) + execute_assertions(resp_1, resp_2, resp_3) + + def test_flow_with_variable(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): + patch_v_s_route_from_yaml(kube_apis.custom_objects, + vsr_adv_routing_setup.route.name, + f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-variable.yaml", + vsr_adv_routing_setup.namespace) + wait_before_test(1) + + resp_1 = requests.get(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) + resp_2 = requests.post(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) + resp_3 = requests.put(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) + execute_assertions(resp_1, resp_2, resp_3) + + def test_flow_with_complex_conditions(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): + patch_v_s_route_from_yaml(kube_apis.custom_objects, + vsr_adv_routing_setup.route.name, + f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-complex.yaml", + vsr_adv_routing_setup.namespace) + wait_before_test(1) + + resp_1 = requests.get(vsr_adv_routing_setup.backends_url + "?arg1=v1", + headers={"host": vsr_adv_routing_setup.vs_host, + "x-version": "future"}, cookies={"user": "some"}) + resp_2 = requests.post(vsr_adv_routing_setup.backends_url + "?arg1=v2", + headers={"host": vsr_adv_routing_setup.vs_host, + "x-version": "deprecated"}, cookies={"user": "bad"}) + resp_3 = requests.get(vsr_adv_routing_setup.backends_url + "?arg1=v2", + headers={"host": vsr_adv_routing_setup.vs_host, + "x-version": "deprecated"}, cookies={"user": "bad"}) + execute_assertions(resp_1, resp_2, resp_3) diff --git a/tests/suite/test_v_s_route_focused_canary.py b/tests/suite/test_v_s_route_focused_canary.py new file mode 100644 index 0000000000..cde7aec7b3 --- /dev/null +++ b/tests/suite/test_v_s_route_focused_canary.py @@ -0,0 +1,138 @@ +import pytest +import requests +import yaml + +from settings import TEST_DATA +from suite.custom_resources_utils import create_virtual_server_from_yaml, create_v_s_route_from_yaml +from suite.fixtures import VirtualServerRoute +from suite.resources_utils import ensure_response_from_backend, create_example_app, \ + wait_until_all_pods_are_ready, create_namespace_with_name_from_yaml, delete_namespace +from suite.yaml_utils import get_paths_from_vsr_yaml, get_first_vs_host_from_yaml, get_route_namespace_from_vs_yaml + + +def get_weights_of_splitting(file) -> []: + """ + Parse VSR yaml file into an array of weights. + + :param file: an absolute path to file + :return: [] + """ + weights = [] + with open(file) as f: + docs = yaml.load_all(f) + for dep in docs: + for item in dep['spec']['subroutes'][0]['matches'][0]['splits']: + weights.append(item['weight']) + return weights + + +def get_upstreams_of_splitting(file) -> []: + """ + Parse VSR yaml file into an array of upstreams. + + :param file: an absolute path to file + :return: [] + """ + upstreams = [] + with open(file) as f: + docs = yaml.load_all(f) + for dep in docs: + for item in dep['spec']['subroutes'][0]['matches'][0]['splits']: + upstreams.append(item['action']['pass']) + return upstreams + + +class VSRAdvancedRoutingSetup: + """ + Encapsulate advanced routing VSR example details. + + Attributes: + namespace (str): + vs_host (str): + vs_name (str): + route (VirtualServerRoute): + backends_url (str): backend url + """ + + def __init__(self, namespace, vs_host, vs_name, route: VirtualServerRoute, backends_url): + self.namespace = namespace + self.vs_host = vs_host + self.vs_name = vs_name + self.route = route + self.backends_url = backends_url + + +@pytest.fixture(scope="class") +def vsr_canary_setup(request, kube_apis, + ingress_controller_prerequisites, ingress_controller_endpoint) -> VSRAdvancedRoutingSetup: + """ + Prepare an example app for advanced routing VSR. + + Single namespace with VS+VSR and simple app. + + :param request: internal pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: + :param ingress_controller_prerequisites: + :return: + """ + vs_routes_ns = get_route_namespace_from_vs_yaml( + f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml") + ns_1 = create_namespace_with_name_from_yaml(kube_apis.v1, + vs_routes_ns[0], + f"{TEST_DATA}/common/ns.yaml") + print("------------------------- Deploy Virtual Server -----------------------------------") + vs_name = create_virtual_server_from_yaml(kube_apis.custom_objects, + f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml", + ns_1) + vs_host = get_first_vs_host_from_yaml(f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml") + + print("------------------------- Deploy Virtual Server Route -----------------------------------") + vsr_name = create_v_s_route_from_yaml(kube_apis.custom_objects, + f"{TEST_DATA}/{request.param['example']}/virtual-server-route.yaml", + ns_1) + vsr_paths = get_paths_from_vsr_yaml(f"{TEST_DATA}/{request.param['example']}/virtual-server-route.yaml") + route = VirtualServerRoute(ns_1, vsr_name, vsr_paths) + backends_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}{vsr_paths[0]}" + + print("---------------------- Deploy simple app ----------------------------") + create_example_app(kube_apis, "simple", ns_1) + wait_until_all_pods_are_ready(kube_apis.v1, ns_1) + + def fin(): + print("Delete test namespace") + delete_namespace(kube_apis.v1, ns_1) + + request.addfinalizer(fin) + + return VSRAdvancedRoutingSetup(ns_1, vs_host, vs_name, route, backends_url) + + +@pytest.mark.parametrize('crd_ingress_controller, vsr_canary_setup', + [({"type": "complete", "extra_args": [f"-enable-custom-resources"]}, + {"example": "virtual-server-route-focused-canary"})], + indirect=True) +class TestVSRFocusedCanaryRelease: + def test_flow_with_header(self, kube_apis, crd_ingress_controller, vsr_canary_setup): + ensure_response_from_backend(vsr_canary_setup.backends_url, vsr_canary_setup.vs_host) + + weights = get_weights_of_splitting( + f"{TEST_DATA}/virtual-server-route-focused-canary/virtual-server-route.yaml") + upstreams = get_upstreams_of_splitting( + f"{TEST_DATA}/virtual-server-route-focused-canary/virtual-server-route.yaml") + sum_weights = sum(weights) + ratios = [round(i/sum_weights, 1) for i in weights] + + counter_v1, counter_v2 = 0, 0 + for _ in range(100): + resp = requests.get(vsr_canary_setup.backends_url, + headers={"host": vsr_canary_setup.vs_host, "x-version": "canary"}) + if upstreams[0] in resp.text in resp.text: + counter_v1 = counter_v1 + 1 + elif upstreams[1] in resp.text in resp.text: + counter_v2 = counter_v2 + 1 + else: + pytest.fail(f"An unexpected backend in response: {resp.text}") + + assert abs(round(counter_v1/(counter_v1 + counter_v2), 1) - ratios[0]) <= 0.2 + assert abs(round(counter_v2/(counter_v1 + counter_v2), 1) - ratios[1]) <= 0.2 diff --git a/tests/suite/test_v_s_route_split_traffic.py b/tests/suite/test_v_s_route_split_traffic.py new file mode 100644 index 0000000000..c296462f86 --- /dev/null +++ b/tests/suite/test_v_s_route_split_traffic.py @@ -0,0 +1,68 @@ +import pytest +import requests +import yaml + +from settings import TEST_DATA +from suite.yaml_utils import get_paths_from_vsr_yaml + + +def get_weights_of_splitting(file) -> []: + """ + Parse VSR yaml file into an array of weights. + + :param file: an absolute path to file + :return: [] + """ + weights = [] + with open(file) as f: + docs = yaml.load_all(f) + for dep in docs: + for item in dep['spec']['subroutes'][0]['splits']: + weights.append(item['weight']) + return weights + + +def get_upstreams_of_splitting(file) -> []: + """ + Parse VSR yaml file into an array of upstreams. + + :param file: an absolute path to file + :return: [] + """ + upstreams = [] + with open(file) as f: + docs = yaml.load_all(f) + for dep in docs: + for item in dep['spec']['subroutes'][0]['splits']: + upstreams.append(item['action']['pass']) + return upstreams + + +@pytest.mark.parametrize('crd_ingress_controller, v_s_route_setup', + [({"type": "complete", "extra_args": [f"-enable-custom-resources"]}, + {"example": "virtual-server-route-split-traffic"})], + indirect=True) +class TestVSRTrafficSplitting: + def test_several_requests(self, kube_apis, crd_ingress_controller, v_s_route_setup, v_s_route_app_setup): + split_path = get_paths_from_vsr_yaml(f"{TEST_DATA}/virtual-server-route-split-traffic/route-multiple.yaml") + req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}{split_path[0]}" + weights = get_weights_of_splitting( + f"{TEST_DATA}/virtual-server-route-split-traffic/route-multiple.yaml") + upstreams = get_upstreams_of_splitting( + f"{TEST_DATA}/virtual-server-route-split-traffic/route-multiple.yaml") + sum_weights = sum(weights) + ratios = [round(i/sum_weights, 1) for i in weights] + + counter_v1, counter_v2 = 0, 0 + for _ in range(100): + resp = requests.get(req_url, + headers={"host": v_s_route_setup.vs_host}) + if upstreams[0] in resp.text in resp.text: + counter_v1 = counter_v1 + 1 + elif upstreams[1] in resp.text in resp.text: + counter_v2 = counter_v2 + 1 + else: + pytest.fail(f"An unexpected backend in response: {resp.text}") + + assert abs(round(counter_v1/(counter_v1 + counter_v2), 1) - ratios[0]) <= 0.2 + assert abs(round(counter_v2/(counter_v1 + counter_v2), 1) - ratios[1]) <= 0.2 diff --git a/tests/suite/test_virtual_server_focused_canary.py b/tests/suite/test_virtual_server_focused_canary.py new file mode 100644 index 0000000000..3af6dc34e2 --- /dev/null +++ b/tests/suite/test_virtual_server_focused_canary.py @@ -0,0 +1,66 @@ +import pytest + +import requests +import yaml + +from settings import TEST_DATA + + +def get_weights_of_splitting(file) -> []: + """ + Parse yaml file into an array of weights. + + :param file: an absolute path to file + :return: [] + """ + weights = [] + with open(file) as f: + docs = yaml.safe_load_all(f) + for dep in docs: + for item in dep['spec']['routes'][0]['matches'][0]['splits']: + weights.append(item['weight']) + return weights + + +def get_upstreams_of_splitting(file) -> []: + """ + Parse yaml file into an array of upstreams. + + :param file: an absolute path to file + :return: [] + """ + upstreams = [] + with open(file) as f: + docs = yaml.safe_load_all(f) + for dep in docs: + for item in dep['spec']['routes'][0]['matches'][0]['splits']: + upstreams.append(item['action']['pass']) + return upstreams + + +@pytest.mark.parametrize('crd_ingress_controller, virtual_server_setup', + [({"type": "complete", "extra_args": [f"-enable-custom-resources"]}, + {"example": "virtual-server-focused-canary", "app_type": "simple"})], + indirect=True) +class TestVSFocusedCanaryRelease: + def test_several_requests(self, kube_apis, crd_ingress_controller, virtual_server_setup): + weights = get_weights_of_splitting( + f"{TEST_DATA}/virtual-server-focused-canary/standard/virtual-server.yaml") + upstreams = get_upstreams_of_splitting( + f"{TEST_DATA}/virtual-server-focused-canary/standard/virtual-server.yaml") + sum_weights = sum(weights) + ratios = [round(i/sum_weights, 1) for i in weights] + + counter_v1, counter_v2 = 0, 0 + for _ in range(100): + resp = requests.get(virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host, "x-version": "canary"}) + if upstreams[0] in resp.text in resp.text: + counter_v1 = counter_v1 + 1 + elif upstreams[1] in resp.text in resp.text: + counter_v2 = counter_v2 + 1 + else: + pytest.fail(f"An unexpected backend in response: {resp.text}") + + assert abs(round(counter_v1/(counter_v1 + counter_v2), 1) - ratios[0]) <= 0.2 + assert abs(round(counter_v2/(counter_v1 + counter_v2), 1) - ratios[1]) <= 0.2 diff --git a/tests/suite/test_virtual_server_split_traffic.py b/tests/suite/test_virtual_server_split_traffic.py index 3bb8ae1f25..b89e8f792b 100644 --- a/tests/suite/test_virtual_server_split_traffic.py +++ b/tests/suite/test_virtual_server_split_traffic.py @@ -34,7 +34,7 @@ def get_upstreams_of_splitting(file) -> []: docs = yaml.safe_load_all(f) for dep in docs: for item in dep['spec']['routes'][0]['splits']: - upstreams.append(item['upstream']) + upstreams.append(item['action']['pass']) return upstreams diff --git a/tests/suite/test_virtual_server_validation.py b/tests/suite/test_virtual_server_validation.py index 9d56899a46..cc7c56003d 100644 --- a/tests/suite/test_virtual_server_validation.py +++ b/tests/suite/test_virtual_server_validation.py @@ -8,7 +8,8 @@ def assert_new_event_emitted(virtual_server_setup, new_list, previous_list): - text_invalid = f"VirtualServer {virtual_server_setup.namespace}/{virtual_server_setup.vs_name} is invalid and was rejected" + item_name = f"{virtual_server_setup.namespace}/{virtual_server_setup.vs_name}" + text_invalid = f"VirtualServer {item_name} is invalid and was rejected" new_event = new_list[len(new_list) - 1] assert len(new_list) - len(previous_list) == 1 assert text_invalid in new_event.message @@ -33,7 +34,8 @@ def assert_template_conf_exists(kube_apis, ic_pod_name, ic_namespace, virtual_se def assert_event_count_increased(virtual_server_setup, new_list, previous_list): - text_valid = f"Configuration for {virtual_server_setup.namespace}/{virtual_server_setup.vs_name} was added or updated" + item_name = f"{virtual_server_setup.namespace}/{virtual_server_setup.vs_name}" + text_valid = f"Configuration for {item_name} was added or updated" for i in range(len(previous_list)-1, 0, -1): if text_valid in previous_list[i].message: assert new_list[i].count - previous_list[i].count == 1, "We expect the counter to increase" @@ -74,7 +76,7 @@ def test_virtual_server_behavior(self, print("Step 2: make a valid VirtualServer invalid and check") patch_virtual_server_from_yaml(kube_apis.custom_objects, virtual_server_setup.vs_name, - f"{TEST_DATA}/virtual-server-validation/virtual-server-invalid.yaml", + f"{TEST_DATA}/virtual-server-validation/virtual-server-invalid-cookie.yaml", virtual_server_setup.namespace) wait_before_test(1) step_2_list = get_events(kube_apis.v1, virtual_server_setup.namespace) @@ -86,7 +88,7 @@ def test_virtual_server_behavior(self, print("Step 3: update an invalid VirtualServer with another invalid and check") patch_virtual_server_from_yaml(kube_apis.custom_objects, virtual_server_setup.vs_name, - f"{TEST_DATA}/virtual-server-validation/virtual-server-invalid-2.yaml", + f"{TEST_DATA}/virtual-server-validation/virtual-server-no-default-action.yaml", virtual_server_setup.namespace) wait_before_test(1) step_3_list = get_events(kube_apis.v1, virtual_server_setup.namespace) @@ -110,7 +112,7 @@ def test_virtual_server_behavior(self, print("Step 5: delete VS and then create an invalid and check") delete_virtual_server(kube_apis.custom_objects, virtual_server_setup.vs_name, virtual_server_setup.namespace) create_virtual_server_from_yaml(kube_apis.custom_objects, - f"{TEST_DATA}/virtual-server-validation/virtual-server-invalid.yaml", + f"{TEST_DATA}/virtual-server-validation/virtual-server-invalid-cookie.yaml", virtual_server_setup.namespace) wait_before_test(1) step_5_list = get_events(kube_apis.v1, virtual_server_setup.namespace)