Skip to content

Commit

Permalink
Merge pull request #2 from Kuadrant/gwapi-topology
Browse files Browse the repository at this point in the history
Gateway API specifics decoupled from Topology
  • Loading branch information
guicassolato authored Jul 2, 2024
2 parents b184a2b + d5def03 commit be55eeb
Show file tree
Hide file tree
Showing 11 changed files with 1,459 additions and 938 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
with:
go-version: '1.22.x'
- name: Test with the Go CLI
run: go test -tags=intetration -v ./...
run: go test -tags=integration -v ./...
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

"github.com/samber/lo"
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand All @@ -17,7 +18,7 @@ import (
"github.com/kuadrant/policy-machinery/machinery"
)

// TestMergeBasedOnTopology tests ColorPolicy's merge strategies for painting a house, based on network traffic
// TestKuadrantMergeBasedOnTopology tests ColorPolicy's merge strategies for painting a house, based on network traffic
// flowing through the following topology of Gateway API resources:
//
// ┌────────────┐
Expand All @@ -42,24 +43,23 @@ import (
// ┌────────────┐
// │ my-service │
// └────────────┘
func TestMergeBasedOnTopology(t *testing.T) {
gateway := &machinery.Gateway{Gateway: machinery.BuildGateway()}
httpRoutes := []*machinery.HTTPRoute{
{HTTPRoute: machinery.BuildHTTPRoute(func(r *gwapiv1.HTTPRoute) {
func TestKuadrantMergeBasedOnTopology(t *testing.T) {
gateway := machinery.BuildGateway()
httpRoutes := []*gwapiv1.HTTPRoute{
machinery.BuildHTTPRoute(func(r *gwapiv1.HTTPRoute) {
r.Name = "my-route-1"
r.Spec.Rules = append(r.Spec.Rules, gwapiv1.HTTPRouteRule{
BackendRefs: []gwapiv1.HTTPBackendRef{machinery.BuildHTTPBackendRef()},
})
})},
{HTTPRoute: machinery.BuildHTTPRoute(func(r *gwapiv1.HTTPRoute) {
}),
machinery.BuildHTTPRoute(func(r *gwapiv1.HTTPRoute) {
r.Name = "my-route-2"
r.Spec.Rules = append(r.Spec.Rules, gwapiv1.HTTPRouteRule{
BackendRefs: []gwapiv1.HTTPBackendRef{machinery.BuildHTTPBackendRef()},
})
})},
}),
}
httpRouteRules := lo.FlatMap(httpRoutes, machinery.HTTPRouteRulesFromHTTPRouteFunc)
services := []*machinery.Service{{Service: machinery.BuildService()}}
services := []*core.Service{machinery.BuildService()}
policies := []machinery.Policy{
buildPolicy(func(p *ColorPolicy) { // atomic defaults
p.Name = "house-colors-gw"
Expand Down Expand Up @@ -141,23 +141,29 @@ func TestMergeBasedOnTopology(t *testing.T) {
}),
}

topology := machinery.NewTopology(
machinery.WithTargetables(gateway),
machinery.WithTargetables(httpRoutes...),
machinery.WithTargetables(httpRouteRules...),
machinery.WithTargetables(services...),
machinery.WithLinks(
machinery.LinkGatewayToHTTPRouteFunc([]*machinery.Gateway{gateway}),
machinery.LinkHTTPRouteToHTTPRouteRuleFunc(),
machinery.LinkHTTPRouteRuleToServiceFunc(httpRouteRules, false),
),
machinery.WithPolicies(policies...),
topology := machinery.NewGatewayAPITopology(
machinery.WithGateways(gateway),
machinery.WithHTTPRoutes(httpRoutes...),
machinery.ExpandHTTPRouteRules(),
machinery.WithServices(services...),
machinery.WithGatewayAPITopologyPolicies(policies...),
)

machinery.SaveToOutputDir(t, topology.ToDot(), "../../tests/out", ".dot")

gateways := topology.Targetables(func(o machinery.Object) bool {
_, ok := o.(*machinery.Gateway)
return ok
})
httpRouteRules := topology.Targetables(func(o machinery.Object) bool {
_, ok := o.(*machinery.HTTPRouteRule)
return ok
})

effectivePoliciesByPath := make(map[string]ColorPolicy)

for _, httpRouteRule := range httpRouteRules {
for _, path := range topology.Paths(gateway, httpRouteRule) {
for _, path := range topology.Paths(gateways[0], httpRouteRule) {
// Gather all policies in the path sorted from the least specific (gateway) to the most specific (httprouterule)
// Since in this example there are no targetables with more than one policy attached to it, we can safely just
// flat the slices of policies; otherwise we would need to ensure that the policies at the same level are sorted
Expand Down
439 changes: 439 additions & 0 deletions machinery/gateway_api_test_helper.go

Large diffs are not rendered by default.

147 changes: 147 additions & 0 deletions machinery/gateway_api.go → machinery/gateway_api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,153 @@ import (
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
)

type GatewayAPITopologyOptions struct {
GatewayClasses []*GatewayClass
Gateways []*Gateway
HTTPRoutes []*HTTPRoute
Services []*Service
Policies []Policy

ExpandGatewayListeners bool
ExpandHTTPRouteRules bool
ExpandServicePorts bool
}

type GatewayAPITopologyOptionsFunc func(*GatewayAPITopologyOptions)

// WithGatewayClasses adds gateway classes to the options to initialize a new Gateway API topology.
func WithGatewayClasses(gatewayClasses ...*gwapiv1.GatewayClass) GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.GatewayClasses = append(o.GatewayClasses, lo.Map(gatewayClasses, func(gatewayClass *gwapiv1.GatewayClass, _ int) *GatewayClass {
return &GatewayClass{GatewayClass: gatewayClass}
})...)
}
}

// WithGateways adds gateways to the options to initialize a new Gateway API topology.
func WithGateways(gateways ...*gwapiv1.Gateway) GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.Gateways = append(o.Gateways, lo.Map(gateways, func(gateway *gwapiv1.Gateway, _ int) *Gateway {
return &Gateway{Gateway: gateway}
})...)
}
}

// WithHTTPRoutes adds HTTP routes to the options to initialize a new Gateway API topology.
func WithHTTPRoutes(httpRoutes ...*gwapiv1.HTTPRoute) GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.HTTPRoutes = append(o.HTTPRoutes, lo.Map(httpRoutes, func(httpRoute *gwapiv1.HTTPRoute, _ int) *HTTPRoute {
return &HTTPRoute{HTTPRoute: httpRoute}
})...)
}
}

// WithServices adds services to the options to initialize a new Gateway API topology.
func WithServices(services ...*core.Service) GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.Services = append(o.Services, lo.Map(services, func(service *core.Service, _ int) *Service {
return &Service{Service: service}
})...)
}
}

// WithGatewayAPITopologyPolicies adds policies to the options to initialize a new Gateway API topology.
func WithGatewayAPITopologyPolicies(policies ...Policy) GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.Policies = append(o.Policies, policies...)
}
}

// ExpandGatewayListeners adds targetable gateway listeners to the options to initialize a new Gateway API topology.
func ExpandGatewayListeners() GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.ExpandGatewayListeners = true
}
}

// ExpandHTTPRouteRules adds targetable HTTP route rules to the options to initialize a new Gateway API topology.
func ExpandHTTPRouteRules() GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.ExpandHTTPRouteRules = true
}
}

// ExpandServicePorts adds targetable service ports to the options to initialize a new Gateway API topology.
func ExpandServicePorts() GatewayAPITopologyOptionsFunc {
return func(o *GatewayAPITopologyOptions) {
o.ExpandServicePorts = true
}
}

// NewGatewayAPITopology returns a topology of Gateway API objects and attached policies.
//
// The links between the targetables are established based on the relationships defined by Gateway API.
//
// Principal objects like Gateways, HTTPRoutes and Services can be expanded to automatically include their targetable
// sections (listeners, route rules, service ports) as independent objects in the topology, by supplying the
// corresponding options ExpandGatewayListeners(), ExpandHTTPRouteRules(), and ExpandServicePorts().
// The links will then be established accordingly. E.g.:
// - Without expanding Gateway listeners (default): Gateway -> HTTPRoute links.
// - Expanding Gateway listeners: Gateway -> Listener and Listener -> HTTPRoute links.
func NewGatewayAPITopology(options ...GatewayAPITopologyOptionsFunc) *Topology {
o := &GatewayAPITopologyOptions{}
for _, f := range options {
f(o)
}

opts := []TopologyOptionsFunc{
WithPolicies(o.Policies...),
WithTargetables(o.GatewayClasses...),
WithTargetables(o.Gateways...),
WithTargetables(o.HTTPRoutes...),
WithTargetables(o.Services...),
WithLinks(LinkGatewayClassToGatewayFunc(o.GatewayClasses)), // GatewayClass -> Gateway
}

if o.ExpandGatewayListeners {
listeners := lo.FlatMap(o.Gateways, ListenersFromGatewayFunc)
opts = append(opts, WithTargetables(listeners...))
opts = append(opts, WithLinks(
LinkGatewayToListenerFunc(), // Gateway -> Listener
LinkListenerToHTTPRouteFunc(o.Gateways, listeners), // Listener -> HTTPRoute
))
} else {
opts = append(opts, WithLinks(LinkGatewayToHTTPRouteFunc(o.Gateways))) // Gateway -> HTTPRoute
}

if o.ExpandHTTPRouteRules {
httpRouteRules := lo.FlatMap(o.HTTPRoutes, HTTPRouteRulesFromHTTPRouteFunc)
opts = append(opts, WithTargetables(httpRouteRules...))
opts = append(opts, WithLinks(LinkHTTPRouteToHTTPRouteRuleFunc())) // HTTPRoute -> HTTPRouteRule

if o.ExpandServicePorts {
servicePorts := lo.FlatMap(o.Services, ServicePortsFromBackendFunc)
opts = append(opts, WithTargetables(servicePorts...))
opts = append(opts, WithLinks(
LinkHTTPRouteRuleToServicePortFunc(httpRouteRules), // HTTPRouteRule -> ServicePort
LinkHTTPRouteRuleToServiceFunc(httpRouteRules, true), // HTTPRouteRule -> Service
))
} else {
opts = append(opts, WithLinks(LinkHTTPRouteRuleToServiceFunc(httpRouteRules, false))) // HTTPRouteRule -> Service
}
} else {
if o.ExpandServicePorts {
opts = append(opts, WithLinks(
LinkHTTPRouteToServicePortFunc(o.HTTPRoutes), // HTTPRoute -> ServicePort
LinkHTTPRouteToServiceFunc(o.HTTPRoutes, true), // HTTPRoute -> Service
))
} else {
opts = append(opts, WithLinks(LinkHTTPRouteToServiceFunc(o.HTTPRoutes, false))) // HTTPRoute -> Service
}
}

if o.ExpandServicePorts {
opts = append(opts, WithLinks(LinkServiceToServicePortFunc())) // Service -> ServicePort
}

return NewTopology(opts...)
}

// ListenersFromGatewayFunc returns a list of targetable listeners from a targetable gateway.
func ListenersFromGatewayFunc(gateway *Gateway, _ int) []*Listener {
return lo.Map(gateway.Spec.Listeners, func(listener gwapiv1.Listener, _ int) *Listener {
Expand Down
Loading

0 comments on commit be55eeb

Please sign in to comment.