rollout trafficrouting support requestHeaderModifier (#156)
Signed-off-by: liheng.zms <liheng.zms@alibaba-inc.com>
This commit is contained in:
parent
8737f336f0
commit
1d343d5a26
|
|
@ -18,6 +18,7 @@ package v1alpha1
|
|||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -86,7 +87,7 @@ type TrafficRoutingStrategy struct {
|
|||
// my-header: bar
|
||||
//
|
||||
// +optional
|
||||
// RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter `json:"requestHeaderModifier,omitempty"`
|
||||
RequestHeaderModifier *gatewayv1alpha2.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.
|
||||
|
|
|
|||
|
|
@ -920,6 +920,11 @@ func (in *TrafficRoutingStrategy) DeepCopyInto(out *TrafficRoutingStrategy) {
|
|||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.RequestHeaderModifier != nil {
|
||||
in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
|
||||
*out = new(v1alpha2.HTTPRequestHeaderFilter)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Matches != nil {
|
||||
in, out := &in.Matches, &out.Matches
|
||||
*out = make([]HttpRouteMatch, len(*in))
|
||||
|
|
|
|||
|
|
@ -111,20 +111,13 @@ spec:
|
|||
description: CanaryStep defines a step of a canary workload.
|
||||
properties:
|
||||
matches:
|
||||
description: "Set overwrites the request with the given
|
||||
header (name, value) before the action. \n Input:
|
||||
\ GET /foo HTTP/1.1 my-header: foo \n requestHeaderModifier:
|
||||
\ set: - name: \"my-header\" value: \"bar\"
|
||||
\n Output: GET /foo HTTP/1.1 my-header: bar \n
|
||||
RequestHeaderModifier *gatewayv1alpha2.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."
|
||||
description: 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.
|
||||
items:
|
||||
properties:
|
||||
headers:
|
||||
|
|
@ -206,6 +199,110 @@ spec:
|
|||
pods in this batch it can be an absolute number (ex:
|
||||
5) or a percentage of total pods.'
|
||||
x-kubernetes-int-or-string: true
|
||||
requestHeaderModifier:
|
||||
description: "Set overwrites the request with the given
|
||||
header (name, value) before the action. \n Input:
|
||||
\ GET /foo HTTP/1.1 my-header: foo \n requestHeaderModifier:
|
||||
\ set: - name: \"my-header\" value: \"bar\"
|
||||
\n Output: GET /foo HTTP/1.1 my-header: bar"
|
||||
properties:
|
||||
add:
|
||||
description: "Add adds the given header(s) (name,
|
||||
value) to the request before the action. It appends
|
||||
to any existing values associated with the header
|
||||
name. \n Input: GET /foo HTTP/1.1 my-header:
|
||||
foo \n Config: add: - name: \"my-header\"
|
||||
\ value: \"bar\" \n Output: GET /foo HTTP/1.1
|
||||
\ my-header: foo my-header: bar"
|
||||
items:
|
||||
description: HTTPHeader represents an HTTP Header
|
||||
name and value as defined by RFC 7230.
|
||||
properties:
|
||||
name:
|
||||
description: "Name is the name of the HTTP
|
||||
Header to be matched. Name matching MUST
|
||||
be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
|
||||
\n If multiple entries specify equivalent
|
||||
header names, the first entry with an equivalent
|
||||
name MUST be considered for a match. Subsequent
|
||||
entries with an equivalent header name MUST
|
||||
be ignored. Due to the case-insensitivity
|
||||
of header names, \"foo\" and \"Foo\" are
|
||||
considered equivalent."
|
||||
maxLength: 256
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
|
||||
type: string
|
||||
value:
|
||||
description: Value is the value of HTTP Header
|
||||
to be matched.
|
||||
maxLength: 4096
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
type: object
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
remove:
|
||||
description: "Remove the given header(s) from the
|
||||
HTTP request before the action. The value of Remove
|
||||
is a list of HTTP header names. Note that the
|
||||
header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).
|
||||
\n Input: GET /foo HTTP/1.1 my-header1: foo
|
||||
\ my-header2: bar my-header3: baz \n Config:
|
||||
\ remove: [\"my-header1\", \"my-header3\"] \n
|
||||
Output: GET /foo HTTP/1.1 my-header2: bar"
|
||||
items:
|
||||
type: string
|
||||
maxItems: 16
|
||||
type: array
|
||||
set:
|
||||
description: "Set overwrites the request with the
|
||||
given header (name, value) before the action.
|
||||
\n Input: GET /foo HTTP/1.1 my-header: foo
|
||||
\n Config: set: - name: \"my-header\" value:
|
||||
\"bar\" \n Output: GET /foo HTTP/1.1 my-header:
|
||||
bar"
|
||||
items:
|
||||
description: HTTPHeader represents an HTTP Header
|
||||
name and value as defined by RFC 7230.
|
||||
properties:
|
||||
name:
|
||||
description: "Name is the name of the HTTP
|
||||
Header to be matched. Name matching MUST
|
||||
be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
|
||||
\n If multiple entries specify equivalent
|
||||
header names, the first entry with an equivalent
|
||||
name MUST be considered for a match. Subsequent
|
||||
entries with an equivalent header name MUST
|
||||
be ignored. Due to the case-insensitivity
|
||||
of header names, \"foo\" and \"Foo\" are
|
||||
considered equivalent."
|
||||
maxLength: 256
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
|
||||
type: string
|
||||
value:
|
||||
description: Value is the value of HTTP Header
|
||||
to be matched.
|
||||
maxLength: 4096
|
||||
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
|
||||
weight:
|
||||
description: Weight indicate how many percentage of
|
||||
traffic the canary pods should receive
|
||||
|
|
|
|||
|
|
@ -98,17 +98,12 @@ spec:
|
|||
description: trafficrouting strategy
|
||||
properties:
|
||||
matches:
|
||||
description: "Set overwrites the request with the given header
|
||||
(name, value) before the action. \n Input: GET /foo HTTP/1.1
|
||||
\ my-header: foo \n requestHeaderModifier: set: - name:
|
||||
\"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1
|
||||
\ my-header: bar \n RequestHeaderModifier *gatewayv1alpha2.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."
|
||||
description: 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.
|
||||
items:
|
||||
properties:
|
||||
headers:
|
||||
|
|
@ -167,6 +162,106 @@ spec:
|
|||
type: array
|
||||
type: object
|
||||
type: array
|
||||
requestHeaderModifier:
|
||||
description: "Set overwrites the request with the given header
|
||||
(name, value) before the action. \n Input: GET /foo HTTP/1.1
|
||||
\ my-header: foo \n requestHeaderModifier: set: - name:
|
||||
\"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1
|
||||
\ my-header: bar"
|
||||
properties:
|
||||
add:
|
||||
description: "Add adds the given header(s) (name, value) to
|
||||
the request before the action. It appends to any existing
|
||||
values associated with the header name. \n Input: GET
|
||||
/foo HTTP/1.1 my-header: foo \n Config: add: - name:
|
||||
\"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1
|
||||
\ my-header: foo my-header: bar"
|
||||
items:
|
||||
description: HTTPHeader represents an HTTP Header name and
|
||||
value as defined by RFC 7230.
|
||||
properties:
|
||||
name:
|
||||
description: "Name is the name of the HTTP Header to
|
||||
be matched. Name matching MUST be case insensitive.
|
||||
(See https://tools.ietf.org/html/rfc7230#section-3.2).
|
||||
\n If multiple entries specify equivalent header names,
|
||||
the first entry with an equivalent name MUST be considered
|
||||
for a match. Subsequent entries with an equivalent
|
||||
header name MUST be ignored. Due to the case-insensitivity
|
||||
of header names, \"foo\" and \"Foo\" are considered
|
||||
equivalent."
|
||||
maxLength: 256
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
|
||||
type: string
|
||||
value:
|
||||
description: Value is the value of HTTP Header to be
|
||||
matched.
|
||||
maxLength: 4096
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
type: object
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
remove:
|
||||
description: "Remove the given header(s) from the HTTP request
|
||||
before the action. The value of Remove is a list of HTTP
|
||||
header names. Note that the header names are case-insensitive
|
||||
(see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).
|
||||
\n Input: GET /foo HTTP/1.1 my-header1: foo my-header2:
|
||||
bar my-header3: baz \n Config: remove: [\"my-header1\",
|
||||
\"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2:
|
||||
bar"
|
||||
items:
|
||||
type: string
|
||||
maxItems: 16
|
||||
type: array
|
||||
set:
|
||||
description: "Set overwrites the request with the given header
|
||||
(name, value) before the action. \n Input: GET /foo HTTP/1.1
|
||||
\ my-header: foo \n Config: set: - name: \"my-header\"
|
||||
\ value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header:
|
||||
bar"
|
||||
items:
|
||||
description: HTTPHeader represents an HTTP Header name and
|
||||
value as defined by RFC 7230.
|
||||
properties:
|
||||
name:
|
||||
description: "Name is the name of the HTTP Header to
|
||||
be matched. Name matching MUST be case insensitive.
|
||||
(See https://tools.ietf.org/html/rfc7230#section-3.2).
|
||||
\n If multiple entries specify equivalent header names,
|
||||
the first entry with an equivalent name MUST be considered
|
||||
for a match. Subsequent entries with an equivalent
|
||||
header name MUST be ignored. Due to the case-insensitivity
|
||||
of header names, \"foo\" and \"Foo\" are considered
|
||||
equivalent."
|
||||
maxLength: 256
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
|
||||
type: string
|
||||
value:
|
||||
description: Value is the value of HTTP Header to be
|
||||
matched.
|
||||
maxLength: 4096
|
||||
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
|
||||
weight:
|
||||
description: Weight indicate how many percentage of traffic the
|
||||
canary pods should receive
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
)
|
||||
|
||||
type ingressController struct {
|
||||
|
|
@ -84,7 +85,7 @@ func (r *ingressController) Initialize(ctx context.Context) error {
|
|||
func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) {
|
||||
weight := strategy.Weight
|
||||
matches := strategy.Matches
|
||||
// headerModifier := strategy.RequestHeaderModifier
|
||||
headerModifier := strategy.RequestHeaderModifier
|
||||
|
||||
canaryIngress := &netv1.Ingress{}
|
||||
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: defaultCanaryIngressName(r.conf.TrafficConf.Name)}, canaryIngress)
|
||||
|
|
@ -101,7 +102,7 @@ func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *rolloutv
|
|||
}
|
||||
// build and create canary ingress
|
||||
canaryIngress = r.buildCanaryIngress(ingress)
|
||||
canaryIngress.Annotations, err = r.executeLuaForCanary(canaryIngress.Annotations, utilpointer.Int32(0), nil)
|
||||
canaryIngress.Annotations, err = r.executeLuaForCanary(canaryIngress.Annotations, utilpointer.Int32(0), nil, nil)
|
||||
if err != nil {
|
||||
klog.Errorf("%s execute lua failed: %s", r.conf.Key, err.Error())
|
||||
return false, err
|
||||
|
|
@ -116,7 +117,7 @@ func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *rolloutv
|
|||
klog.Errorf("%s get canary ingress failed: %s", r.conf.Key, err.Error())
|
||||
return false, err
|
||||
}
|
||||
newAnnotations, err := r.executeLuaForCanary(canaryIngress.Annotations, weight, matches)
|
||||
newAnnotations, err := r.executeLuaForCanary(canaryIngress.Annotations, weight, matches, headerModifier)
|
||||
if err != nil {
|
||||
klog.Errorf("%s execute lua failed: %s", r.conf.Key, err.Error())
|
||||
return false, err
|
||||
|
|
@ -216,7 +217,8 @@ func defaultCanaryIngressName(name string) string {
|
|||
return fmt.Sprintf("%s-canary", name)
|
||||
}
|
||||
|
||||
func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch) (map[string]string, error) {
|
||||
func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch,
|
||||
headerModifier *gatewayv1alpha2.HTTPRequestHeaderFilter) (map[string]string, error) {
|
||||
|
||||
if weight == nil {
|
||||
// the lua script does not have a pointer type,
|
||||
|
|
@ -228,14 +230,14 @@ func (r *ingressController) executeLuaForCanary(annotations map[string]string, w
|
|||
Weight string
|
||||
Matches []rolloutv1alpha1.HttpRouteMatch
|
||||
CanaryService string
|
||||
//todo, RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter
|
||||
RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter
|
||||
}
|
||||
data := &LuaData{
|
||||
Annotations: annotations,
|
||||
Weight: fmt.Sprintf("%d", *weight),
|
||||
Matches: matches,
|
||||
CanaryService: r.conf.CanaryService,
|
||||
// RequestHeaderModifier: headerModifier,
|
||||
RequestHeaderModifier: headerModifier,
|
||||
}
|
||||
unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(data)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
/*RequestHeaderModifier: &gatewayv1alpha2.HTTPRequestHeaderFilter{
|
||||
RequestHeaderModifier: &gatewayv1alpha2.HTTPRequestHeaderFilter{
|
||||
Set: []gatewayv1alpha2.HTTPHeader{
|
||||
{
|
||||
Name: "gray",
|
||||
|
|
@ -357,7 +357,7 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
Value: "green",
|
||||
},
|
||||
},
|
||||
},*/
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
@ -368,7 +368,7 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = "demo"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-header"] = "user_id"
|
||||
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = "123456"
|
||||
//expect.Annotations["mse.ingress.kubernetes.io/request-header-control-update"] = "gray blue\ngray green\n"
|
||||
expect.Annotations["mse.ingress.kubernetes.io/request-header-control-update"] = "gray blue\ngray green\n"
|
||||
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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue