Skip to content

Commit

Permalink
feat(gateway-api): Add custom backendRef and filters support for HTTP…
Browse files Browse the repository at this point in the history
…Route

Signed-off-by: kahirokunn <okinakahiro@gmail.com>
  • Loading branch information
kahirokunn committed Dec 16, 2024
1 parent a85887d commit e86aec0
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 49 deletions.
37 changes: 35 additions & 2 deletions pkg/apis/flagger/v1beta1/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"time"

v1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1"
"github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1"
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -213,11 +214,11 @@ type CanaryService struct {

// Primary is the metadata to add to the primary service
// +optional
Primary *CustomMetadata `json:"primary,omitempty"`
Primary *CustomBackend `json:"primary,omitempty"`

// Canary is the metadata to add to the canary service
// +optional
Canary *CustomMetadata `json:"canary,omitempty"`
Canary *CustomBackend `json:"canary,omitempty"`
}

// CanaryAnalysis is used to describe how the analysis should be done
Expand Down Expand Up @@ -496,6 +497,38 @@ type CustomMetadata struct {
Annotations map[string]string `json:"annotations,omitempty"`
}

// CustomBackend holds labels, annotations, and proxyRef to set on generated objects.
type CustomBackend struct {
CustomMetadata

// Backend is a reference to a backend to forward matched requests to.
// Defaults to the primary or canary service.
// +optional
Backend *HTTPBackendRefTemplate `json:"backendRef,omitempty"`
}

// HTTPBackendRefTemplate is a reference to a backend to forward matched requests to.
type HTTPBackendRefTemplate struct {
// Ref references a Kubernetes object.
BackendObjectReference *v1.BackendObjectReference `json:"ref,omitempty"`

// Filters defined at this level should be executed if and only if the
// request is being forwarded to the backend defined here.
//
// Support: Implementation-specific (For broader support of filters, use the
// Filters field in HTTPRouteRule.)
//
// +optional
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:validation:XValidation:message="May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both",rule="!(self.exists(f, f.type == 'RequestRedirect') && self.exists(f, f.type == 'URLRewrite'))"
// +kubebuilder:validation:XValidation:message="May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both",rule="!(self.exists(f, f.type == 'RequestRedirect') && self.exists(f, f.type == 'URLRewrite'))"
// +kubebuilder:validation:XValidation:message="RequestHeaderModifier filter cannot be repeated",rule="self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1"
// +kubebuilder:validation:XValidation:message="ResponseHeaderModifier filter cannot be repeated",rule="self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1"
// +kubebuilder:validation:XValidation:message="RequestRedirect filter cannot be repeated",rule="self.filter(f, f.type == 'RequestRedirect').size() <= 1"
// +kubebuilder:validation:XValidation:message="URLRewrite filter cannot be repeated",rule="self.filter(f, f.type == 'URLRewrite').size() <= 1"
Filters []v1.HTTPRouteFilter `json:"filters,omitempty"`
}

// HTTPRewrite holds information about how to modify a request URI during
// forwarding.
type HTTPRewrite struct {
Expand Down
55 changes: 53 additions & 2 deletions 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.

60 changes: 28 additions & 32 deletions pkg/router/gateway_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,8 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error {
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
},
{
BackendRef: gwr.makeBackendRef(canarySvcName, initialCanaryWeight, canary.Spec.Service.Port),
},
gwr.makeHTTPBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port, canary.Spec.Service.Primary),
gwr.makeHTTPBackendRef(canarySvcName, initialCanaryWeight, canary.Spec.Service.Port, canary.Spec.Service.Canary),
},
},
},
Expand All @@ -122,9 +118,7 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error {
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
},
gwr.makeHTTPBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port, canary.Spec.Service.Primary),
},
})
if canary.Spec.Service.Timeout != "" {
Expand Down Expand Up @@ -340,12 +334,8 @@ func (gwr *GatewayAPIRouter) SetRoutes(
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, pWeight, canary.Spec.Service.Port),
},
{
BackendRef: gwr.makeBackendRef(canarySvcName, cWeight, canary.Spec.Service.Port),
},
gwr.makeHTTPBackendRef(primarySvcName, pWeight, canary.Spec.Service.Port, canary.Spec.Service.Primary),
gwr.makeHTTPBackendRef(canarySvcName, cWeight, canary.Spec.Service.Port, canary.Spec.Service.Canary),
},
}
if canary.Spec.Service.Timeout != "" {
Expand Down Expand Up @@ -399,9 +389,7 @@ func (gwr *GatewayAPIRouter) SetRoutes(
Matches: matches,
Filters: gwr.makeFilters(canary),
BackendRefs: []v1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
},
gwr.makeHTTPBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port, canary.Spec.Service.Primary),
},
Timeouts: &v1.HTTPRouteTimeouts{
Request: &timeout,
Expand Down Expand Up @@ -484,12 +472,8 @@ func (gwr *GatewayAPIRouter) getSessionAffinityRouteRules(canary *flaggerv1.Cana
mergedMatches := gwr.mergeMatchConditions([]v1.HTTPRouteMatch{cookieMatch}, svcMatches)
stickyRouteRule.Matches = mergedMatches
stickyRouteRule.BackendRefs = []v1.HTTPBackendRef{
{
BackendRef: gwr.makeBackendRef(primarySvcName, 0, canary.Spec.Service.Port),
},
{
BackendRef: gwr.makeBackendRef(canarySvcName, 100, canary.Spec.Service.Port),
},
gwr.makeHTTPBackendRef(primarySvcName, 0, canary.Spec.Service.Port, canary.Spec.Service.Primary),
gwr.makeHTTPBackendRef(canarySvcName, 100, canary.Spec.Service.Port, canary.Spec.Service.Canary),
}
} else {
// If canary weight is 0 and SessionAffinityCookie is non-blank, then it belongs to a previous canary run.
Expand Down Expand Up @@ -612,16 +596,28 @@ func (gwr *GatewayAPIRouter) mapRouteMatches(requestMatches []istiov1beta1.HTTPM
return matches, nil
}

func (gwr *GatewayAPIRouter) makeBackendRef(svcName string, weight, port int32) v1.BackendRef {
return v1.BackendRef{
BackendObjectReference: v1.BackendObjectReference{
Group: (*v1.Group)(&backendRefGroup),
Kind: (*v1.Kind)(&backendRefKind),
Name: v1.ObjectName(svcName),
Port: (*v1.PortNumber)(&port),
func (gwr *GatewayAPIRouter) makeHTTPBackendRef(svcName string, weight, port int32, customBackend *flaggerv1.CustomBackend) v1.HTTPBackendRef {
httpBackendRef := v1.HTTPBackendRef{
BackendRef: v1.BackendRef{
BackendObjectReference: v1.BackendObjectReference{
Group: (*v1.Group)(&backendRefGroup),
Kind: (*v1.Kind)(&backendRefKind),
Name: v1.ObjectName(svcName),
Port: (*v1.PortNumber)(&port),
},
Weight: &weight,
},
Weight: &weight,
}
if customBackend != nil && customBackend.Backend != nil {
if customBackend.Backend.BackendObjectReference != nil {
httpBackendRef.BackendObjectReference = *customBackend.Backend.BackendObjectReference
}
if customBackend.Backend.Filters != nil {
httpBackendRef.Filters = customBackend.Backend.Filters
}
}

return httpBackendRef
}

func (gwr *GatewayAPIRouter) mergeMatchConditions(analysis, service []v1.HTTPRouteMatch) []v1.HTTPRouteMatch {
Expand Down
8 changes: 2 additions & 6 deletions pkg/router/gateway_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,8 @@ func TestGatewayAPIRouter_getSessionAffinityRouteRules(t *testing.T) {
_, pSvcName, cSvcName := canary.GetServiceNames()
weightedRouteRule := &v1.HTTPRouteRule{
BackendRefs: []v1.HTTPBackendRef{
{
BackendRef: router.makeBackendRef(pSvcName, initialPrimaryWeight, canary.Spec.Service.Port),
},
{
BackendRef: router.makeBackendRef(cSvcName, initialCanaryWeight, canary.Spec.Service.Port),
},
router.makeHTTPBackendRef(pSvcName, initialPrimaryWeight, canary.Spec.Service.Port, canary.Spec.Service.Primary),
router.makeHTTPBackendRef(cSvcName, initialCanaryWeight, canary.Spec.Service.Port, canary.Spec.Service.Canary),
},
}
rules, err := router.getSessionAffinityRouteRules(canary, 10, weightedRouteRule)
Expand Down
12 changes: 10 additions & 2 deletions pkg/router/kubernetes_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,21 @@ func (c *KubernetesDefaultRouter) Initialize(canary *flaggerv1.Canary) error {
_, primaryName, canaryName := canary.GetServiceNames()

// canary svc
err := c.reconcileService(canary, canaryName, c.labelValue, canary.Spec.Service.Canary)
canaryCustomMetadata := &flaggerv1.CustomMetadata{}
if canary.Spec.Service.Canary != nil {
canaryCustomMetadata = &canary.Spec.Service.Canary.CustomMetadata
}
err := c.reconcileService(canary, canaryName, c.labelValue, canaryCustomMetadata)
if err != nil {
return fmt.Errorf("reconcileService failed: %w", err)
}

// primary svc
err = c.reconcileService(canary, primaryName, fmt.Sprintf("%s-primary", c.labelValue), canary.Spec.Service.Primary)
primaryCustomMetadata := &flaggerv1.CustomMetadata{}
if canary.Spec.Service.Primary != nil {
primaryCustomMetadata = &canary.Spec.Service.Primary.CustomMetadata
}
err = c.reconcileService(canary, primaryName, fmt.Sprintf("%s-primary", c.labelValue), primaryCustomMetadata)
if err != nil {
return fmt.Errorf("reconcileService failed: %w", err)
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/router/kubernetes_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,13 @@ func TestServiceRouter_InitializeMetadata(t *testing.T) {
labelSelector: "app",
}

metadata := &flaggerv1.CustomMetadata{
Labels: map[string]string{"test": "test"},
Annotations: map[string]string{"test": "test"},
mocks.canary.Spec.Service.Canary = &flaggerv1.CustomBackend{
CustomMetadata: flaggerv1.CustomMetadata{
Labels: map[string]string{"test": "test"},
Annotations: map[string]string{"test": "test"},
},
}

mocks.canary.Spec.Service.Canary = metadata

err := router.Initialize(mocks.canary)
require.NoError(t, err)

Expand Down

0 comments on commit e86aec0

Please sign in to comment.