Skip to content
Merged
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
13 changes: 9 additions & 4 deletions internal/controller/provisioner/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ func (h *eventHandler) HandleEventBatch(ctx context.Context, logger logr.Logger,
objLabels := labels.Set(obj.GetLabels())
if h.labelSelector.Matches(objLabels) {
gatewayName := objLabels.Get(controller.GatewayLabel)
if gatewayName == "" {
gatewayName = obj.GetAnnotations()[controller.GatewayLabel]
}
gatewayNSName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: gatewayName}

if err := h.updateOrDeleteResources(ctx, logger, obj, gatewayNSName); err != nil {
logger.Error(err, "error handling resource update")
}
Expand All @@ -77,12 +79,13 @@ func (h *eventHandler) HandleEventBatch(ctx context.Context, logger logr.Logger,
objLabels := labels.Set(obj.GetLabels())
if h.labelSelector.Matches(objLabels) {
gatewayName := objLabels.Get(controller.GatewayLabel)
if gatewayName == "" {
gatewayName = obj.GetAnnotations()[controller.GatewayLabel]
}
gatewayNSName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: gatewayName}

if err := h.updateOrDeleteResources(ctx, logger, obj, gatewayNSName); err != nil {
logger.Error(err, "error handling resource update")
}

statusUpdate := &status.QueueObject{
Deployment: client.ObjectKeyFromObject(obj),
UpdateType: status.UpdateGateway,
Expand All @@ -94,8 +97,10 @@ func (h *eventHandler) HandleEventBatch(ctx context.Context, logger logr.Logger,
objLabels := labels.Set(obj.GetLabels())
if h.labelSelector.Matches(objLabels) {
gatewayName := objLabels.Get(controller.GatewayLabel)
if gatewayName == "" {
gatewayName = obj.GetAnnotations()[controller.GatewayLabel]
}
gatewayNSName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: gatewayName}

if err := h.updateOrDeleteResources(ctx, logger, obj, gatewayNSName); err != nil {
logger.Error(err, "error handling resource update")
}
Expand Down
7 changes: 6 additions & 1 deletion internal/controller/provisioner/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,17 @@ func (p *NginxProvisioner) buildNginxResourceObjects(

selectorLabels := make(map[string]string)
maps.Copy(selectorLabels, p.baseLabelSelector.MatchLabels)
selectorLabels[controller.GatewayLabel] = gateway.GetName()
selectorLabels[controller.AppNameLabel] = resourceName

labels := make(map[string]string)
annotations := make(map[string]string)

if len(gateway.GetName()) > controller.MaxServiceNameLen {
annotations[controller.GatewayLabel] = gateway.GetName()
} else {
selectorLabels[controller.GatewayLabel] = gateway.GetName()
}

maps.Copy(labels, selectorLabels)

if gateway.Spec.Infrastructure != nil {
Expand Down
5 changes: 2 additions & 3 deletions internal/controller/state/graph/backend_refs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha2"
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/sort"
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/state/conditions"
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/controller"
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/helpers"
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/kinds"
)
Expand Down Expand Up @@ -116,7 +115,7 @@ func addBackendRefsToRules(
}

poolName := types.NamespacedName{
Name: controller.GetInferencePoolName(string(ref.Name)),
Name: ref.InferencePoolName,
Namespace: namespace,
}

Expand Down Expand Up @@ -596,7 +595,7 @@ func validateBackendRefHTTPRoute(
inferencePool = true
inferencePoolName = types.NamespacedName{
Namespace: string(*ref.Namespace),
Name: controller.GetInferencePoolName(string(ref.Name)),
Name: ref.InferencePoolName,
}
default:
refNsName := types.NamespacedName{Namespace: string(*ref.Namespace), Name: string(ref.Name)}
Expand Down
7 changes: 5 additions & 2 deletions internal/controller/state/graph/backend_refs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ func TestValidateRouteBackendRef(t *testing.T) {
backend.Name = gatewayv1.ObjectName(controller.CreateInferencePoolServiceName("ipool"))
return backend
}),
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: "ipool",
},
expectedValid: true,
},
Expand Down Expand Up @@ -393,7 +394,8 @@ func TestValidateBackendRefHTTPRoute(t *testing.T) {
backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("invalid")
return backend
}),
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: "ipool",
},
refGrantResolver: alwaysFalseRefGrantResolver,
expectedValid: false,
Expand Down Expand Up @@ -1220,6 +1222,7 @@ func TestAddBackendRefsToRules(t *testing.T) {
route := createRoute("hr-inference", RouteTypeHTTP, "Service", 1, svcInferenceName)
// Mark the backend ref as IsInferencePool and set the port to nil (simulate InferencePool logic)
route.Spec.Rules[0].RouteBackendRefs[0].IsInferencePool = true
route.Spec.Rules[0].RouteBackendRefs[0].InferencePoolName = "ipool"
route.Spec.Rules[0].RouteBackendRefs[0].Port = nil
return route
}(),
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/state/graph/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ func TestBuildGraph(t *testing.T) {
}
rbrs := []RouteBackendRef{
{
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: "ipool",
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Group: helpers.GetPointer[gatewayv1.Group](""),
Expand Down
13 changes: 7 additions & 6 deletions internal/controller/state/graph/httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func processHTTPRouteRule(
// If route specifies an InferencePool backend, we need to convert it to its associated
// headless Service backend (that we created), so nginx config can be built properly.
// Only do this if the InferencePool actually exists.
if inferencePoolBackend(b, routeNamespace, inferencePools) {
if ok, key := inferencePoolBackend(b, routeNamespace, inferencePools); ok {
// We don't support traffic splitting at the Route level for
// InferencePool backends, so if there's more than one backendRef, and one of them
// is an InferencePool, we mark the rule as invalid.
Expand All @@ -239,7 +239,8 @@ func processHTTPRouteRule(

svcName := controller.CreateInferencePoolServiceName(string(b.Name))
rbr = RouteBackendRef{
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: key.Name,
BackendRef: v1.BackendRef{
BackendObjectReference: v1.BackendObjectReference{
Group: helpers.GetPointer[v1.Group](""),
Expand Down Expand Up @@ -346,12 +347,12 @@ func processHTTPRouteRules(
}

// inferencePoolBackend returns if a Route references an InferencePool backend
// and that InferencePool exists.
// and that InferencePool exists. Also returns the NamespacedName of the InferencePool.
func inferencePoolBackend(
backendRef v1.HTTPBackendRef,
routeNamespace string,
inferencePools map[types.NamespacedName]*inference.InferencePool,
) bool {
) (bool, types.NamespacedName) {
if backendRef.Group != nil &&
*backendRef.Group == inferenceAPIGroup &&
*backendRef.Kind == kinds.InferencePool {
Expand All @@ -364,11 +365,11 @@ func inferencePoolBackend(
Namespace: namespace,
}
if _, exists := inferencePools[key]; exists {
return true
return true, key
}
}

return false
return false, types.NamespacedName{}
}

func validateMatch(
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/state/graph/httproute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,8 @@ func TestBuildHTTPRoute(t *testing.T) {
Matches: hrInferencePool.Spec.Rules[0].Matches,
RouteBackendRefs: []RouteBackendRef{
{
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: "ipool",
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Group: helpers.GetPointer[gatewayv1.Group](""),
Expand Down
3 changes: 1 addition & 2 deletions internal/controller/state/graph/inferencepools.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
apiv1 "sigs.k8s.io/gateway-api/apis/v1"

"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/state/conditions"
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/controller"
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/kinds"
)

Expand Down Expand Up @@ -101,7 +100,7 @@ func processInferencePoolsForGateway(
}

poolName := types.NamespacedName{
Name: controller.GetInferencePoolName(string(ref.Name)),
Name: ref.InferencePoolName,
Namespace: namespace,
}

Expand Down
6 changes: 4 additions & 2 deletions internal/controller/state/graph/inferencepools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func TestBuildReferencedInferencePools(t *testing.T) {
{
RouteBackendRefs: []RouteBackendRef{
{
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: "pool",
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Namespace: helpers.GetPointer[gatewayv1.Namespace]("test"),
Expand Down Expand Up @@ -111,7 +112,8 @@ func TestBuildReferencedInferencePools(t *testing.T) {
{
RouteBackendRefs: []RouteBackendRef{
{
IsInferencePool: true,
IsInferencePool: true,
InferencePoolName: "pool",
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Kind: helpers.GetPointer[gatewayv1.Kind](kinds.Service),
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/state/graph/route_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ type RouteBackendRef struct {
// EndpointPickerConfig is the configuration for the EndpointPicker, if this backendRef is for an InferencePool.
EndpointPickerConfig EndpointPickerConfig

// InferencePoolName is the name of the InferencePool, if this backendRef is for an InferencePool.
InferencePoolName string

Filters []any

// IsInferencePool indicates if this backend is an InferencePool disguised as a Service.
Expand Down
42 changes: 28 additions & 14 deletions internal/framework/controller/resource.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
package controller

import (
"fmt"
"strings"
"crypto/sha256"
"encoding/hex"
)

// inferencePoolServiceSuffix is the suffix of the headless Service name for an InferencePool.
const inferencePoolServiceSuffix = "-pool-svc"
const (
// inferencePoolServiceSuffix is the suffix of the headless Service name for an InferencePool.
inferencePoolServiceSuffix = "pool-svc"
MaxServiceNameLen = 63
hashLen = 8
)

// CreateNginxResourceName creates the base resource name for all nginx resources
// created by the control plane.
func CreateNginxResourceName(prefix, suffix string) string {
return fmt.Sprintf("%s-%s", prefix, suffix)
return truncateAndHashName(prefix, suffix)
}

// CreateInferencePoolServiceName creates the name for a headless Service that
// we create for an InferencePool.
func CreateInferencePoolServiceName(name string) string {
svcName := fmt.Sprintf("%s%s", name, inferencePoolServiceSuffix)
// if InferencePool name is already at or near max length, just use that name
if len(svcName) > 253 {
return name
return truncateAndHashName(name, inferencePoolServiceSuffix)
}

// truncateAndHashName truncates the input name to fit within maxLen,
// appending a hash for uniqueness if needed.
func truncateAndHashName(name string, suffix string) string {
sep := "-"
full := name + sep + suffix
if len(full) <= MaxServiceNameLen {
return full
}

return svcName
}
// Always include the suffix, truncate name as needed
hash := sha256.Sum256([]byte(full))
hashStr := hex.EncodeToString(hash[:])[:hashLen]
maxNameLen := MaxServiceNameLen - (len(sep) * 2) - hashLen - len(suffix)
truncName := name
if len(name) > maxNameLen {
truncName = name[:maxNameLen]
}

// GetInferencePoolName returns the name of the InferencePool for a given headless Service name.
func GetInferencePoolName(serviceName string) string {
return strings.TrimSuffix(serviceName, inferencePoolServiceSuffix)
return truncName + sep + hashStr + sep + suffix
}
86 changes: 86 additions & 0 deletions internal/framework/controller/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package controller

import (
"strings"
"testing"

. "github.com/onsi/gomega"
)

func TestCreateNginxResourceName(t *testing.T) {
t.Parallel()

tests := []struct {
prefix string
suffix string
expected string
msg string
}{
{
prefix: "shortprefix",
suffix: "shortsuffix",
expected: "shortprefix-shortsuffix",
msg: "short names",
},
{
prefix: strings.Repeat("a", 64),
suffix: "suffix",
expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-b9a00a3c-suffix",
msg: "prefix is longer than max",
},
{
prefix: strings.Repeat("b", 60),
suffix: "suffix",
expected: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-1930ffb3-suffix",
msg: "prefix + suffix is longer than max",
},
}

for _, test := range tests {
t.Run(test.msg, func(t *testing.T) {
t.Parallel()
g := NewWithT(t)

name := CreateNginxResourceName(test.prefix, test.suffix)
g.Expect(len(name)).To(BeNumerically("<=", MaxServiceNameLen))
g.Expect(name).To(Equal(test.expected), "expected %q, got %q", test.expected, name)
})
}
}

func TestCreateInferencePoolServiceName(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expected string
msg string
}{
{
name: "pool",
expected: "pool-pool-svc",
msg: "short name",
},
{
name: strings.Repeat("a", 64),
expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-b33d7b99-pool-svc",
msg: "prefix is longer than max",
},
{
name: strings.Repeat("b", 60),
expected: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-af3f976c-pool-svc",
msg: "prefix + suffix is longer than max",
},
}

for _, test := range tests {
t.Run(test.msg, func(t *testing.T) {
t.Parallel()
g := NewWithT(t)

serviceName := CreateInferencePoolServiceName(test.name)
g.Expect(len(serviceName)).To(BeNumerically("<=", MaxServiceNameLen))
g.Expect(serviceName).To(Equal(test.expected), "expected %q, got %q", test.expected, serviceName)
})
}
}
Loading