Skip to content

Commit

Permalink
gatewayapi: add support for route rule filters
Browse files Browse the repository at this point in the history
Add support for [`Filters`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter)
in the HTTPRoute API. We reuse most of the existing fields used for
Istio to construct the appopriate filter. A new API
`.spec.service.mirror` is added to allow for request mirroring. The
`.spec.service.rewrite` API has been changed to a custom `HTTPRewrite`
API instead of importing it from Istio, to allow covering all features
that Gateway API provides.

Support for the [`RequestRedirect`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRequestRedirectFilter)
Filter has been left out on purpose, since it's not possible to specify
it if the same rule also specifies `.backendRefs` (which Flagger does).

Signed-off-by: Sanskar Jaiswal <[email protected]>
  • Loading branch information
aryan9600 committed Sep 22, 2023
1 parent 794fea8 commit c0e2096
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 9 deletions.
48 changes: 48 additions & 0 deletions artifacts/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,54 @@ spec:
uri:
format: string
type: string
authority:
format: string
type: string
type:
format: string
type: string
mirror:
description: Mirror defines a schema for a filter that mirrors requests.
type: array
items:
type: object
properties:
backendRef:
properties:
group:
default: ""
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
default: Service
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
maxLength: 253
minLength: 1
type: string
namespace:
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
port:
format: int32
maximum: 65535
minimum: 1
type: integer
required:
- name
type: object
x-kubernetes-validations:
- message: Must have port for Service reference
rule: '(size(self.group) == 0 && self.kind == ''Service'')
? has(self.port) : true'
required:
- backendRef
headers:
description: Headers operations
type: object
Expand Down
48 changes: 48 additions & 0 deletions charts/flagger/crds/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,54 @@ spec:
uri:
format: string
type: string
authority:
format: string
type: string
type:
format: string
type: string
mirror:
description: Mirror defines a schema for a filter that mirrors requests.
type: array
items:
type: object
properties:
backendRef:
properties:
group:
default: ""
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
default: Service
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
maxLength: 253
minLength: 1
type: string
namespace:
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
port:
format: int32
maximum: 65535
minimum: 1
type: integer
required:
- name
type: object
x-kubernetes-validations:
- message: Must have port for Service reference
rule: '(size(self.group) == 0 && self.kind == ''Service'')
? has(self.port) : true'
required:
- backendRef
headers:
description: Headers operations
type: object
Expand Down
48 changes: 48 additions & 0 deletions kustomize/base/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,54 @@ spec:
uri:
format: string
type: string
authority:
format: string
type: string
type:
format: string
type: string
mirror:
description: Mirror defines a schema for a filter that mirrors requests.
type: array
items:
type: object
properties:
backendRef:
properties:
group:
default: ""
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
default: Service
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
maxLength: 253
minLength: 1
type: string
namespace:
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
port:
format: int32
maximum: 65535
minimum: 1
type: integer
required:
- name
type: object
x-kubernetes-validations:
- message: Must have port for Service reference
rule: '(size(self.group) == 0 && self.kind == ''Service'')
? has(self.port) : true'
required:
- backendRef
headers:
description: Headers operations
type: object
Expand Down
41 changes: 40 additions & 1 deletion pkg/apis/flagger/v1beta1/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ type CanaryService struct {

// Rewrite HTTP URIs for the generated service
// +optional
Rewrite *istiov1alpha3.HTTPRewrite `json:"rewrite,omitempty"`
Rewrite *HTTPRewrite `json:"rewrite,omitempty"`

// Retries policy for the generated virtual service
// +optional
Expand All @@ -191,6 +191,10 @@ type CanaryService struct {
// +optional
Headers *istiov1alpha3.Headers `json:"headers,omitempty"`

// Mirror specifies the destination for request mirroring.
// Responses from this destination are dropped.
Mirror []v1beta1.HTTPRequestMirrorFilter `json:"mirror,omitempty"`

// Cross-Origin Resource Sharing policy for the generated Istio virtual service
// +optional
CorsPolicy *istiov1alpha3.CorsPolicy `json:"corsPolicy,omitempty"`
Expand Down Expand Up @@ -484,6 +488,41 @@ type CustomMetadata struct {
Annotations map[string]string `json:"annotations,omitempty"`
}

// HTTPRewrite holds information about how to modify a request URI during
// forwarding.
type HTTPRewrite struct {
// rewrite the path (or the prefix) portion of the URI with this
// value. If the original URI was matched based on prefix, the value
// provided in this field will replace the corresponding matched prefix.
Uri string `json:"uri,omitempty"`

// rewrite the Authority/Host header with this value.
Authority string `json:"authority,omitempty"`

// Type is the type of path modification to make.
// +optional
Type string `json:"type,omitempty"`
}

// GetType returns the type of HTTP path rewrite to be performed.
func (r *HTTPRewrite) GetType() string {
if r.Type == string(v1beta1.PrefixMatchHTTPPathModifier) {
return r.Type
}
return string(v1beta1.FullPathHTTPPathModifier)
}

// GetIstioRewrite returns a istiov1alpha3.HTTPRewrite object.
func (s *CanaryService) GetIstioRewrite() *istiov1alpha3.HTTPRewrite {
if s.Rewrite != nil {
return &istiov1alpha3.HTTPRewrite{
Authority: s.Rewrite.Authority,
Uri: s.Rewrite.Uri,
}
}
return nil
}

// GetMaxAge returns the max age of a cookie in seconds.
func (s *SessionAffinity) GetMaxAge() int {
if s.MaxAge == 0 {
Expand Down
25 changes: 24 additions & 1 deletion pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions pkg/router/gateway_api_v1beta1.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error {
Rules: []v1beta1.HTTPRouteRule{
{
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1beta1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
Expand All @@ -106,6 +107,7 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error {
httpRouteSpec.Rules[0].Matches = gwr.mergeMatchConditions(analysisMatches, matches)
httpRouteSpec.Rules = append(httpRouteSpec.Rules, v1beta1.HTTPRouteRule{
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1beta1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
Expand Down Expand Up @@ -295,6 +297,7 @@ func (gwr *GatewayAPIV1Beta1Router) SetRoutes(
}
weightedRouteRule := &v1beta1.HTTPRouteRule{
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1beta1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, pWeight, canary.Spec.Service.Port),
Expand Down Expand Up @@ -330,6 +333,7 @@ func (gwr *GatewayAPIV1Beta1Router) SetRoutes(
hrClone.Spec.Rules[0].Matches = gwr.mergeMatchConditions(analysisMatches, matches)
hrClone.Spec.Rules = append(hrClone.Spec.Rules, v1beta1.HTTPRouteRule{
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1beta1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
Expand Down Expand Up @@ -570,3 +574,94 @@ func (gwr *GatewayAPIV1Beta1Router) mergeMatchConditions(analysis, service []v1b
}
return merged
}

func (gwr *GatewayAPIV1Beta1Router) makeFilters(canary *flaggerv1.Canary) []v1beta1.HTTPRouteFilter {
var filters []v1beta1.HTTPRouteFilter

if canary.Spec.Service.Headers != nil {
if canary.Spec.Service.Headers.Request != nil {
requestHeaderFilter := v1beta1.HTTPRouteFilter{
Type: v1beta1.HTTPRouteFilterRequestHeaderModifier,
RequestHeaderModifier: &v1beta1.HTTPHeaderFilter{},
}

for name, val := range canary.Spec.Service.Headers.Request.Add {
requestHeaderFilter.RequestHeaderModifier.Add = append(requestHeaderFilter.RequestHeaderModifier.Add, v1beta1.HTTPHeader{
Name: v1beta1.HTTPHeaderName(name),
Value: val,
})
}
for name, val := range canary.Spec.Service.Headers.Request.Set {
requestHeaderFilter.RequestHeaderModifier.Set = append(requestHeaderFilter.RequestHeaderModifier.Set, v1beta1.HTTPHeader{
Name: v1beta1.HTTPHeaderName(name),
Value: val,
})
}

for _, name := range canary.Spec.Service.Headers.Request.Remove {
requestHeaderFilter.RequestHeaderModifier.Remove = append(requestHeaderFilter.RequestHeaderModifier.Remove, name)
}

filters = append(filters, requestHeaderFilter)
}
if canary.Spec.Service.Headers.Response != nil {
responseHeaderFilter := v1beta1.HTTPRouteFilter{
Type: v1beta1.HTTPRouteFilterResponseHeaderModifier,
ResponseHeaderModifier: &v1beta1.HTTPHeaderFilter{},
}

for name, val := range canary.Spec.Service.Headers.Response.Add {
responseHeaderFilter.ResponseHeaderModifier.Add = append(responseHeaderFilter.ResponseHeaderModifier.Add, v1beta1.HTTPHeader{
Name: v1beta1.HTTPHeaderName(name),
Value: val,
})
}
for name, val := range canary.Spec.Service.Headers.Response.Set {
responseHeaderFilter.ResponseHeaderModifier.Set = append(responseHeaderFilter.ResponseHeaderModifier.Set, v1beta1.HTTPHeader{
Name: v1beta1.HTTPHeaderName(name),
Value: val,
})
}

for _, name := range canary.Spec.Service.Headers.Response.Remove {
responseHeaderFilter.ResponseHeaderModifier.Remove = append(responseHeaderFilter.ResponseHeaderModifier.Remove, name)
}

filters = append(filters, responseHeaderFilter)
}
}

if canary.Spec.Service.Rewrite != nil {
rewriteFilter := v1beta1.HTTPRouteFilter{
Type: v1beta1.HTTPRouteFilterURLRewrite,
URLRewrite: &v1beta1.HTTPURLRewriteFilter{},
}
if canary.Spec.Service.Rewrite.Authority != "" {
hostname := v1beta1.PreciseHostname(canary.Spec.Service.Rewrite.Authority)
rewriteFilter.URLRewrite.Hostname = &hostname
}
if canary.Spec.Service.Rewrite.Uri != "" {
rewriteFilter.URLRewrite.Path = &v1beta1.HTTPPathModifier{
Type: v1beta1.HTTPPathModifierType(canary.Spec.Service.Rewrite.GetType()),
}
if rewriteFilter.URLRewrite.Path.Type == v1beta1.FullPathHTTPPathModifier {
rewriteFilter.URLRewrite.Path.ReplaceFullPath = &canary.Spec.Service.Rewrite.Uri
} else {
rewriteFilter.URLRewrite.Path.ReplacePrefixMatch = &canary.Spec.Service.Rewrite.Uri
}
}

filters = append(filters, rewriteFilter)
}

for _, mirror := range canary.Spec.Service.Mirror {
mirror := mirror
mirrorFilter := v1beta1.HTTPRouteFilter{
Type: v1beta1.HTTPRouteFilterRequestMirror,
RequestMirror: &mirror,
}
filters = append(filters, mirrorFilter)
}

return filters
}
Loading

0 comments on commit c0e2096

Please sign in to comment.