Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions controllers/gateway/targetgroup_configuration_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)

const (
targetReferenceKindService = "Service"
targetReferenceKindGateway = "Gateway"
)

// NewTargetGroupConfigurationReconciler constructs a reconciler that responds to targetgroup configuration changes
Expand Down Expand Up @@ -126,6 +132,26 @@ func (r *targetgroupConfigurationReconciler) handleDelete(tgConf *elbv2gw.Target
return r.finalizerManager.RemoveFinalizers(context.Background(), tgConf, shared_constants.TargetGroupConfigurationFinalizer)
}

targetKind := targetReferenceKindService
if tgConf.Spec.TargetReference.Kind != nil {
targetKind = *tgConf.Spec.TargetReference.Kind
}

if targetKind == targetReferenceKindGateway {
inUseRoutes, err := r.isGatewayTargetTGCInUse(context.Background(), tgConf)
if err != nil {
return err
}
if inUseRoutes != "" {
return fmt.Errorf("targetgroup configuration [%+v] is still in use by TCPRoutes [%s]", k8s.NamespacedName(tgConf), inUseRoutes)
}
return r.finalizerManager.RemoveFinalizers(context.Background(), tgConf, shared_constants.TargetGroupConfigurationFinalizer)
}

if targetKind != targetReferenceKindService {
return fmt.Errorf("targetgroup configuration [%+v] has unsupported targetReference kind %q", k8s.NamespacedName(tgConf), targetKind)
}

svcReference := types.NamespacedName{
Namespace: tgConf.Namespace,
Name: tgConf.Spec.TargetReference.Name,
Expand Down Expand Up @@ -184,6 +210,40 @@ func (r *targetgroupConfigurationReconciler) isDefaultTGCInUse(ctx context.Conte
return "", nil
}

func (r *targetgroupConfigurationReconciler) isGatewayTargetTGCInUse(ctx context.Context, tgConf *elbv2gw.TargetGroupConfiguration) (string, error) {
tcpRouteList := &gwalpha2.TCPRouteList{}
if err := r.k8sClient.List(ctx, tcpRouteList); err != nil {
return "", err
}

var inUseRoutes []string
for i := range tcpRouteList.Items {
route := &tcpRouteList.Items[i]
for _, rule := range route.Spec.Rules {
for _, beRef := range rule.BackendRefs {
if beRef.Kind == nil || *beRef.Kind != targetReferenceKindGateway {
continue
}

routeTargetNamespace := route.Namespace
if beRef.Namespace != nil {
routeTargetNamespace = string(*beRef.Namespace)
}

if routeTargetNamespace == tgConf.Namespace && string(beRef.Name) == tgConf.Spec.TargetReference.Name {
inUseRoutes = append(inUseRoutes, k8s.NamespacedName(route).String())
break
}
}
}
}

if len(inUseRoutes) > 0 {
return strings.Join(inUseRoutes, ", "), nil
}
return "", nil
}

func (r *targetgroupConfigurationReconciler) SetupWithManager(_ context.Context, mgr ctrl.Manager) (controller.Controller, error) {
return controller.New(constants.TargetGroupConfigurationController, mgr, controller.Options{
MaxConcurrentReconciles: r.workers,
Expand Down
118 changes: 118 additions & 0 deletions controllers/gateway/targetgroup_configuration_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package gateway

import (
"context"
"testing"

"github.com/go-logr/logr"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1"
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
"sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants"
"sigs.k8s.io/aws-load-balancer-controller/pkg/testutils"
"sigs.k8s.io/controller-runtime/pkg/client"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)

func TestTargetGroupConfigurationReconciler_handleDelete_GatewayTargetStillInUse(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

k8sClient := testutils.GenerateTestClient()
finalizerManager := k8s.NewMockFinalizerManager(ctrl)

targetKind := targetReferenceKindGateway
tgConf := &elbv2gw.TargetGroupConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "gateway-tgc",
Namespace: "test-ns",
Finalizers: []string{shared_constants.TargetGroupConfigurationFinalizer},
},
Spec: elbv2gw.TargetGroupConfigurationSpec{
TargetReference: &elbv2gw.Reference{
Name: "chained-gateway",
Kind: &targetKind,
},
},
}
routeKind := gwalpha2.Kind(targetReferenceKindGateway)
route := &gwalpha2.TCPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp-route",
Namespace: "test-ns",
},
Spec: gwalpha2.TCPRouteSpec{
Rules: []gwalpha2.TCPRouteRule{
{
BackendRefs: []gwalpha2.BackendRef{
{
BackendObjectReference: gwalpha2.BackendObjectReference{
Name: "chained-gateway",
Kind: &routeKind,
},
},
},
},
},
},
}

assert.NoError(t, k8sClient.Create(context.Background(), tgConf))
assert.NoError(t, k8sClient.Create(context.Background(), route))

r := &targetgroupConfigurationReconciler{
k8sClient: k8sClient,
logger: logr.Discard(),
finalizerManager: finalizerManager,
gwRetrieveFn: func(ctx context.Context, k8sClient client.Client, gwController string) ([]*gwv1.Gateway, error) {
return nil, nil
},
}

err := r.handleDelete(tgConf)
assert.EqualError(t, err, "targetgroup configuration [test-ns/gateway-tgc] is still in use by TCPRoutes [test-ns/tcp-route]")
}

func TestTargetGroupConfigurationReconciler_handleDelete_GatewayTargetNotInUse(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

k8sClient := testutils.GenerateTestClient()
finalizerManager := k8s.NewMockFinalizerManager(ctrl)

targetKind := targetReferenceKindGateway
tgConf := &elbv2gw.TargetGroupConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "gateway-tgc",
Namespace: "test-ns",
Finalizers: []string{shared_constants.TargetGroupConfigurationFinalizer},
},
Spec: elbv2gw.TargetGroupConfigurationSpec{
TargetReference: &elbv2gw.Reference{
Name: "chained-gateway",
Kind: &targetKind,
},
},
}

assert.NoError(t, k8sClient.Create(context.Background(), tgConf))

finalizerManager.EXPECT().
RemoveFinalizers(context.Background(), tgConf, shared_constants.TargetGroupConfigurationFinalizer).
Return(nil)

r := &targetgroupConfigurationReconciler{
k8sClient: k8sClient,
logger: logr.Discard(),
finalizerManager: finalizerManager,
gwRetrieveFn: func(ctx context.Context, k8sClient client.Client, gwController string) ([]*gwv1.Gateway, error) {
return nil, nil
},
}

err := r.handleDelete(tgConf)
assert.NoError(t, err)
}