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,26 +47,40 @@ function GenerateRoutesWithMatches(spec, matches, stableService, canaryService)
 | 
			
		|||
    for _, match in ipairs(matches) do
 | 
			
		||||
        local route = {}
 | 
			
		||||
        route["match"] = {}
 | 
			
		||||
 | 
			
		||||
        local vsMatch = {}
 | 
			
		||||
        for key, value in pairs(match) do
 | 
			
		||||
            local vsMatch = {}
 | 
			
		||||
            vsMatch[key] = {}
 | 
			
		||||
            for _, rule in ipairs(value) 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"] == "Prefix" then
 | 
			
		||||
                elseif rule["type"] == "PathPrefix" then
 | 
			
		||||
                    matchType = "prefix"
 | 
			
		||||
                end
 | 
			
		||||
                if key == "headers" then
 | 
			
		||||
                    vsMatch[key][rule["name"]] = {}
 | 
			
		||||
                    vsMatch[key][rule["name"]][matchType] = rule.value
 | 
			
		||||
                else
 | 
			
		||||
                    vsMatch[key][matchType] = rule.value
 | 
			
		||||
                vsMatch["uri"][matchType] = rule.value
 | 
			
		||||
            else
 | 
			
		||||
                vsMatch[key] = {}
 | 
			
		||||
                for _, rule in ipairs(value) do
 | 
			
		||||
                    if rule["type"] == "RegularExpression" then
 | 
			
		||||
                        matchType = "regex"
 | 
			
		||||
                    elseif rule["type"] == "Exact" then
 | 
			
		||||
                        matchType = "exact"
 | 
			
		||||
                    elseif rule["type"] == "Prefix" then
 | 
			
		||||
                        matchType = "prefix"
 | 
			
		||||
                    end
 | 
			
		||||
                    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
 | 
			
		||||
            end
 | 
			
		||||
            table.insert(route["match"], vsMatch)
 | 
			
		||||
        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,18 +37,30 @@ then
 | 
			
		|||
    return annotations
 | 
			
		||||
end
 | 
			
		||||
for _,match in ipairs(obj.matches) do
 | 
			
		||||
    header = match.headers[1]
 | 
			
		||||
    if ( header.name == "canary-by-cookie" )
 | 
			
		||||
    then
 | 
			
		||||
        annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value
 | 
			
		||||
    else
 | 
			
		||||
        annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name
 | 
			
		||||
        if ( header.type == "RegularExpression" )
 | 
			
		||||
    if match.headers and next(match.headers) ~= nil then
 | 
			
		||||
        header = match.headers[1]
 | 
			
		||||
        if ( header.name == "canary-by-cookie" )
 | 
			
		||||
        then
 | 
			
		||||
            annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value
 | 
			
		||||
            annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value
 | 
			
		||||
        else
 | 
			
		||||
            annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value
 | 
			
		||||
            annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name
 | 
			
		||||
            if ( header.type == "RegularExpression" )
 | 
			
		||||
            then
 | 
			
		||||
                annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value
 | 
			
		||||
            else
 | 
			
		||||
                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
 | 
			
		||||
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,26 +103,39 @@ function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stab
 | 
			
		|||
        local route = {}
 | 
			
		||||
        route["match"] = {}
 | 
			
		||||
 | 
			
		||||
        local vsMatch = {}
 | 
			
		||||
        for key, value in pairs(match) do
 | 
			
		||||
            local vsMatch = {}
 | 
			
		||||
            vsMatch[key] = {}
 | 
			
		||||
            for _, rule in ipairs(value) 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"] == "Prefix" then
 | 
			
		||||
                elseif rule["type"] == "PathPrefix" then
 | 
			
		||||
                    matchType = "prefix"
 | 
			
		||||
                end
 | 
			
		||||
                if key == "headers" then
 | 
			
		||||
                    vsMatch[key][rule["name"]] = {}
 | 
			
		||||
                    vsMatch[key][rule["name"]][matchType] = rule.value
 | 
			
		||||
                else
 | 
			
		||||
                    vsMatch[key][matchType] = rule.value
 | 
			
		||||
                vsMatch["uri"][matchType] = rule.value
 | 
			
		||||
            else
 | 
			
		||||
                vsMatch[key] = {}
 | 
			
		||||
                for _, rule in ipairs(value) do
 | 
			
		||||
                    if rule["type"] == "RegularExpression" then
 | 
			
		||||
                        matchType = "regex"
 | 
			
		||||
                    elseif rule["type"] == "Exact" then
 | 
			
		||||
                        matchType = "exact"
 | 
			
		||||
                    elseif rule["type"] == "Prefix" then
 | 
			
		||||
                        matchType = "prefix"
 | 
			
		||||
                    end
 | 
			
		||||
                    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
 | 
			
		||||
            end
 | 
			
		||||
            table.insert(route["match"], vsMatch)
 | 
			
		||||
        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,17 +83,29 @@ var (
 | 
			
		|||
					return annotations
 | 
			
		||||
				end
 | 
			
		||||
				for _,match in ipairs(obj.matches) do
 | 
			
		||||
					header = match.headers[1]
 | 
			
		||||
					if ( header.name == "canary-by-cookie" )
 | 
			
		||||
					then
 | 
			
		||||
						annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value
 | 
			
		||||
					else
 | 
			
		||||
						annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name
 | 
			
		||||
						if ( header.type == "RegularExpression" )
 | 
			
		||||
					if match.headers and next(match.headers) ~= nil then
 | 
			
		||||
						header = match.headers[1]
 | 
			
		||||
						if ( header.name == "canary-by-cookie" )
 | 
			
		||||
						then
 | 
			
		||||
							annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value
 | 
			
		||||
							annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value
 | 
			
		||||
						else
 | 
			
		||||
							annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value
 | 
			
		||||
							annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name
 | 
			
		||||
							if ( header.type == "RegularExpression" )
 | 
			
		||||
							then
 | 
			
		||||
								annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value
 | 
			
		||||
							else
 | 
			
		||||
								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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    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