diff --git a/control-plane/api/mesh/v2beta1/api_gateway_types.go b/control-plane/api/mesh/v2beta1/api_gateway_types.go index 208b6a78af..e0ef9e4b77 100644 --- a/control-plane/api/mesh/v2beta1/api_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/api_gateway_types.go @@ -12,6 +12,8 @@ import ( "google.golang.org/protobuf/testing/protocmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/hashicorp/consul-k8s/control-plane/api/common" inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" @@ -47,6 +49,20 @@ type APIGatewayStatus struct { Listeners []ListenerStatus `json:"listeners"` } +func (in *APIGatewayList) ReconcileRequests() []reconcile.Request { + requests := make([]reconcile.Request, 0, len(in.Items)) + + for _, item := range in.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.Name, + Namespace: item.Namespace, + }, + }) + } + return requests +} + type ListenerStatus struct { Status `json:"status,omitempty"` Name string `json:"name"` diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go index 961c949944..61a33fceb8 100644 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go @@ -12,6 +12,8 @@ import ( "google.golang.org/protobuf/testing/protocmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/hashicorp/consul-k8s/control-plane/api/common" inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" @@ -50,6 +52,20 @@ type MeshGatewayList struct { Items []*MeshGateway `json:"items"` } +func (in *MeshGatewayList) ReconcileRequests() []reconcile.Request { + requests := make([]reconcile.Request, 0, len(in.Items)) + + for _, item := range in.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.Name, + Namespace: item.Namespace, + }, + }) + } + return requests +} + func (in *MeshGateway) ResourceID(_, partition string) *pbresource.ID { return &pbresource.ID{ Name: in.Name, diff --git a/control-plane/controllers/resources/api-gateway-controller.go b/control-plane/controllers/resources/api-gateway-controller.go index 8646fb1fce..2728ef74df 100644 --- a/control-plane/controllers/resources/api-gateway-controller.go +++ b/control-plane/controllers/resources/api-gateway-controller.go @@ -64,7 +64,7 @@ func (r *APIGatewayController) UpdateStatus(ctx context.Context, obj client.Obje } func (r *APIGatewayController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.APIGateway{}, r) + return setupGatewayControllerWithManager[*meshv2beta1.APIGatewayList](mgr, &meshv2beta1.APIGateway{}, r.Client, r, APIGateway_GatewayClassIndex) } func (r *APIGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.APIGateway) error { diff --git a/control-plane/controllers/resources/gateway_controller_setup.go b/control-plane/controllers/resources/gateway_controller_setup.go new file mode 100644 index 0000000000..257b6733e4 --- /dev/null +++ b/control-plane/controllers/resources/gateway_controller_setup.go @@ -0,0 +1,137 @@ +package resources + +import ( + "context" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/fields" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +type gatewayList interface { + *meshv2beta1.MeshGatewayList | *meshv2beta1.APIGatewayList + client.ObjectList + ReconcileRequests() []reconcile.Request +} + +func setupGatewayControllerWithManager[L gatewayList](mgr ctrl.Manager, obj client.Object, k8sClient client.Client, gwc reconcile.Reconciler, index indexName) error { + return ctrl.NewControllerManagedBy(mgr). + For(obj). + Owns(&appsv1.Deployment{}). + Owns(&rbacv1.Role{}). + Owns(&rbacv1.RoleBinding{}). + Owns(&corev1.Service{}). + Owns(&corev1.ServiceAccount{}). + Watches( + source.NewKindWithCache(&meshv2beta1.GatewayClass{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { + gc := o.(*meshv2beta1.GatewayClass) + if gc == nil { + return nil + } + + gateways, err := getGatewaysReferencingGatewayClass[L](context.Background(), k8sClient, gc.Name, index) + if err != nil { + return nil + } + + return gateways.ReconcileRequests() + })). + Watches( + source.NewKindWithCache(&meshv2beta1.GatewayClassConfig{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { + gcc := o.(*meshv2beta1.GatewayClassConfig) + if gcc == nil { + return nil + } + + classes, err := getGatewayClassesReferencingGatewayClassConfig(context.Background(), k8sClient, gcc.Name) + if err != nil { + return nil + } + + var requests []reconcile.Request + for _, class := range classes.Items { + if class == nil { + continue + } + + gateways, err := getGatewaysReferencingGatewayClass[L](context.Background(), k8sClient, class.Name, index) + if err != nil { + continue + } + + requests = append(requests, gateways.ReconcileRequests()...) + } + + return requests + })). + Complete(gwc) +} + +// TODO: uncomment when moving the CRUD hooks from mesh gateway controller +//func getGatewayClassConfigByGatewayClassName(ctx context.Context, k8sClient client.Client, className string) (*meshv2beta1.GatewayClassConfig, error) { +// gatewayClass, err := getGatewayClassByName(ctx, k8sClient, className) +// if err != nil { +// return nil, err +// } +// +// if gatewayClass == nil { +// return nil, nil +// } +// +// gatewayClassConfig := &meshv2beta1.GatewayClassConfig{} +// if ref := gatewayClass.Spec.ParametersRef; ref != nil { +// if ref.Group != meshv2beta1.MeshGroup || ref.Kind != v2beta1.KindGatewayClassConfig { +// // TODO @Gateway-Management additionally check for controller name when available +// return nil, nil +// } +// +// if err := k8sClient.Get(ctx, types.NamespacedName{Name: ref.Name}, gatewayClassConfig); err != nil { +// return nil, client.IgnoreNotFound(err) +// } +// } +// return gatewayClassConfig, nil +//} +// +//func getGatewayClassByName(ctx context.Context, k8sClient client.Client, className string) (*meshv2beta1.GatewayClass, error) { +//var gatewayClass meshv2beta1.GatewayClass +// +// if err := k8sClient.Get(ctx, types.NamespacedName{Name: className}, &gatewayClass); err != nil { +// return nil, client.IgnoreNotFound(err) +// } +// return &gatewayClass, nil +//} + +// getGatewayClassesReferencingGatewayClassConfig queries all GatewayClass resources in the +// cluster and returns any that reference the given GatewayClassConfig by name. +func getGatewayClassesReferencingGatewayClassConfig(ctx context.Context, k8sClient client.Client, configName string) (*meshv2beta1.GatewayClassList, error) { + allClasses := &meshv2beta1.GatewayClassList{} + if err := k8sClient.List(ctx, allClasses, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(string(GatewayClass_GatewayClassConfigIndex), configName), + }); err != nil { + return nil, client.IgnoreNotFound(err) + } + + return allClasses, nil +} + +// getGatewaysReferencingGatewayClass queries all xGateway resources in the cluster +// and returns any that reference the given GatewayClass by name. +func getGatewaysReferencingGatewayClass[T gatewayList](ctx context.Context, k8sClient client.Client, className string, index indexName) (T, error) { + var allGateways T + if err := k8sClient.List(ctx, allGateways, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(string(index), className), + }); err != nil { + return nil, client.IgnoreNotFound(err) + } + + return allGateways, nil +} diff --git a/control-plane/controllers/resources/gateway_indices.go b/control-plane/controllers/resources/gateway_indices.go new file mode 100644 index 0000000000..29e221d191 --- /dev/null +++ b/control-plane/controllers/resources/gateway_indices.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resources + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +type indexName string + +const ( + // Naming convention: TARGET_REFERENCE. + GatewayClass_GatewayClassConfigIndex indexName = "__v2_gatewayclass_referencing_gatewayclassconfig" + + APIGateway_GatewayClassIndex indexName = "__v2_api_gateway_referencing_gatewayclass" + MeshGateway_GatewayClassIndex indexName = "__v2_mesh_gateway_referencing_gatewayclass" +) + +// RegisterGatewayFieldIndexes registers all of the field indexes for the xGateway controllers. +// These indexes are similar to indexes used in databases to speed up queries. +// They allow us to quickly find objects based on a field value. +func RegisterGatewayFieldIndexes(ctx context.Context, mgr ctrl.Manager) error { + for _, index := range indexes { + if err := mgr.GetFieldIndexer().IndexField(ctx, index.target, string(index.name), index.indexerFunc); err != nil { + return err + } + } + return nil +} + +type index struct { + name indexName + target client.Object + indexerFunc client.IndexerFunc +} + +var indexes = []index{ + { + name: GatewayClass_GatewayClassConfigIndex, + target: &meshv2beta1.GatewayClass{}, + indexerFunc: func(o client.Object) []string { + gc := o.(*meshv2beta1.GatewayClass) + + pr := gc.Spec.ParametersRef + if pr != nil && pr.Kind == v2beta1.KindGatewayClassConfig { + return []string{pr.Name} + } + + return []string{} + }, + }, + { + name: APIGateway_GatewayClassIndex, + target: &meshv2beta1.APIGateway{}, + indexerFunc: func(o client.Object) []string { + g := o.(*meshv2beta1.APIGateway) + return []string{string(g.Spec.GatewayClassName)} + }, + }, + { + name: MeshGateway_GatewayClassIndex, + target: &meshv2beta1.MeshGateway{}, + indexerFunc: func(o client.Object) []string { + g := o.(*meshv2beta1.MeshGateway) + return []string{string(g.Spec.GatewayClassName)} + }, + }, +} diff --git a/control-plane/controllers/resources/mesh_gateway_controller.go b/control-plane/controllers/resources/mesh_gateway_controller.go index 8cc2b61a7c..3ea2b228da 100644 --- a/control-plane/controllers/resources/mesh_gateway_controller.go +++ b/control-plane/controllers/resources/mesh_gateway_controller.go @@ -19,9 +19,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/gateways" @@ -80,57 +77,7 @@ func (r *MeshGatewayController) UpdateStatus(ctx context.Context, obj client.Obj } func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&meshv2beta1.MeshGateway{}). - Owns(&appsv1.Deployment{}). - Owns(&rbacv1.Role{}). - Owns(&rbacv1.RoleBinding{}). - Owns(&corev1.Service{}). - Owns(&corev1.ServiceAccount{}). - Watches( - source.NewKindWithCache(&meshv2beta1.GatewayClass{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - gateways, err := r.getGatewaysReferencingGatewayClass(context.Background(), o.(*meshv2beta1.GatewayClass)) - if err != nil { - return nil - } - - requests := make([]reconcile.Request, 0, len(gateways.Items)) - for _, gateway := range gateways.Items { - requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ - Namespace: gateway.Namespace, - Name: gateway.Name, - }}) - } - - return requests - })). - Watches( - source.NewKindWithCache(&meshv2beta1.GatewayClassConfig{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - classes, err := r.getGatewayClassesReferencingGatewayClassConfig(context.Background(), o.(*meshv2beta1.GatewayClassConfig)) - if err != nil { - return nil - } - - var requests []reconcile.Request - for _, class := range classes.Items { - gateways, err := r.getGatewaysReferencingGatewayClass(context.Background(), class) - if err != nil { - continue - } - - for _, gateway := range gateways.Items { - requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ - Namespace: gateway.Namespace, - Name: gateway.Name, - }}) - } - } - - return requests - })). - Complete(r) + return setupGatewayControllerWithManager[*meshv2beta1.MeshGatewayList](mgr, &meshv2beta1.MeshGateway{}, r.Client, r, MeshGateway_GatewayClassIndex) } // onCreateUpdate is responsible for creating/updating all K8s resources that @@ -333,45 +280,3 @@ func (r *MeshGatewayController) getGatewayClassForGateway(ctx context.Context, g } return &gatewayClass, nil } - -// getGatewayClassesReferencingGatewayClassConfig queries all GatewayClass resources in the -// cluster and returns any that reference the given GatewayClassConfig. -func (r *MeshGatewayController) getGatewayClassesReferencingGatewayClassConfig(ctx context.Context, config *meshv2beta1.GatewayClassConfig) (*meshv2beta1.GatewayClassList, error) { - if config == nil { - return nil, nil - } - - allClasses := &meshv2beta1.GatewayClassList{} - if err := r.Client.List(ctx, allClasses); err != nil { - return nil, client.IgnoreNotFound(err) - } - - matchingClasses := &meshv2beta1.GatewayClassList{} - for _, class := range allClasses.Items { - if class.Spec.ParametersRef != nil && class.Spec.ParametersRef.Name == config.Name { - matchingClasses.Items = append(matchingClasses.Items, class) - } - } - return matchingClasses, nil -} - -// getGatewaysReferencingGatewayClass queries all MeshGateway resources in the cluster -// and returns any that reference the given GatewayClass. -func (r *MeshGatewayController) getGatewaysReferencingGatewayClass(ctx context.Context, class *meshv2beta1.GatewayClass) (*meshv2beta1.MeshGatewayList, error) { - if class == nil { - return nil, nil - } - - allGateways := &meshv2beta1.MeshGatewayList{} - if err := r.Client.List(ctx, allGateways); err != nil { - return nil, client.IgnoreNotFound(err) - } - - matchingGateways := &meshv2beta1.MeshGatewayList{} - for _, gateway := range allGateways.Items { - if gateway.Spec.GatewayClassName == class.Name { - matchingGateways.Items = append(matchingGateways.Items, gateway) - } - } - return matchingGateways, nil -} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index ddf0758df1..13f83a12cc 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -200,6 +200,11 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } + if err := resourceControllers.RegisterGatewayFieldIndexes(ctx, mgr); err != nil { + setupLog.Error(err, "unable to register field indexes") + return err + } + if err := (&resourceControllers.MeshConfigurationController{ Controller: consulResourceController, Client: mgr.GetClient(),