Support Path and QueryParams in http route matches (#204)
* support queryparams for gateway api Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * support mse Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * support path and queryParams Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix lint Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix testcase Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix manifests Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * do not provide default value for path Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * Allow Not generating Canary Service && Fixed a bug caused by NOT considering case-insensitivity. (#200) * Fixed a bug caused by NOT considering case-insensitivity. Signed-off-by: yunbo <yunbo10124scut@gmail.com> * add DisableGenerateCanaryService for CanaryStrategy amend1: update crd yaml amend2: add DisableGenerateCanaryService for v1alpha1 Signed-off-by: yunbo <yunbo10124scut@gmail.com> --------- Signed-off-by: yunbo <yunbo10124scut@gmail.com> Co-authored-by: yunbo <yunbo10124scut@gmail.com> Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * revert test images Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * polish comments Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * add gateway api tests Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix MSE cases Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * update golang lint ci Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * regenerate manifests Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * remove generic usage Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * update istio lua script Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * update v1alpha1 in e2e to v1beta1 Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix cases Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * refactor istio case to include queryParams and path Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix cloneset issue Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * fix typo Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> * revert images Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> --------- Signed-off-by: Megrez Lu <lujiajing1126@gmail.com> Signed-off-by: yunbo <yunbo10124scut@gmail.com> Co-authored-by: myname4423 <57184070+myname4423@users.noreply.github.com> Co-authored-by: yunbo <yunbo10124scut@gmail.com>
This commit is contained in:
parent
3eeb7b4ddc
commit
62794dc883
|
|
@ -11,7 +11,7 @@ on:
|
|||
env:
|
||||
# Common versions
|
||||
GO_VERSION: '1.19'
|
||||
GOLANGCI_VERSION: 'v1.42'
|
||||
GOLANGCI_VERSION: 'v1.52'
|
||||
|
||||
jobs:
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
run: |
|
||||
make generate
|
||||
- name: Lint golang code
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_VERSION }}
|
||||
args: --verbose
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ jobs:
|
|||
kubectl apply -f ./test/e2e/test_data/customNetworkProvider/lua_script_configmap.yaml
|
||||
make ginkgo
|
||||
set +e
|
||||
./bin/ginkgo -timeout 60m -v --focus='Canary rollout with custon network provider' test/e2e
|
||||
./bin/ginkgo -timeout 60m -v --focus='Canary rollout with custom network provider' test/e2e
|
||||
retVal=$?
|
||||
# kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout
|
||||
restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}')
|
||||
|
|
|
|||
|
|
@ -146,19 +146,47 @@ type TrafficRoutingStrategy struct {
|
|||
//
|
||||
// +optional
|
||||
RequestHeaderModifier *gatewayv1beta1.HTTPRequestHeaderFilter `json:"requestHeaderModifier,omitempty"`
|
||||
// Matches define conditions used for matching the incoming HTTP requests to canary service.
|
||||
// Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied.
|
||||
// If Gateway API, current only support one match.
|
||||
// And cannot support both weight and matches, if both are configured, then matches takes precedence.
|
||||
// Matches define conditions used for matching incoming HTTP requests to the canary service.
|
||||
// Each match is independent, i.e. this rule will be matched as long as **any** one of the matches is satisfied.
|
||||
//
|
||||
// It cannot support Traffic (weight-based routing) and Matches simultaneously, if both are configured.
|
||||
// In such cases, Matches takes precedence.
|
||||
Matches []HttpRouteMatch `json:"matches,omitempty"`
|
||||
}
|
||||
|
||||
type HttpRouteMatch struct {
|
||||
// Path specifies a HTTP request path matcher.
|
||||
// Supported list:
|
||||
// - Istio: https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPMatchRequest
|
||||
// - GatewayAPI: If path is defined, the whole HttpRouteMatch will be used as a standalone matcher
|
||||
//
|
||||
// +optional
|
||||
Path *gatewayv1beta1.HTTPPathMatch `json:"path,omitempty"`
|
||||
|
||||
// Headers specifies HTTP request header matchers. Multiple match values are
|
||||
// ANDed together, meaning, a request must match all the specified headers
|
||||
// to select the route.
|
||||
//
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +optional
|
||||
// +kubebuilder:validation:MaxItems=16
|
||||
Headers []gatewayv1beta1.HTTPHeaderMatch `json:"headers,omitempty"`
|
||||
|
||||
// QueryParams specifies HTTP query parameter matchers. Multiple match
|
||||
// values are ANDed together, meaning, a request must match all the
|
||||
// specified query parameters to select the route.
|
||||
// Supported list:
|
||||
// - Istio: https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPMatchRequest
|
||||
// - MSE Ingress: https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/annotations-supported-by-mse-ingress-gateways-1
|
||||
// Header/Cookie > QueryParams
|
||||
// - Gateway API
|
||||
//
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +optional
|
||||
// +kubebuilder:validation:MaxItems=16
|
||||
QueryParams []gatewayv1beta1.HTTPQueryParamMatch `json:"queryParams,omitempty"`
|
||||
}
|
||||
|
||||
// RolloutPause defines a pause stage for a rollout
|
||||
|
|
|
|||
|
|
@ -295,6 +295,11 @@ func (in *GatewayTrafficRouting) DeepCopy() *GatewayTrafficRouting {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HttpRouteMatch) DeepCopyInto(out *HttpRouteMatch) {
|
||||
*out = *in
|
||||
if in.Path != nil {
|
||||
in, out := &in.Path, &out.Path
|
||||
*out = new(apisv1beta1.HTTPPathMatch)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Headers != nil {
|
||||
in, out := &in.Headers, &out.Headers
|
||||
*out = make([]apisv1beta1.HTTPHeaderMatch, len(*in))
|
||||
|
|
@ -302,6 +307,13 @@ func (in *HttpRouteMatch) DeepCopyInto(out *HttpRouteMatch) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.QueryParams != nil {
|
||||
in, out := &in.QueryParams, &out.QueryParams
|
||||
*out = make([]apisv1beta1.HTTPQueryParamMatch, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HttpRouteMatch.
|
||||
|
|
|
|||
|
|
@ -622,13 +622,13 @@ spec:
|
|||
description: CanaryStep defines a step of a canary workload.
|
||||
properties:
|
||||
matches:
|
||||
description: Matches define conditions used for matching
|
||||
the incoming HTTP requests to canary service. Each
|
||||
description: "Matches define conditions used for matching
|
||||
incoming HTTP requests to the canary service. Each
|
||||
match is independent, i.e. this rule will be matched
|
||||
if **any** one of the matches is satisfied. If Gateway
|
||||
API, current only support one match. And cannot support
|
||||
both weight and matches, if both are configured, then
|
||||
matches takes precedence.
|
||||
as long as **any** one of the matches is satisfied.
|
||||
\n It cannot support Traffic (weight-based routing)
|
||||
and Matches simultaneously, if both are configured.
|
||||
In such cases, Matches takes precedence."
|
||||
items:
|
||||
properties:
|
||||
headers:
|
||||
|
|
@ -690,6 +690,88 @@ spec:
|
|||
type: object
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
path:
|
||||
description: 'Path specifies a HTTP request path
|
||||
matcher. Supported list: - Istio: https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPMatchRequest
|
||||
- GatewayAPI: If path is defined, the whole
|
||||
HttpRouteMatch will be used as a standalone
|
||||
matcher'
|
||||
properties:
|
||||
type:
|
||||
default: PathPrefix
|
||||
description: "Type specifies how to match
|
||||
against the path Value. \n Support: Core
|
||||
(Exact, PathPrefix) \n Support: Custom (RegularExpression)"
|
||||
enum:
|
||||
- Exact
|
||||
- PathPrefix
|
||||
- RegularExpression
|
||||
type: string
|
||||
value:
|
||||
default: /
|
||||
description: Value of the HTTP path to match
|
||||
against.
|
||||
maxLength: 1024
|
||||
type: string
|
||||
type: object
|
||||
queryParams:
|
||||
description: 'QueryParams specifies HTTP query
|
||||
parameter matchers. Multiple match values are
|
||||
ANDed together, meaning, a request must match
|
||||
all the specified query parameters to select
|
||||
the route. Supported list: - Istio: https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPMatchRequest
|
||||
- MSE Ingress: https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/annotations-supported-by-mse-ingress-gateways-1 Header/Cookie
|
||||
> QueryParams - Gateway API'
|
||||
items:
|
||||
description: HTTPQueryParamMatch describes how
|
||||
to select a HTTP route by matching HTTP query
|
||||
parameters.
|
||||
properties:
|
||||
name:
|
||||
description: "Name is the name of the HTTP
|
||||
query param to be matched. This must be
|
||||
an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).
|
||||
\n If multiple entries specify equivalent
|
||||
query param names, only the first entry
|
||||
with an equivalent name MUST be considered
|
||||
for a match. Subsequent entries with an
|
||||
equivalent query param name MUST be ignored."
|
||||
maxLength: 256
|
||||
minLength: 1
|
||||
type: string
|
||||
type:
|
||||
default: Exact
|
||||
description: "Type specifies how to match
|
||||
against the value of the query parameter.
|
||||
\n Support: Extended (Exact) \n Support:
|
||||
Custom (RegularExpression) \n Since RegularExpression
|
||||
QueryParamMatchType has custom conformance,
|
||||
implementations can support POSIX, PCRE
|
||||
or any other dialects of regular expressions.
|
||||
Please read the implementation's documentation
|
||||
to determine the supported dialect."
|
||||
enum:
|
||||
- Exact
|
||||
- RegularExpression
|
||||
type: string
|
||||
value:
|
||||
description: Value is the value of HTTP
|
||||
query param to be matched.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
type: object
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
type: object
|
||||
type: array
|
||||
pause:
|
||||
|
|
|
|||
|
|
@ -47,8 +47,21 @@ function GenerateRoutesWithMatches(spec, matches, stableService, canaryService)
|
|||
for _, match in ipairs(matches) do
|
||||
local route = {}
|
||||
route["match"] = {}
|
||||
for key, value in pairs(match) do
|
||||
|
||||
local vsMatch = {}
|
||||
for key, value in pairs(match) do
|
||||
if key == "path" then
|
||||
vsMatch["uri"] = {}
|
||||
local rule = value
|
||||
if rule["type"] == "RegularExpression" then
|
||||
matchType = "regex"
|
||||
elseif rule["type"] == "Exact" then
|
||||
matchType = "exact"
|
||||
elseif rule["type"] == "PathPrefix" then
|
||||
matchType = "prefix"
|
||||
end
|
||||
vsMatch["uri"][matchType] = rule.value
|
||||
else
|
||||
vsMatch[key] = {}
|
||||
for _, rule in ipairs(value) do
|
||||
if rule["type"] == "RegularExpression" then
|
||||
|
|
@ -58,15 +71,16 @@ function GenerateRoutesWithMatches(spec, matches, stableService, canaryService)
|
|||
elseif rule["type"] == "Prefix" then
|
||||
matchType = "prefix"
|
||||
end
|
||||
if key == "headers" then
|
||||
if key == "headers" or key == "queryParams" then
|
||||
vsMatch[key][rule["name"]] = {}
|
||||
vsMatch[key][rule["name"]][matchType] = rule.value
|
||||
else
|
||||
vsMatch[key][matchType] = rule.value
|
||||
end
|
||||
end
|
||||
table.insert(route["match"], vsMatch)
|
||||
end
|
||||
end
|
||||
table.insert(route["match"], vsMatch)
|
||||
route.route = {
|
||||
{
|
||||
destination = {}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = nil
|
|||
annotations["nginx.ingress.kubernetes.io/canary-by-header"] = nil
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = nil
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = nil
|
||||
-- MSE extended annotations
|
||||
annotations["mse.ingress.kubernetes.io/canary-by-query"] = nil
|
||||
annotations["mse.ingress.kubernetes.io/canary-by-query-pattern"] = nil
|
||||
annotations["mse.ingress.kubernetes.io/canary-by-query-value"] = nil
|
||||
annotations["nginx.ingress.kubernetes.io/canary-weight"] = nil
|
||||
if ( obj.weight ~= "-1" )
|
||||
then
|
||||
|
|
@ -33,6 +37,7 @@ then
|
|||
return annotations
|
||||
end
|
||||
for _,match in ipairs(obj.matches) do
|
||||
if match.headers and next(match.headers) ~= nil then
|
||||
header = match.headers[1]
|
||||
if ( header.name == "canary-by-cookie" )
|
||||
then
|
||||
|
|
@ -46,5 +51,16 @@ for _,match in ipairs(obj.matches) do
|
|||
annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value
|
||||
end
|
||||
end
|
||||
end
|
||||
if match.queryParams and next(match.queryParams) ~= nil then
|
||||
queryParam = match.queryParams[1]
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-query"] = queryParam.name
|
||||
if ( queryParam.type == "RegularExpression" )
|
||||
then
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-query-pattern"] = queryParam.value
|
||||
else
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-query-value"] = queryParam.value
|
||||
end
|
||||
end
|
||||
end
|
||||
return annotations
|
||||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/openkruise/rollouts/pkg/util"
|
||||
"github.com/openkruise/rollouts/pkg/util/configuration"
|
||||
"github.com/openkruise/rollouts/pkg/util/luamanager"
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -45,6 +46,7 @@ import (
|
|||
luajson "layeh.com/gopher-json"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
|
@ -229,7 +231,7 @@ func checkEqual(cli client.Client, t *testing.T, expect *unstructured.Unstructur
|
|||
fmt.Println(util.DumpJSON(obj.GetAnnotations()), util.DumpJSON(expect.GetAnnotations()))
|
||||
t.Fatalf("expect(%s), but get(%s)", util.DumpJSON(expect.GetAnnotations()), util.DumpJSON(obj.GetAnnotations()))
|
||||
}
|
||||
if util.DumpJSON(expect.Object["spec"]) != util.DumpJSON(obj.Object["spec"]) {
|
||||
if !assert.JSONEq(t, util.DumpJSON(expect.Object["spec"]), util.DumpJSON(obj.Object["spec"])) {
|
||||
t.Fatalf("expect(%s), but get(%s)", util.DumpJSON(expect.Object["spec"]), util.DumpJSON(obj.Object["spec"]))
|
||||
}
|
||||
}
|
||||
|
|
@ -245,7 +247,7 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
expectUnstructureds func() []*unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "test1, do traffic routing for VirtualService and DestinationRule",
|
||||
name: "Do weight-based traffic routing for VirtualService and DestinationRule",
|
||||
getRoutes: func() *v1beta1.TrafficRoutingStrategy {
|
||||
return &v1beta1.TrafficRoutingStrategy{
|
||||
Traffic: utilpointer.String("5%"),
|
||||
|
|
@ -316,6 +318,102 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
return done, hasError
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Do header/queryParam-based traffic routing for VirtualService and DestinationRule",
|
||||
getRoutes: func() *v1beta1.TrafficRoutingStrategy {
|
||||
pathTypePrefix := gatewayv1beta1.PathMatchPathPrefix
|
||||
headerTypeExact := gatewayv1beta1.HeaderMatchExact
|
||||
queryParamRegex := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
return &v1beta1.TrafficRoutingStrategy{
|
||||
Matches: []v1beta1.HttpRouteMatch{
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Type: &pathTypePrefix,
|
||||
Value: utilpointer.String("/api/v2"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: &headerTypeExact,
|
||||
Name: "user_id",
|
||||
Value: "123456",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Type: &queryParamRegex,
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
getUnstructureds: func() []*unstructured.Unstructured {
|
||||
objects := make([]*unstructured.Unstructured, 0)
|
||||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(virtualServiceDemo))
|
||||
u.SetAPIVersion("networking.istio.io/v1alpha3")
|
||||
objects = append(objects, u)
|
||||
|
||||
u = &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(destinationRuleDemo))
|
||||
u.SetAPIVersion("networking.istio.io/v1alpha3")
|
||||
objects = append(objects, u)
|
||||
return objects
|
||||
},
|
||||
getConfig: func() Config {
|
||||
return Config{
|
||||
Key: "rollout-demo",
|
||||
StableService: "echoserver",
|
||||
CanaryService: "echoserver-canary",
|
||||
TrafficConf: []v1beta1.ObjectRef{
|
||||
{
|
||||
APIVersion: "networking.istio.io/v1alpha3",
|
||||
Kind: "VirtualService",
|
||||
Name: "echoserver",
|
||||
},
|
||||
{
|
||||
APIVersion: "networking.istio.io/v1alpha3",
|
||||
Kind: "DestinationRule",
|
||||
Name: "dr-demo",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
expectUnstructureds: func() []*unstructured.Unstructured {
|
||||
objects := make([]*unstructured.Unstructured, 0)
|
||||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(virtualServiceDemo))
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
specStr := `{"hosts":["echoserver.example.com"],"http":[{"match":[{"headers":{"user_id":{"exact":"123456"}},"queryParams":{"user_id":{"regex":"123*"}},"uri":{"prefix":"/api/v2"}}],"route":[{"destination":{"host":"echoserver-canary"}}]},{"route":[{"destination":{"host":"echoserver"}}]}]}`
|
||||
var spec interface{}
|
||||
_ = json.Unmarshal([]byte(specStr), &spec)
|
||||
u.Object["spec"] = spec
|
||||
objects = append(objects, u)
|
||||
|
||||
u = &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(destinationRuleDemo))
|
||||
annotations = map[string]string{
|
||||
OriginalSpecAnnotation: `{"spec":{"host":"mockb","subsets":[{"labels":{"version":"base"},"name":"version-base"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`,
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
specStr = `{"host":"mockb","subsets":[{"labels":{"version":"base"},"name":"version-base"},{"labels":{"istio.service.tag":"gray"},"name":"canary"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}`
|
||||
_ = json.Unmarshal([]byte(specStr), &spec)
|
||||
u.Object["spec"] = spec
|
||||
objects = append(objects, u)
|
||||
return objects
|
||||
},
|
||||
expectState: func() (bool, bool) {
|
||||
done := false
|
||||
hasError := false
|
||||
return done, hasError
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test2, do traffic routing but failed to execute lua",
|
||||
getRoutes: func() *v1beta1.TrafficRoutingStrategy {
|
||||
|
|
|
|||
|
|
@ -103,8 +103,20 @@ function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stab
|
|||
local route = {}
|
||||
route["match"] = {}
|
||||
|
||||
for key, value in pairs(match) do
|
||||
local vsMatch = {}
|
||||
for key, value in pairs(match) do
|
||||
if key == "path" then
|
||||
vsMatch["uri"] = {}
|
||||
local rule = value
|
||||
if rule["type"] == "RegularExpression" then
|
||||
matchType = "regex"
|
||||
elseif rule["type"] == "Exact" then
|
||||
matchType = "exact"
|
||||
elseif rule["type"] == "PathPrefix" then
|
||||
matchType = "prefix"
|
||||
end
|
||||
vsMatch["uri"][matchType] = rule.value
|
||||
else
|
||||
vsMatch[key] = {}
|
||||
for _, rule in ipairs(value) do
|
||||
if rule["type"] == "RegularExpression" then
|
||||
|
|
@ -114,15 +126,16 @@ function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stab
|
|||
elseif rule["type"] == "Prefix" then
|
||||
matchType = "prefix"
|
||||
end
|
||||
if key == "headers" then
|
||||
if key == "headers" or key == "queryParams" then
|
||||
vsMatch[key][rule["name"]] = {}
|
||||
vsMatch[key][rule["name"]][matchType] = rule.value
|
||||
else
|
||||
vsMatch[key][matchType] = rule.value
|
||||
end
|
||||
end
|
||||
table.insert(route["match"], vsMatch)
|
||||
end
|
||||
end
|
||||
table.insert(route["match"], vsMatch)
|
||||
route.route = {
|
||||
{
|
||||
destination = {}
|
||||
|
|
|
|||
|
|
@ -153,9 +153,15 @@ func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1beta1.HTTPRou
|
|||
return r.buildCanaryWeightHttpRoutes(rules, weight)
|
||||
}
|
||||
|
||||
func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1beta1.HTTPRouteRule, matchs []v1beta1.HttpRouteMatch) []gatewayv1beta1.HTTPRouteRule {
|
||||
func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1beta1.HTTPRouteRule, matches []v1beta1.HttpRouteMatch) []gatewayv1beta1.HTTPRouteRule {
|
||||
var desired []gatewayv1beta1.HTTPRouteRule
|
||||
var canarys []gatewayv1beta1.HTTPRouteRule
|
||||
var canaries []gatewayv1beta1.HTTPRouteRule
|
||||
pathMatches := util.FilterHttpRouteMatch(matches, func(match v1beta1.HttpRouteMatch) bool {
|
||||
return match.Path != nil
|
||||
})
|
||||
nonPathMatches := util.FilterHttpRouteMatch(matches, func(match v1beta1.HttpRouteMatch) bool {
|
||||
return match.Path == nil
|
||||
})
|
||||
for i := range rules {
|
||||
rule := rules[i]
|
||||
if _, canaryRef := getServiceBackendRef(rule, r.conf.CanaryService); canaryRef != nil {
|
||||
|
|
@ -172,18 +178,35 @@ func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1beta1.H
|
|||
canaryRule.BackendRefs = []gatewayv1beta1.HTTPBackendRef{*canaryRef}
|
||||
// set canary headers in httpRoute
|
||||
var newMatches []gatewayv1beta1.HTTPRouteMatch
|
||||
for _, pathMatch := range pathMatches {
|
||||
newMatches = append(newMatches, gatewayv1beta1.HTTPRouteMatch{
|
||||
Path: pathMatch.Path,
|
||||
Headers: pathMatch.Headers,
|
||||
QueryParams: pathMatch.QueryParams,
|
||||
})
|
||||
}
|
||||
// reset pathMatches
|
||||
pathMatches = nil
|
||||
if len(nonPathMatches) == 0 && len(newMatches) == 0 {
|
||||
continue
|
||||
}
|
||||
for j := range canaryRule.Matches {
|
||||
canaryRuleMatch := &canaryRule.Matches[j]
|
||||
for k := range matchs {
|
||||
for k := range nonPathMatches {
|
||||
canaryRuleMatchBase := *canaryRuleMatch
|
||||
canaryRuleMatchBase.Headers = append(canaryRuleMatchBase.Headers, matchs[k].Headers...)
|
||||
if len(matches[k].Headers) > 0 {
|
||||
canaryRuleMatchBase.Headers = append(canaryRuleMatchBase.Headers, matches[k].Headers...)
|
||||
}
|
||||
if len(matches[k].QueryParams) > 0 {
|
||||
canaryRuleMatchBase.QueryParams = append(canaryRuleMatchBase.QueryParams, matches[k].QueryParams...)
|
||||
}
|
||||
newMatches = append(newMatches, canaryRuleMatchBase)
|
||||
}
|
||||
}
|
||||
canaryRule.Matches = newMatches
|
||||
canarys = append(canarys, *canaryRule)
|
||||
canaries = append(canaries, *canaryRule)
|
||||
}
|
||||
desired = append(desired, canarys...)
|
||||
desired = append(desired, canaries...)
|
||||
return desired
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
|
|||
desiredRules func() []gatewayv1beta1.HTTPRouteRule
|
||||
}{
|
||||
{
|
||||
name: "test1 headers",
|
||||
name: "test headers",
|
||||
getRouteRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
return rules
|
||||
|
|
@ -310,6 +310,435 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
|
|||
return rules
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test query params",
|
||||
getRouteRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
return rules
|
||||
},
|
||||
getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) {
|
||||
iType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
return nil, []v1beta1.HttpRouteMatch{
|
||||
// queryparams
|
||||
{
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "234*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
desiredRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
iType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
rules = append(rules, gatewayv1beta1.HTTPRouteRule{
|
||||
Matches: []gatewayv1beta1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/store"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "version",
|
||||
Value: "v2",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/store"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "version",
|
||||
Value: "v2",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "234*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/v2/store"),
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/v2/store"),
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "234*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BackendRefs: []gatewayv1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gatewayv1beta1.BackendRef{
|
||||
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
|
||||
Kind: &kindSvc,
|
||||
Name: "store-svc-canary",
|
||||
Port: &portNum,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
rules = append(rules, gatewayv1beta1.HTTPRouteRule{
|
||||
Matches: []gatewayv1beta1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/storage"),
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/storage"),
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "234*",
|
||||
Type: &iType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BackendRefs: []gatewayv1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gatewayv1beta1.BackendRef{
|
||||
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
|
||||
Kind: &kindSvc,
|
||||
Name: "store-svc-canary",
|
||||
Port: &portNum,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return rules
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test query params and headers",
|
||||
getRouteRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
return rules
|
||||
},
|
||||
getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) {
|
||||
iQueryParamType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
iHeaderType := gatewayv1beta1.HeaderMatchRegularExpression
|
||||
return nil, []v1beta1.HttpRouteMatch{
|
||||
// queryParams + headers
|
||||
{
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iQueryParamType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iHeaderType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
desiredRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
iQueryParamType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
iHeaderType := gatewayv1beta1.HeaderMatchRegularExpression
|
||||
rules = append(rules, gatewayv1beta1.HTTPRouteRule{
|
||||
Matches: []gatewayv1beta1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/store"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "version",
|
||||
Value: "v2",
|
||||
},
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iHeaderType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iQueryParamType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/v2/store"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iHeaderType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iQueryParamType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BackendRefs: []gatewayv1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gatewayv1beta1.BackendRef{
|
||||
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
|
||||
Kind: &kindSvc,
|
||||
Name: "store-svc-canary",
|
||||
Port: &portNum,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
rules = append(rules, gatewayv1beta1.HTTPRouteRule{
|
||||
Matches: []gatewayv1beta1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/storage"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iHeaderType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iQueryParamType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BackendRefs: []gatewayv1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gatewayv1beta1.BackendRef{
|
||||
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
|
||||
Kind: &kindSvc,
|
||||
Name: "store-svc-canary",
|
||||
Port: &portNum,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return rules
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test path replace",
|
||||
getRouteRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
return rules
|
||||
},
|
||||
getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) {
|
||||
iQueryParamType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
iHeaderType := gatewayv1beta1.HeaderMatchRegularExpression
|
||||
return nil, []v1beta1.HttpRouteMatch{
|
||||
// queryParams + headers + path
|
||||
{
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iQueryParamType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iHeaderType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/storage/v2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
desiredRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
rules := routeDemo.DeepCopy().Spec.Rules
|
||||
iQueryParamType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
iHeaderType := gatewayv1beta1.HeaderMatchRegularExpression
|
||||
rules = append(rules, gatewayv1beta1.HTTPRouteRule{
|
||||
Matches: []gatewayv1beta1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatewayv1beta1.HTTPPathMatch{
|
||||
Value: utilpointer.String("/storage/v2"),
|
||||
},
|
||||
Headers: []gatewayv1beta1.HTTPHeaderMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iHeaderType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iQueryParamType,
|
||||
},
|
||||
{
|
||||
Name: "canary",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BackendRefs: []gatewayv1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gatewayv1beta1.BackendRef{
|
||||
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
|
||||
Kind: &kindSvc,
|
||||
Name: "store-svc-canary",
|
||||
Port: &portNum,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return rules
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "canary weight: 20",
|
||||
getRouteRules: func() []gatewayv1beta1.HTTPRouteRule {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ var (
|
|||
annotations["nginx.ingress.kubernetes.io/canary-by-header"] = nil
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = nil
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = nil
|
||||
-- MSE extended annotations
|
||||
annotations["mse.ingress.kubernetes.io/canary-by-query"] = nil
|
||||
annotations["mse.ingress.kubernetes.io/canary-by-query-pattern"] = nil
|
||||
annotations["mse.ingress.kubernetes.io/canary-by-query-value"] = nil
|
||||
annotations["nginx.ingress.kubernetes.io/canary-weight"] = nil
|
||||
if ( obj.weight ~= "-1" )
|
||||
then
|
||||
|
|
@ -79,6 +83,7 @@ var (
|
|||
return annotations
|
||||
end
|
||||
for _,match in ipairs(obj.matches) do
|
||||
if match.headers and next(match.headers) ~= nil then
|
||||
header = match.headers[1]
|
||||
if ( header.name == "canary-by-cookie" )
|
||||
then
|
||||
|
|
@ -93,6 +98,17 @@ var (
|
|||
end
|
||||
end
|
||||
end
|
||||
if match.queryParams and next(match.queryParams) ~= nil then
|
||||
queryParam = match.queryParams[1]
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-query"] = queryParam.name
|
||||
if ( queryParam.type == "RegularExpression" )
|
||||
then
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-query-pattern"] = queryParam.value
|
||||
else
|
||||
annotations["nginx.ingress.kubernetes.io/canary-by-query-value"] = queryParam.value
|
||||
end
|
||||
end
|
||||
end
|
||||
return annotations
|
||||
`,
|
||||
fmt.Sprintf("%s.aliyun-alb", configuration.LuaTrafficRoutingIngressTypePrefix): `
|
||||
|
|
@ -519,6 +535,128 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
return expect
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ensure routes test5",
|
||||
getConfigmap: func() *corev1.ConfigMap {
|
||||
return demoConf.DeepCopy()
|
||||
},
|
||||
getIngress: func() []*netv1.Ingress {
|
||||
canary := demoIngress.DeepCopy()
|
||||
canary.Name = "echoserver-canary"
|
||||
canary.Annotations["nginx.ingress.kubernetes.io/canary"] = "true"
|
||||
canary.Annotations["nginx.ingress.kubernetes.io/canary-weight"] = "0"
|
||||
canary.Annotations["mse.ingress.kubernetes.io/service-subset"] = ""
|
||||
canary.Spec.Rules[0].HTTP.Paths = canary.Spec.Rules[0].HTTP.Paths[:1]
|
||||
canary.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
||||
},
|
||||
getRoutes: func() *v1beta1.CanaryStep {
|
||||
return &v1beta1.CanaryStep{
|
||||
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||
Traffic: nil,
|
||||
Matches: []v1beta1.HttpRouteMatch{
|
||||
// querystring
|
||||
{
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123456",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RequestHeaderModifier: &gatewayv1beta1.HTTPRequestHeaderFilter{
|
||||
Set: []gatewayv1beta1.HTTPHeader{
|
||||
{
|
||||
Name: "gray",
|
||||
Value: "blue",
|
||||
},
|
||||
{
|
||||
Name: "gray",
|
||||
Value: "green",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
expectIngress: func() *netv1.Ingress {
|
||||
expect := demoIngress.DeepCopy()
|
||||
expect.Name = "echoserver-canary"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary"] = "true"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-query"] = "user_id"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-query-value"] = "123456"
|
||||
expect.Annotations["mse.ingress.kubernetes.io/request-header-control-update"] = "gray blue\ngray green\n"
|
||||
expect.Annotations["mse.ingress.kubernetes.io/service-subset"] = "gray"
|
||||
expect.Spec.Rules[0].HTTP.Paths = expect.Spec.Rules[0].HTTP.Paths[:1]
|
||||
expect.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
expect.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
return expect
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ensure routes test5",
|
||||
getConfigmap: func() *corev1.ConfigMap {
|
||||
return demoConf.DeepCopy()
|
||||
},
|
||||
getIngress: func() []*netv1.Ingress {
|
||||
canary := demoIngress.DeepCopy()
|
||||
canary.Name = "echoserver-canary"
|
||||
canary.Annotations["nginx.ingress.kubernetes.io/canary"] = "true"
|
||||
canary.Annotations["nginx.ingress.kubernetes.io/canary-weight"] = "0"
|
||||
canary.Annotations["mse.ingress.kubernetes.io/service-subset"] = ""
|
||||
canary.Spec.Rules[0].HTTP.Paths = canary.Spec.Rules[0].HTTP.Paths[:1]
|
||||
canary.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
||||
},
|
||||
getRoutes: func() *v1beta1.CanaryStep {
|
||||
iType := gatewayv1beta1.QueryParamMatchRegularExpression
|
||||
return &v1beta1.CanaryStep{
|
||||
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||
Traffic: nil,
|
||||
Matches: []v1beta1.HttpRouteMatch{
|
||||
// querystring
|
||||
{
|
||||
QueryParams: []gatewayv1beta1.HTTPQueryParamMatch{
|
||||
{
|
||||
Name: "user_id",
|
||||
Value: "123*",
|
||||
Type: &iType,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RequestHeaderModifier: &gatewayv1beta1.HTTPRequestHeaderFilter{
|
||||
Set: []gatewayv1beta1.HTTPHeader{
|
||||
{
|
||||
Name: "gray",
|
||||
Value: "blue",
|
||||
},
|
||||
{
|
||||
Name: "gray",
|
||||
Value: "green",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
expectIngress: func() *netv1.Ingress {
|
||||
expect := demoIngress.DeepCopy()
|
||||
expect.Name = "echoserver-canary"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary"] = "true"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-query"] = "user_id"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-query-pattern"] = "123*"
|
||||
expect.Annotations["mse.ingress.kubernetes.io/request-header-control-update"] = "gray blue\ngray green\n"
|
||||
expect.Annotations["mse.ingress.kubernetes.io/service-subset"] = "gray"
|
||||
expect.Spec.Rules[0].HTTP.Paths = expect.Spec.Rules[0].HTTP.Paths[:1]
|
||||
expect.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
expect.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||
return expect
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config := Config{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2022 The Kruise Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/openkruise/rollouts/api/v1beta1"
|
||||
)
|
||||
|
||||
func FilterHttpRouteMatch(s []v1beta1.HttpRouteMatch, f func(v1beta1.HttpRouteMatch) bool) []v1beta1.HttpRouteMatch {
|
||||
s2 := make([]v1beta1.HttpRouteMatch, 0, len(s))
|
||||
for _, e := range s {
|
||||
if f(e) {
|
||||
s2 = append(s2, e)
|
||||
}
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import (
|
|||
appsv1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
appsv1beta1 "github.com/openkruise/kruise-api/apps/v1beta1"
|
||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||
"github.com/openkruise/rollouts/api/v1beta1"
|
||||
"github.com/openkruise/rollouts/pkg/util"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
|
@ -59,6 +60,16 @@ func getRolloutCondition(status v1alpha1.RolloutStatus, condType v1alpha1.Rollou
|
|||
return nil
|
||||
}
|
||||
|
||||
func getRolloutConditionV1beta1(status v1beta1.RolloutStatus, condType v1beta1.RolloutConditionType) *v1beta1.RolloutCondition {
|
||||
for i := range status.Conditions {
|
||||
c := status.Conditions[i]
|
||||
if c.Type == condType {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ = SIGDescribe("Rollout", func() {
|
||||
var namespace string
|
||||
|
||||
|
|
@ -218,6 +229,21 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
}, 10*time.Second, time.Second).Should(BeTrue())
|
||||
}
|
||||
|
||||
ResumeRolloutCanaryV1beta1 := func(name string) {
|
||||
Eventually(func() bool {
|
||||
clone := &v1beta1.Rollout{}
|
||||
Expect(GetObject(name, clone)).NotTo(HaveOccurred())
|
||||
if clone.Status.CanaryStatus.CurrentStepState != v1beta1.CanaryStepStatePaused {
|
||||
fmt.Println("resume rollout success, and CurrentStepState", util.DumpJSON(clone.Status))
|
||||
return true
|
||||
}
|
||||
|
||||
body := fmt.Sprintf(`{"status":{"canaryStatus":{"currentStepState":"%s"}}}`, v1beta1.CanaryStepStateReady)
|
||||
Expect(k8sClient.Status().Patch(context.TODO(), clone, client.RawPatch(types.MergePatchType, []byte(body)))).NotTo(HaveOccurred())
|
||||
return false
|
||||
}, 10*time.Second, time.Second).Should(BeTrue())
|
||||
}
|
||||
|
||||
WaitDeploymentAllPodsReady := func(deployment *apps.Deployment) {
|
||||
Eventually(func() bool {
|
||||
clone := &apps.Deployment{}
|
||||
|
|
@ -2652,9 +2678,9 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
})
|
||||
|
||||
KruiseDescribe("Canary rollout with custon network provider", func() {
|
||||
It("V1->V2: Route traffic with header matches and weight using rollout for VirtualService", func() {
|
||||
It("V1->V2: Route traffic with header/queryParams/path matches and weight using rollout for VirtualService", func() {
|
||||
By("Creating Rollout...")
|
||||
rollout := &v1alpha1.Rollout{}
|
||||
rollout := &v1beta1.Rollout{}
|
||||
Expect(ReadYamlToObject("./test_data/customNetworkProvider/rollout_with_trafficrouting.yaml", rollout)).ToNot(HaveOccurred())
|
||||
CreateObject(rollout)
|
||||
|
||||
|
|
@ -2678,7 +2704,7 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
|
||||
// check rollout status
|
||||
Expect(GetObject(rollout.Name, rollout)).NotTo(HaveOccurred())
|
||||
Expect(rollout.Status.Phase).Should(Equal(v1alpha1.RolloutPhaseHealthy))
|
||||
Expect(rollout.Status.Phase).Should(Equal(v1beta1.RolloutPhaseHealthy))
|
||||
By("check rollout status & paused success")
|
||||
|
||||
// v1 -> v2, start rollout action
|
||||
|
|
@ -2698,14 +2724,14 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
Expect(rollout.Status.CanaryStatus.CanaryReadyReplicas).Should(BeNumerically("==", 1))
|
||||
// check virtualservice spec
|
||||
Expect(GetObject(vs.GetName(), vs)).NotTo(HaveOccurred())
|
||||
expectedSpec := `{"gateways":["nginx-gateway"],"hosts":["*"],"http":[{"match":[{"headers":{"user-agent":{"exact":"pc"}}}],"route":[{"destination":{"host":"echoserver-canary"}}]},{"route":[{"destination":{"host":"echoserver"}}]}]}`
|
||||
expectedSpec := `{"gateways":["nginx-gateway"],"hosts":["*"],"http":[{"match":[{"uri":{"prefix":"/pc"}}],"route":[{"destination":{"host":"echoserver-canary"}}]},{"match":[{"queryParams":{"user-agent":{"exact":"pc"}}}],"route":[{"destination":{"host":"echoserver-canary"}}]},{"match":[{"headers":{"user-agent":{"exact":"pc"}}}],"route":[{"destination":{"host":"echoserver-canary"}}]},{"route":[{"destination":{"host":"echoserver"}}]}]}`
|
||||
Expect(util.DumpJSON(vs.Object["spec"])).Should(Equal(expectedSpec))
|
||||
// check original spec annotation
|
||||
expectedAnno := `{"spec":{"gateways":["nginx-gateway"],"hosts":["*"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}}`
|
||||
Expect(vs.GetAnnotations()[OriginalSpecAnnotation]).Should(Equal(expectedAnno))
|
||||
|
||||
// resume rollout canary
|
||||
ResumeRolloutCanary(rollout.Name)
|
||||
ResumeRolloutCanaryV1beta1(rollout.Name)
|
||||
By("Resume rollout, and wait next step(2), routing 50% traffic to new version pods")
|
||||
WaitRolloutCanaryStepPaused(rollout.Name, 2)
|
||||
// check rollout status
|
||||
|
|
@ -2720,7 +2746,7 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
Expect(vs.GetAnnotations()[OriginalSpecAnnotation]).Should(Equal(expectedAnno))
|
||||
|
||||
// resume rollout
|
||||
ResumeRolloutCanary(rollout.Name)
|
||||
ResumeRolloutCanaryV1beta1(rollout.Name)
|
||||
WaitRolloutStatusPhase(rollout.Name, v1alpha1.RolloutPhaseHealthy)
|
||||
By("rollout completed, and check")
|
||||
// check service & virtualservice & deployment
|
||||
|
|
@ -2747,10 +2773,10 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
}
|
||||
// check progressing succeed
|
||||
Expect(GetObject(rollout.Name, rollout)).NotTo(HaveOccurred())
|
||||
cond := getRolloutCondition(rollout.Status, v1alpha1.RolloutConditionProgressing)
|
||||
Expect(cond.Reason).Should(Equal(v1alpha1.ProgressingReasonCompleted))
|
||||
cond := getRolloutConditionV1beta1(rollout.Status, v1beta1.RolloutConditionProgressing)
|
||||
Expect(cond.Reason).Should(Equal(v1beta1.ProgressingReasonCompleted))
|
||||
Expect(string(cond.Status)).Should(Equal(string(metav1.ConditionFalse)))
|
||||
cond = getRolloutCondition(rollout.Status, v1alpha1.RolloutConditionSucceeded)
|
||||
cond = getRolloutConditionV1beta1(rollout.Status, v1beta1.RolloutConditionSucceeded)
|
||||
Expect(string(cond.Status)).Should(Equal(string(metav1.ConditionTrue)))
|
||||
Expect(GetObject(workload.Name, workload)).NotTo(HaveOccurred())
|
||||
WaitRolloutWorkloadGeneration(rollout.Name, workload.Generation)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
apiVersion: rollouts.kruise.io/v1alpha1
|
||||
apiVersion: rollouts.kruise.io/v1beta1
|
||||
kind: Rollout
|
||||
metadata:
|
||||
name: rollouts-demo
|
||||
annotations:
|
||||
rollouts.kruise.io/rolling-style: canary
|
||||
spec:
|
||||
disabled: false
|
||||
objectRef:
|
||||
workloadRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: echoserver
|
||||
strategy:
|
||||
canary:
|
||||
enableExtraWorkloadForCanary: true
|
||||
steps:
|
||||
- replicas: 1
|
||||
matches:
|
||||
|
|
@ -20,7 +18,14 @@
|
|||
- type: Exact
|
||||
name: user-agent
|
||||
value: pc
|
||||
- weight: 50
|
||||
- queryParams:
|
||||
- type: Exact
|
||||
name: user-agent
|
||||
value: pc
|
||||
- path:
|
||||
value: /pc
|
||||
- replicas: "50%"
|
||||
traffic: "50%"
|
||||
trafficRoutings:
|
||||
- service: echoserver
|
||||
customNetworkRefs:
|
||||
|
|
|
|||
Loading…
Reference in New Issue