Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NET-7793] Gateways Controllers Reusability #3574

Merged
merged 7 commits into from
Feb 7, 2024
16 changes: 16 additions & 0 deletions control-plane/api/mesh/v2beta1/api_gateway_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"`
Expand Down
16 changes: 16 additions & 0 deletions control-plane/api/mesh/v2beta1/mesh_gateway_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

func (r *APIGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.APIGateway) error {
Expand Down
141 changes: 141 additions & 0 deletions control-plane/controllers/resources/gateway_controller_setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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"
"k8s.io/apimachinery/pkg/types"
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) 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 {
gateways, err := getGatewaysReferencingGatewayClass[L](context.Background(), k8sClient, o.(*meshv2beta1.GatewayClass))
if err != nil {
return nil
}

return gateways.ReconcileRequests()
})).
Watches(
source.NewKindWithCache(&meshv2beta1.GatewayClassConfig{}, mgr.GetCache()),
handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
classes, err := getGatewayClassesReferencingGatewayClassConfig(context.Background(), k8sClient, o.(*meshv2beta1.GatewayClassConfig))
if err != nil {
return nil
}

var requests []reconcile.Request
for _, class := range classes.Items {
gateways, err := getGatewaysReferencingGatewayClass[L](context.Background(), k8sClient, class)
if err != nil {
continue
}

requests = append(requests, gateways.ReconcileRequests()...)
}

return requests
})).
Complete(gwc)
}

func getGatewayClassConfigForGatewayClass(ctx context.Context, k8sClient client.Client, gatewayClass *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) {
if gatewayClass == nil {
// if we don't have a gateway class we can't fetch the corresponding config
return nil, nil
}

config := &meshv2beta1.GatewayClassConfig{}
if ref := gatewayClass.Spec.ParametersRef; ref != nil {
if ref.Group != meshv2beta1.MeshGroup || ref.Kind != "GatewayClassConfig" {
// TODO @Gateway-Management additionally check for controller name when available
return nil, nil
}

if err := k8sClient.Get(ctx, types.NamespacedName{Name: ref.Name}, config); err != nil {
return nil, client.IgnoreNotFound(err)
}
}
return config, nil
}

func getGatewayClassForGateway(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.
func getGatewayClassesReferencingGatewayClassConfig(ctx context.Context, k8sClient client.Client, config *meshv2beta1.GatewayClassConfig) (*meshv2beta1.GatewayClassList, error) {
if config == nil {
return nil, nil
}

allClasses := &meshv2beta1.GatewayClassList{}
if err := k8sClient.List(ctx, allClasses, &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(GatewayClass_GatewayClassConfigIndex, config.Name),
}); err != nil {
return nil, client.IgnoreNotFound(err)
}

return allClasses, nil
}

// getGatewaysReferencingGatewayClass queries all MeshGateway resources in the cluster
// and returns any that reference the given GatewayClass.
func getGatewaysReferencingGatewayClass[T gatewayList](ctx context.Context, k8sClient client.Client, class *meshv2beta1.GatewayClass) (T, error) {
if class == nil {
return nil, nil
}

var allGateways T
if err := k8sClient.List(ctx, allGateways, &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(Gateway_GatewayClassIndex, class.Name),
}); err != nil {
return nil, client.IgnoreNotFound(err)
}

return allGateways, nil
}

func getGatewayClassConfigForGateway(ctx context.Context, k8sClient client.Client, className string) (*meshv2beta1.GatewayClassConfig, error) {
gatewayClass, err := getGatewayClassForGateway(ctx, k8sClient, className)
if err != nil {
return nil, err
}

gatewayClassConfig, err := getGatewayClassConfigForGatewayClass(ctx, k8sClient, gatewayClass)
if err != nil {
return nil, err
}

return gatewayClassConfig, nil
}
70 changes: 70 additions & 0 deletions control-plane/controllers/resources/gateway_indices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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"
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1"
)

const (
// Naming convention: TARGET_REFERENCE.
GatewayClass_GatewayClassConfigIndex = "__v2_gatewayclass_referencing_gatewayclassconfig"

Gateway_GatewayClassIndex = "__v2_gateway_referencing_gatewayclass"
)

// RegisterFieldIndexes registers all of the field indexes for the API gateway 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 RegisterFieldIndexes(ctx context.Context, mgr ctrl.Manager) error {
for _, index := range indexes {
if err := mgr.GetFieldIndexer().IndexField(ctx, index.target, index.name, index.indexerFunc); err != nil {
return err
}
}
return nil
}

type index struct {
name string
target client.Object
indexerFunc client.IndexerFunc
}

var indexes = []index{
{
name: GatewayClass_GatewayClassConfigIndex,
target: &meshv2beta1.GatewayClass{},
indexerFunc: gatewayClassConfigForGatewayClass,
},
{
name: Gateway_GatewayClassIndex,
target: &gwv1beta1.Gateway{},
indexerFunc: gatewayClassForGateway,
},
}

// gatewayClassConfigForGatewayClass creates an index of every GatewayClassConfig referenced by a GatewayClass.
func gatewayClassConfigForGatewayClass(o client.Object) []string {
gc := o.(*meshv2beta1.GatewayClass)

pr := gc.Spec.ParametersRef
if pr != nil && pr.Kind == "GatewayClassConfig" {
return []string{pr.Name}
}

return []string{}
}

// gatewayClassForGateway creates an index of every GatewayClass referenced by a Gateway.
func gatewayClassForGateway(o client.Object) []string {
g := o.(*meshv2beta1.APIGateway)
return []string{string(g.Spec.GatewayClassName)}
}
55 changes: 1 addition & 54 deletions control-plane/controllers/resources/mesh_gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

// onCreateUpdate is responsible for creating/updating all K8s resources that
Expand Down
5 changes: 5 additions & 0 deletions control-plane/subcommand/inject-connect/v2controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage
return err
}

if err := resourceControllers.RegisterFieldIndexes(ctx, mgr); err != nil {
setupLog.Error(err, "unable to register field indexes")
return err
}

if err := (&resourceControllers.MeshConfigurationController{
Controller: consulResourceController,
Client: mgr.GetClient(),
Expand Down
Loading