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" {
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
// 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
jm96441n marked this conversation as resolved.
Show resolved Hide resolved

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),
nathancoleman marked this conversation as resolved.
Show resolved Hide resolved
}); err != nil {
return nil, client.IgnoreNotFound(err)
}

return allClasses, nil
}

// getGatewaysReferencingGatewayClass queries all MeshGateway resources in the cluster
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
// and returns any that reference the given GatewayClass.
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
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) {
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
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 {
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
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" {
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
return []string{pr.Name}
}

return []string{}
}
jm96441n marked this conversation as resolved.
Show resolved Hide resolved

// 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)}
}
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
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