From e24369f4aa3c45d1989f4e169f0bdf9f9177fda6 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Fri, 6 Sep 2024 11:56:33 +0100 Subject: [PATCH] Pass allow loops option from controller creation all the way down to the topology builder Signed-off-by: Jim Fitzpatrick --- controller/controller.go | 71 ++++++++++++++++++------------- controller/controller_test.go | 5 +++ controller/topology_builder.go | 4 +- machinery/gateway_api_topology.go | 13 ++++++ 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/controller/controller.go b/controller/controller.go index aca155e..3857b80 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -23,15 +23,16 @@ import ( ) type ControllerOptions struct { - name string - logger logr.Logger - client *dynamic.DynamicClient - manager ctrlruntime.Manager - runnables map[string]RunnableBuilder - reconcile ReconcileFunc - policyKinds []schema.GroupKind - objectKinds []schema.GroupKind - objectLinks []LinkFunc + name string + logger logr.Logger + client *dynamic.DynamicClient + manager ctrlruntime.Manager + runnables map[string]RunnableBuilder + reconcile ReconcileFunc + policyKinds []schema.GroupKind + objectKinds []schema.GroupKind + objectLinks []LinkFunc + allowTopologyLoops bool } type ControllerOption func(*ControllerOptions) @@ -94,6 +95,12 @@ func ManagedBy(manager ctrlruntime.Manager) ControllerOption { } } +func AllowLoops() ControllerOption { + return func(o *ControllerOptions) { + o.allowTopologyLoops = true + } +} + func NewController(f ...ControllerOption) *Controller { opts := &ControllerOptions{ name: "controller", @@ -107,14 +114,15 @@ func NewController(f ...ControllerOption) *Controller { } controller := &Controller{ - name: opts.name, - logger: opts.logger, - client: opts.client, - manager: opts.manager, - cache: &watchableCacheStore{}, - topology: newGatewayAPITopologyBuilder(opts.policyKinds, opts.objectKinds, opts.objectLinks), - runnables: map[string]Runnable{}, - reconcile: opts.reconcile, + name: opts.name, + logger: opts.logger, + client: opts.client, + manager: opts.manager, + cache: &watchableCacheStore{}, + topology: newGatewayAPITopologyBuilder(opts.policyKinds, opts.objectKinds, opts.objectLinks), + runnables: map[string]Runnable{}, + reconcile: opts.reconcile, + allowTopologyLoops: opts.allowTopologyLoops, } for name, builder := range opts.runnables { @@ -129,16 +137,17 @@ type WatchFunc func(ctrlruntime.Manager) ctrlruntimesrc.Source type Controller struct { sync.Mutex - name string - logger logr.Logger - client *dynamic.DynamicClient - manager ctrlruntime.Manager - cache Cache - topology *gatewayAPITopologyBuilder - runnables map[string]Runnable - listFuncs []ListFunc - watchFuncs []WatchFunc - reconcile ReconcileFunc + name string + logger logr.Logger + client *dynamic.DynamicClient + manager ctrlruntime.Manager + cache Cache + topology *gatewayAPITopologyBuilder + runnables map[string]Runnable + listFuncs []ListFunc + watchFuncs []WatchFunc + reconcile ReconcileFunc + allowTopologyLoops bool } // Start starts the runnables and blocks until the context is cancelled @@ -239,8 +248,12 @@ func (c *Controller) delete(obj Object) { } func (c *Controller) propagate(resourceEvents []ResourceEvent) { - topology, err := c.topology.Build(c.cache.List()) - if err != nil { + opts := make([]machinery.GatewayAPITopologyOptionsFunc, 0) + if c.allowTopologyLoops { + opts = append(opts, machinery.AllowTopologyLoops()) + } + topology, err := c.topology.Build(c.cache.List(), opts...) + if !c.allowTopologyLoops && err != nil { panic(err) } c.reconcile(LoggerIntoContext(context.TODO(), c.logger), resourceEvents, topology) diff --git a/controller/controller_test.go b/controller/controller_test.go index 479e0b6..6d3f402 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -93,6 +93,11 @@ func TestControllerOptions(t *testing.T) { if opts.reconcile == nil { t.Errorf("expected reconcile func, got nil") } + + AllowLoops()(opts) + if opts.allowTopologyLoops == false { + t.Errorf("expected allowTopologyLoops true, got false") + } } func TestNewController(t *testing.T) { diff --git a/controller/topology_builder.go b/controller/topology_builder.go index 4d68246..3fbbc03 100644 --- a/controller/topology_builder.go +++ b/controller/topology_builder.go @@ -23,7 +23,7 @@ type gatewayAPITopologyBuilder struct { objectLinks []LinkFunc } -func (t *gatewayAPITopologyBuilder) Build(objs Store) (*machinery.Topology, error) { +func (t *gatewayAPITopologyBuilder) Build(objs Store, options ...machinery.GatewayAPITopologyOptionsFunc) (*machinery.Topology, error) { gatewayClasses := lo.Map(objs.FilterByGroupKind(machinery.GatewayClassGroupKind), ObjectAs[*gwapiv1.GatewayClass]) gateways := lo.Map(objs.FilterByGroupKind(machinery.GatewayGroupKind), ObjectAs[*gwapiv1.Gateway]) httpRoutes := lo.Map(objs.FilterByGroupKind(machinery.HTTPRouteGroupKind), ObjectAs[*gwapiv1.HTTPRoute]) @@ -44,6 +44,8 @@ func (t *gatewayAPITopologyBuilder) Build(objs Store) (*machinery.Topology, erro machinery.WithGatewayAPITopologyLinks(linkFuncs...), } + opts = append(opts, options...) + for i := range t.policyKinds { policyKind := t.policyKinds[i] policies := lo.Map(objs.FilterByGroupKind(policyKind), ObjectAs[machinery.Policy]) diff --git a/machinery/gateway_api_topology.go b/machinery/gateway_api_topology.go index 6a2ba31..ab3be5e 100644 --- a/machinery/gateway_api_topology.go +++ b/machinery/gateway_api_topology.go @@ -30,6 +30,8 @@ type GatewayAPITopologyOptions struct { ExpandTLSRouteRules bool ExpandUDPRouteRules bool ExpandServicePorts bool + + allowTopologyLoops bool } type GatewayAPITopologyOptionsFunc func(*GatewayAPITopologyOptions) @@ -178,6 +180,13 @@ func ExpandServicePorts() GatewayAPITopologyOptionsFunc { } } +// AllowTopologyLoops adds AllowLoops to the options to initialize a new Gateway API topology. +func AllowTopologyLoops() GatewayAPITopologyOptionsFunc { + return func(o *GatewayAPITopologyOptions) { + o.allowTopologyLoops = 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. @@ -356,6 +365,10 @@ func NewGatewayAPITopology(options ...GatewayAPITopologyOptionsFunc) (*Topology, opts = append(opts, WithLinks(LinkServiceToServicePortFunc())) // Service -> ServicePort } + if o.allowTopologyLoops { + opts = append(opts, AllowLoops()) + } + return NewTopology(opts...) }