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, 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 := getGatewayClassesByGatewayClassConfigName(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
//}

// getGatewayClassesByGatewayClassConfigName queries all GatewayClass resources in the
// cluster and returns any that reference the given GatewayClassConfig.
func getGatewayClassesByGatewayClassConfigName(ctx context.Context, k8sClient client.Client, configName string) (*meshv2beta1.GatewayClassList, error) {
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
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 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, 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"
)

// 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, 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
Loading