Skip to content
Draft
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
30 changes: 13 additions & 17 deletions controller/hybridgateway/builder/kongtarget.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,21 @@ func (b *KongTargetBuilder) WithLabels(route *gwtypes.HTTPRoute) *KongTargetBuil
return b
}

// WithBackendRef sets the target specification based on the given HTTPRoute and backend reference.
func (b *KongTargetBuilder) WithBackendRef(httpRoute *gwtypes.HTTPRoute, bRef *gwtypes.HTTPBackendRef) *KongTargetBuilder {
// Build the dns name of the service for the backendRef.
// TODO(alacuku): We need to handle the cluster domain properly for the cluster where we are running.
var namespace string
if bRef.Namespace == nil || *bRef.Namespace == "" {
namespace = httpRoute.Namespace
} else {
namespace = string(*bRef.Namespace)
}

host := string(bRef.Name) + "." + namespace + ".svc.cluster.local"
port := strconv.Itoa(int(*bRef.Port))
target := net.JoinHostPort(host, port)
// WithTarget sets the target (host:port) for the KongTarget.
func (b *KongTargetBuilder) WithTarget(host string, port gwtypes.PortNumber) *KongTargetBuilder {
target := net.JoinHostPort(host, strconv.Itoa(int(port)))
b.target.Spec.Target = target

// Weight is optional, default to 100 if not specified
if bRef.Weight != nil {
b.target.Spec.Weight = int(*bRef.Weight)
return b
}

// WithWeight sets the weight for the KongTarget. If weight is nil, it defaults to 100.
func (b *KongTargetBuilder) WithWeight(weight *int32) *KongTargetBuilder {
b.target.Spec.Weight = 100 // Weight is optional, default to 100 if not specified
if weight != nil {
newWeight := new(int)
*newWeight = int(*weight)
b.target.Spec.Weight = *newWeight
}
return b
}
Expand Down
123 changes: 17 additions & 106 deletions controller/hybridgateway/builder/kongtarget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package builder
import (
"testing"

"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -54,129 +55,38 @@ func TestKongTargetBuilder_WithLabels(t *testing.T) {
assert.NotEmpty(t, target.Labels)
}

func TestKongTargetBuilder_WithBackendRef(t *testing.T) {
port := gatewayv1.PortNumber(8080)
weight := int32(100)
func TestNewKongTargetBuilder_WithTarget(t *testing.T) {
builder := NewKongTarget().WithTarget("myhost", 8080)

target, err := builder.Build()
require.NoError(t, err)
assert.Equal(t, "myhost:8080", target.Spec.Target)
}

func TestKongTargetBuilder_WithWeight(t *testing.T) {
tests := []struct {
name string
httpRoute *gwtypes.HTTPRoute
backendRef *gwtypes.HTTPBackendRef
expectedTarget string
weight *int32
expectedWeight int
}{
{
name: "backend ref with same namespace",
httpRoute: &gwtypes.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test-route",
Namespace: "test-namespace",
},
},
backendRef: &gwtypes.HTTPBackendRef{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: "test-service",
Port: &port,
},
Weight: &weight,
},
},
expectedTarget: "test-service.test-namespace.svc.cluster.local:8080",
name: "backend ref with weight 100",
weight: lo.ToPtr(int32(100)),
expectedWeight: 100,
},
{
name: "backend ref with different namespace",
httpRoute: &gwtypes.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test-route",
Namespace: "test-namespace",
},
},
backendRef: &gwtypes.HTTPBackendRef{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: "test-service",
Namespace: &[]gatewayv1.Namespace{"other-namespace"}[0],
Port: &port,
},
Weight: &weight,
},
},
expectedTarget: "test-service.other-namespace.svc.cluster.local:8080",
name: "backend ref without weight",
weight: nil,
expectedWeight: 100,
},
{
name: "backend ref with empty namespace",
httpRoute: &gwtypes.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test-route",
Namespace: "test-namespace",
},
},
backendRef: &gwtypes.HTTPBackendRef{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: "test-service",
Namespace: &[]gatewayv1.Namespace{""}[0],
Port: &port,
},
Weight: &weight,
},
},
expectedTarget: "test-service.test-namespace.svc.cluster.local:8080",
expectedWeight: 100,
},
{
name: "backend ref with nil namespace",
httpRoute: &gwtypes.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test-route",
Namespace: "test-namespace",
},
},
backendRef: &gwtypes.HTTPBackendRef{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: "test-service",
Namespace: nil,
Port: &port,
},
Weight: &weight,
},
},
expectedTarget: "test-service.test-namespace.svc.cluster.local:8080",
expectedWeight: 100,
},
{
name: "backend ref without weight",
httpRoute: &gwtypes.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test-route",
Namespace: "test-namespace",
},
},
backendRef: &gwtypes.HTTPBackendRef{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: "test-service",
Port: &port,
},
Weight: nil,
},
},
expectedTarget: "test-service.test-namespace.svc.cluster.local:8080",
expectedWeight: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := NewKongTarget().WithBackendRef(tt.httpRoute, tt.backendRef)
builder := NewKongTarget().WithWeight(tt.weight)

target, err := builder.Build()
require.NoError(t, err)
assert.Equal(t, tt.expectedTarget, target.Spec.Target)
assert.Equal(t, tt.expectedWeight, target.Spec.Weight)
})
}
Expand Down Expand Up @@ -328,7 +238,8 @@ func TestKongTargetBuilder_Chaining(t *testing.T) {
target := NewKongTarget().
WithName("test-target").
WithNamespace("test-namespace").
WithBackendRef(httpRoute, backendRef).
WithTarget(string(backendRef.Name)+"."+httpRoute.Namespace+".svc.cluster.local", *backendRef.Port).
WithWeight(backendRef.Weight).
WithUpstreamRef("test-upstream").
WithOwner(httpRoute).
WithLabels(httpRoute).
Expand Down
83 changes: 83 additions & 0 deletions controller/hybridgateway/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
"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"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

"github.com/kong/kong-operator/controller/hybridgateway/converter"
"github.com/kong/kong-operator/controller/hybridgateway/route"
"github.com/kong/kong-operator/controller/hybridgateway/watch"
"github.com/kong/kong-operator/controller/pkg/log"
"github.com/kong/kong-operator/internal/utils/index"
)

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongroutes,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -63,6 +70,19 @@ func (r *HybridGatewayReconciler[t, tPtr]) SetupWithManager(ctx context.Context,
builder = builder.Owns(owned)
}

// Watch for services to trigger reconciliation of HTTPRoutes that reference them.
// Watch for services to trigger reconciliation of HTTPRoutes that reference them.
builder.Watches(
&corev1.Service{},
handler.EnqueueRequestsFromMapFunc(r.findHTTPRoutesForService),
)

// Watch for endpoint slices to trigger reconciliation of HTTPRoutes that reference them.
builder.Watches(
&discoveryv1.EndpointSlice{},
handler.EnqueueRequestsFromMapFunc(r.findHTTPRoutesForEndpointSlice),
)

return builder.Complete(r)
}

Expand Down Expand Up @@ -105,3 +125,66 @@ func (r *HybridGatewayReconciler[t, tPtr]) Reconcile(ctx context.Context, req ct

return ctrl.Result{}, nil
}

func (r *HybridGatewayReconciler[t, tPtr]) findHTTPRoutesForService(ctx context.Context, obj client.Object) []reconcile.Request {
logger := ctrllog.FromContext(ctx).WithName("HybridGatewayServiceWatcher")
service, ok := obj.(*corev1.Service)
if !ok {
logger.Error(fmt.Errorf("unexpected type %T, expected %T", obj, &corev1.Service{}), "failed to cast object to service")
return nil
}
return r.httpRoutesForService(ctx, service)
}

func (r *HybridGatewayReconciler[t, tPtr]) findHTTPRoutesForEndpointSlice(ctx context.Context, obj client.Object) []reconcile.Request {
logger := ctrllog.FromContext(ctx).WithName("HybridGatewayEndpointSliceWatcher")
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
if !ok {
logger.Error(fmt.Errorf("unexpected type %T, expected %T", obj, &discoveryv1.EndpointSlice{}), "failed to cast object to endpointslice")
return nil
}

serviceName, ok := endpointSlice.Labels[discoveryv1.LabelServiceName]
if !ok {
logger.Info("endpointslice has no service name label", "namespace", endpointSlice.Namespace, "name", endpointSlice.Name)
return nil
}

service := &corev1.Service{}
if err := r.Get(ctx, types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}, service); err != nil {
logger.Error(err, "failed to get service for endpointslice", "servicename", serviceName, "endpointslicenamespace", endpointSlice.Namespace)
return nil
}

return r.httpRoutesForService(ctx, service)
}

func (r *HybridGatewayReconciler[t, tPtr]) httpRoutesForService(ctx context.Context, service *corev1.Service) []reconcile.Request {
logger := ctrllog.FromContext(ctx).WithName("HybridGatewayWatcher")
var httpRoutes gatewayv1.HTTPRouteList
if err := r.List(ctx, &httpRoutes,
client.MatchingFields{
index.BackendServicesOnHTTPRouteIndex: service.Namespace + "/" + service.Name,
},
); err != nil {
logger.Error(err, "failed to list httproutes")
return nil
}

requests := make(map[reconcile.Request]struct{})
for _, httpRoute := range httpRoutes.Items {
requests[reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: httpRoute.Namespace,
Name: httpRoute.Name,
},
}] = struct{}{}
}

reqs := make([]reconcile.Request, 0, len(requests))
for req := range requests {
reqs = append(reqs, req)
}

return reqs
}
Loading
Loading