Skip to content

Commit

Permalink
[NET-7156] Gateways Controllers Reusability (#3574)
Browse files Browse the repository at this point in the history
* make controller setup for gateway controllers generic and reusable, add
indices onto gateway resources in k8s for more efficient lookups

* cleanup from PR review

* Update control-plane/controllers/resources/gateway_controller_setup.go

Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>

* Update control-plane/controllers/resources/gateway_indices.go

Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>

* Update control-plane/controllers/resources/gateway_controller_setup.go

Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>

* Update control-plane/controllers/resources/gateway_controller_setup.go

Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>

* clean up from PR review

---------

Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>
  • Loading branch information
jm96441n and nathancoleman authored Feb 7, 2024
1 parent feb3f6e commit f016fd5
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 97 deletions.
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, APIGateway_GatewayClassIndex)
}

func (r *APIGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.APIGateway) error {
Expand Down
137 changes: 137 additions & 0 deletions control-plane/controllers/resources/gateway_controller_setup.go
Original file line number Diff line number Diff line change
@@ -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
}
75 changes: 75 additions & 0 deletions control-plane/controllers/resources/gateway_indices.go
Original file line number Diff line number Diff line change
@@ -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)}
},
},
}
97 changes: 1 addition & 96 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, MeshGateway_GatewayClassIndex)
}

// onCreateUpdate is responsible for creating/updating all K8s resources that
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit f016fd5

Please sign in to comment.