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:
Jiajing LU 2024-06-12 13:21:42 +08:00 committed by GitHub
parent 3eeb7b4ddc
commit 62794dc883
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 991 additions and 79 deletions

View File

@ -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

View File

@ -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}')

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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 = {}

View File

@ -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

View File

@ -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 {

View File

@ -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 = {}

View File

@ -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
}

View File

@ -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 {

View File

@ -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{

28
pkg/util/slices_utils.go Normal file
View File

@ -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
}

View File

@ -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)

View File

@ -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: