mirror of
https://github.com/hanzo-ml/kserve.git
synced 2026-10-30 20:53:49 +00:00
790 lines
24 KiB
Go
790 lines
24 KiB
Go
/*
|
|
Copyright 2025 The KServe Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package fixture
|
|
|
|
import (
|
|
"cmp"
|
|
"slices"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/utils/ptr"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
igwapi "sigs.k8s.io/gateway-api-inference-extension/api/v1"
|
|
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
|
|
)
|
|
|
|
type ObjectOption[T client.Object] func(T)
|
|
|
|
type GatewayOption ObjectOption[*gwapiv1.Gateway]
|
|
|
|
func Gateway(name string, opts ...GatewayOption) *gwapiv1.Gateway {
|
|
gw := &gwapiv1.Gateway{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Spec: gwapiv1.GatewaySpec{
|
|
GatewayClassName: defaultGatewayClass,
|
|
Listeners: []gwapiv1.Listener{},
|
|
Infrastructure: &gwapiv1.GatewayInfrastructure{},
|
|
},
|
|
Status: gwapiv1.GatewayStatus{
|
|
Addresses: []gwapiv1.GatewayStatusAddress{},
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(gw)
|
|
}
|
|
|
|
return gw
|
|
}
|
|
|
|
func InNamespace[T metav1.Object](namespace string) func(T) {
|
|
return func(t T) {
|
|
t.SetNamespace(namespace)
|
|
}
|
|
}
|
|
|
|
func WithClassName(className string) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
gw.Spec.GatewayClassName = gwapiv1.ObjectName(className)
|
|
}
|
|
}
|
|
|
|
func WithInfrastructureLabels(key, value string) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
if gw.Spec.Infrastructure.Labels == nil {
|
|
gw.Spec.Infrastructure.Labels = make(map[gwapiv1.LabelKey]gwapiv1.LabelValue)
|
|
}
|
|
gw.Spec.Infrastructure.Labels[gwapiv1.LabelKey(key)] = gwapiv1.LabelValue(value)
|
|
}
|
|
}
|
|
|
|
func WithListener(protocol gwapiv1.ProtocolType) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
port := gwapiv1.PortNumber(80)
|
|
if protocol == gwapiv1.HTTPSProtocolType {
|
|
port = gwapiv1.PortNumber(443)
|
|
}
|
|
listener := gwapiv1.Listener{
|
|
Name: gwapiv1.SectionName("listener"),
|
|
Protocol: protocol,
|
|
Port: port,
|
|
}
|
|
gw.Spec.Listeners = append(gw.Spec.Listeners, listener)
|
|
}
|
|
}
|
|
|
|
func WithListeners(listeners ...gwapiv1.Listener) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
gw.Spec.Listeners = append(gw.Spec.Listeners, listeners...)
|
|
}
|
|
}
|
|
|
|
type (
|
|
HTTPRouteOption ObjectOption[*gwapiv1.HTTPRoute]
|
|
ParentRefOption func(*gwapiv1.ParentReference)
|
|
)
|
|
|
|
func HTTPRoute(name string, opts ...HTTPRouteOption) *gwapiv1.HTTPRoute {
|
|
route := &gwapiv1.HTTPRoute{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Spec: gwapiv1.HTTPRouteSpec{
|
|
CommonRouteSpec: gwapiv1.CommonRouteSpec{
|
|
ParentRefs: []gwapiv1.ParentReference{},
|
|
},
|
|
Hostnames: []gwapiv1.Hostname{},
|
|
Rules: []gwapiv1.HTTPRouteRule{},
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(route)
|
|
}
|
|
|
|
return route
|
|
}
|
|
|
|
func WithParentRef(ref gwapiv1.ParentReference) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
route.Spec.ParentRefs = append(route.Spec.ParentRefs, ref)
|
|
}
|
|
}
|
|
|
|
func WithParentRefs(refs ...gwapiv1.ParentReference) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
route.Spec.ParentRefs = refs
|
|
}
|
|
}
|
|
|
|
func WithHostnames(hostnames ...string) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
route.Spec.Hostnames = make([]gwapiv1.Hostname, len(hostnames))
|
|
for i, hostname := range hostnames {
|
|
route.Spec.Hostnames[i] = gwapiv1.Hostname(hostname)
|
|
}
|
|
}
|
|
}
|
|
|
|
func WithAddresses(addresses ...string) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
gw.Status.Addresses = make([]gwapiv1.GatewayStatusAddress, len(addresses))
|
|
for i, address := range addresses {
|
|
gw.Status.Addresses[i] = gwapiv1.GatewayStatusAddress{
|
|
Value: address,
|
|
// Type is left as nil (defaults to IPAddressType behavior)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func WithHostnameAddresses(addresses ...string) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
gw.Status.Addresses = make([]gwapiv1.GatewayStatusAddress, len(addresses))
|
|
for i, address := range addresses {
|
|
gw.Status.Addresses[i] = gwapiv1.GatewayStatusAddress{
|
|
Type: ptr.To(gwapiv1.HostnameAddressType),
|
|
Value: address,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func WithMixedAddresses(addresses ...gwapiv1.GatewayStatusAddress) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
gw.Status.Addresses = addresses
|
|
}
|
|
}
|
|
|
|
func IPAddress(value string) gwapiv1.GatewayStatusAddress {
|
|
return gwapiv1.GatewayStatusAddress{
|
|
Type: ptr.To(gwapiv1.IPAddressType),
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
func HostnameAddress(value string) gwapiv1.GatewayStatusAddress {
|
|
return gwapiv1.GatewayStatusAddress{
|
|
Type: ptr.To(gwapiv1.HostnameAddressType),
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
func WithPath(path string) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
rule := gwapiv1.HTTPRouteRule{
|
|
Matches: []gwapiv1.HTTPRouteMatch{
|
|
{
|
|
Path: &gwapiv1.HTTPPathMatch{
|
|
Value: ptr.To(path),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
route.Spec.Rules = append(route.Spec.Rules, rule)
|
|
}
|
|
}
|
|
|
|
func WithRules(rules ...gwapiv1.HTTPRouteRule) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
route.Spec.Rules = rules
|
|
}
|
|
}
|
|
|
|
func GatewayRef(name string, opts ...ParentRefOption) gwapiv1.ParentReference {
|
|
ref := gwapiv1.ParentReference{
|
|
Name: gwapiv1.ObjectName(name),
|
|
Group: ptr.To(gwapiv1.Group("gateway.networking.k8s.io")),
|
|
Kind: ptr.To(gwapiv1.Kind("Gateway")),
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&ref)
|
|
}
|
|
return ref
|
|
}
|
|
|
|
func RefInNamespace(namespace string) ParentRefOption {
|
|
return func(ref *gwapiv1.ParentReference) {
|
|
ref.Namespace = ptr.To(gwapiv1.Namespace(namespace))
|
|
}
|
|
}
|
|
|
|
func GatewayRefWithoutNamespace(name string) gwapiv1.ParentReference {
|
|
return gwapiv1.ParentReference{
|
|
Name: gwapiv1.ObjectName(name),
|
|
Group: ptr.To(gwapiv1.Group("gateway.networking.k8s.io")),
|
|
Kind: ptr.To(gwapiv1.Kind("Gateway")),
|
|
// Namespace intentionally omitted
|
|
}
|
|
}
|
|
|
|
func HTTPSGateway(name, namespace string, addresses ...string) *gwapiv1.Gateway {
|
|
return Gateway(name,
|
|
InNamespace[*gwapiv1.Gateway](namespace),
|
|
WithListener(gwapiv1.HTTPSProtocolType),
|
|
WithAddresses(addresses...),
|
|
)
|
|
}
|
|
|
|
func HTTPGateway(name, namespace string, addresses ...string) *gwapiv1.Gateway {
|
|
return Gateway(name,
|
|
InNamespace[*gwapiv1.Gateway](namespace),
|
|
WithListener(gwapiv1.HTTPProtocolType),
|
|
WithAddresses(addresses...),
|
|
)
|
|
}
|
|
|
|
type (
|
|
HTTPRouteRuleOption func(*gwapiv1.HTTPRouteRule)
|
|
HTTPBackendRefOption func(*gwapiv1.HTTPBackendRef)
|
|
)
|
|
|
|
func WithHTTPRouteRule(rule gwapiv1.HTTPRouteRule) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
route.Spec.Rules = append(route.Spec.Rules, rule)
|
|
}
|
|
}
|
|
|
|
func HTTPRouteRule(opts ...HTTPRouteRuleOption) gwapiv1.HTTPRouteRule {
|
|
rule := gwapiv1.HTTPRouteRule{
|
|
Matches: []gwapiv1.HTTPRouteMatch{},
|
|
BackendRefs: []gwapiv1.HTTPBackendRef{},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(&rule)
|
|
}
|
|
|
|
return rule
|
|
}
|
|
|
|
func WithMatches(matches ...gwapiv1.HTTPRouteMatch) HTTPRouteRuleOption {
|
|
return func(rule *gwapiv1.HTTPRouteRule) {
|
|
rule.Matches = append(rule.Matches, matches...)
|
|
}
|
|
}
|
|
|
|
func WithBackendRefs(refs ...gwapiv1.HTTPBackendRef) HTTPRouteRuleOption {
|
|
return func(rule *gwapiv1.HTTPRouteRule) {
|
|
rule.BackendRefs = append(rule.BackendRefs, refs...)
|
|
}
|
|
}
|
|
|
|
func BackendRefInferencePool(name string) gwapiv1.HTTPBackendRef {
|
|
return gwapiv1.HTTPBackendRef{
|
|
BackendRef: gwapiv1.BackendRef{
|
|
BackendObjectReference: gwapiv1.BackendObjectReference{
|
|
Group: ptr.To(gwapiv1.Group("inference.networking.k8s.io")),
|
|
Kind: ptr.To(gwapiv1.Kind("InferencePool")),
|
|
Name: gwapiv1.ObjectName(name),
|
|
Port: ptr.To(gwapiv1.PortNumber(8000)),
|
|
},
|
|
Weight: ptr.To(int32(1)),
|
|
},
|
|
}
|
|
}
|
|
|
|
func BackendRefInferencePoolV1Alpha2(name string) gwapiv1.HTTPBackendRef {
|
|
return gwapiv1.HTTPBackendRef{
|
|
BackendRef: gwapiv1.BackendRef{
|
|
BackendObjectReference: gwapiv1.BackendObjectReference{
|
|
Group: ptr.To(gwapiv1.Group("inference.networking.x-k8s.io")),
|
|
Kind: ptr.To(gwapiv1.Kind("InferencePool")),
|
|
Name: gwapiv1.ObjectName(name),
|
|
Port: ptr.To(gwapiv1.PortNumber(8000)),
|
|
},
|
|
Weight: ptr.To(int32(1)),
|
|
},
|
|
}
|
|
}
|
|
|
|
func BackendRefService(name string) gwapiv1.HTTPBackendRef {
|
|
return gwapiv1.HTTPBackendRef{
|
|
BackendRef: gwapiv1.BackendRef{
|
|
BackendObjectReference: gwapiv1.BackendObjectReference{
|
|
Group: ptr.To(gwapiv1.Group("")),
|
|
Kind: ptr.To(gwapiv1.Kind("Service")),
|
|
Name: gwapiv1.ObjectName(name),
|
|
Port: ptr.To(gwapiv1.PortNumber(8000)),
|
|
},
|
|
Weight: ptr.To(int32(1)),
|
|
},
|
|
}
|
|
}
|
|
|
|
func WithTimeouts(backendTimeout, requestTimeout string) HTTPRouteRuleOption {
|
|
return func(rule *gwapiv1.HTTPRouteRule) {
|
|
rule.Timeouts = &gwapiv1.HTTPRouteTimeouts{
|
|
BackendRequest: ptr.To(gwapiv1.Duration(backendTimeout)),
|
|
Request: ptr.To(gwapiv1.Duration(requestTimeout)),
|
|
}
|
|
}
|
|
}
|
|
|
|
func WithFilters(filters ...gwapiv1.HTTPRouteFilter) HTTPRouteRuleOption {
|
|
return func(rule *gwapiv1.HTTPRouteRule) {
|
|
rule.Filters = append(rule.Filters, filters...)
|
|
}
|
|
}
|
|
|
|
func WithHTTPRule(ruleOpts ...HTTPRouteRuleOption) HTTPRouteOption {
|
|
return WithHTTPRouteRule(HTTPRouteRule(ruleOpts...))
|
|
}
|
|
|
|
func Matches(matches ...gwapiv1.HTTPRouteMatch) HTTPRouteRuleOption {
|
|
return WithMatches(matches...)
|
|
}
|
|
|
|
func Timeouts(backendTimeout, requestTimeout string) HTTPRouteRuleOption {
|
|
return WithTimeouts(backendTimeout, requestTimeout)
|
|
}
|
|
|
|
func Filters(filters ...gwapiv1.HTTPRouteFilter) HTTPRouteRuleOption {
|
|
return WithFilters(filters...)
|
|
}
|
|
|
|
func PathPrefixMatch(path string) gwapiv1.HTTPRouteMatch {
|
|
return gwapiv1.HTTPRouteMatch{
|
|
Path: &gwapiv1.HTTPPathMatch{
|
|
Type: ptr.To(gwapiv1.PathMatchPathPrefix),
|
|
Value: ptr.To(path),
|
|
},
|
|
}
|
|
}
|
|
|
|
func ExactPathWithHeaderMatch(path, headerName, headerValue string) gwapiv1.HTTPRouteMatch {
|
|
return gwapiv1.HTTPRouteMatch{
|
|
Path: &gwapiv1.HTTPPathMatch{
|
|
Type: ptr.To(gwapiv1.PathMatchExact),
|
|
Value: ptr.To(path),
|
|
},
|
|
Headers: []gwapiv1.HTTPHeaderMatch{
|
|
{
|
|
Type: ptr.To(gwapiv1.HeaderMatchExact),
|
|
Name: gwapiv1.HTTPHeaderName(headerName),
|
|
Value: headerValue,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func HeaderOnlyMatch(headerName, headerValue string) gwapiv1.HTTPRouteMatch {
|
|
return gwapiv1.HTTPRouteMatch{
|
|
Path: &gwapiv1.HTTPPathMatch{
|
|
Type: ptr.To(gwapiv1.PathMatchPathPrefix),
|
|
Value: ptr.To("/"),
|
|
},
|
|
Headers: []gwapiv1.HTTPHeaderMatch{
|
|
{
|
|
Type: ptr.To(gwapiv1.HeaderMatchExact),
|
|
Name: gwapiv1.HTTPHeaderName(headerName),
|
|
Value: headerValue,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func ServiceRef(name string, port int32, weight int32) gwapiv1.HTTPBackendRef {
|
|
return gwapiv1.HTTPBackendRef{
|
|
BackendRef: gwapiv1.BackendRef{
|
|
BackendObjectReference: gwapiv1.BackendObjectReference{
|
|
Kind: ptr.To(gwapiv1.Kind("Service")),
|
|
Name: gwapiv1.ObjectName(name),
|
|
Port: ptr.To(port),
|
|
},
|
|
Weight: ptr.To(weight),
|
|
},
|
|
}
|
|
}
|
|
|
|
func HTTPRouteRuleWithBackendAndTimeouts(backendName string, backendPort int32, path string, backendTimeout, requestTimeout string) gwapiv1.HTTPRouteRule {
|
|
return gwapiv1.HTTPRouteRule{
|
|
BackendRefs: []gwapiv1.HTTPBackendRef{
|
|
ServiceRef(backendName, backendPort, 1),
|
|
},
|
|
Matches: []gwapiv1.HTTPRouteMatch{
|
|
PathPrefixMatch(path),
|
|
},
|
|
Timeouts: &gwapiv1.HTTPRouteTimeouts{
|
|
BackendRequest: ptr.To(gwapiv1.Duration(backendTimeout)),
|
|
Request: ptr.To(gwapiv1.Duration(requestTimeout)),
|
|
},
|
|
}
|
|
}
|
|
|
|
func GatewayParentRef(name, namespace string) gwapiv1.ParentReference {
|
|
return gwapiv1.ParentReference{
|
|
Group: ptr.To(gwapiv1.Group("gateway.networking.k8s.io")),
|
|
Kind: ptr.To(gwapiv1.Kind("Gateway")),
|
|
Name: gwapiv1.ObjectName(name),
|
|
Namespace: ptr.To(gwapiv1.Namespace(namespace)),
|
|
}
|
|
}
|
|
|
|
func GatewayParentRefWithSection(name, namespace, sectionName string) gwapiv1.ParentReference {
|
|
ref := GatewayParentRef(name, namespace)
|
|
ref.SectionName = ptr.To(gwapiv1.SectionName(sectionName))
|
|
return ref
|
|
}
|
|
|
|
// WithGatewayCondition creates a GatewayOption that sets specific status conditions
|
|
func WithGatewayCondition(conditionType string, status metav1.ConditionStatus, reason, message string) GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
condition := metav1.Condition{
|
|
Type: conditionType,
|
|
Status: status,
|
|
Reason: reason,
|
|
Message: message,
|
|
}
|
|
gw.Status.Conditions = append(gw.Status.Conditions, condition)
|
|
}
|
|
}
|
|
|
|
// WithProgrammedCondition is a convenience function for setting the Programmed condition
|
|
func WithProgrammedCondition(status metav1.ConditionStatus, reason, message string) GatewayOption {
|
|
return WithGatewayCondition(string(gwapiv1.GatewayConditionProgrammed), status, reason, message)
|
|
}
|
|
|
|
// HTTPRoute Status Options
|
|
|
|
// WithHTTPRouteParentStatus adds parent status to the HTTPRoute
|
|
func WithHTTPRouteParentStatus(parentRef gwapiv1.ParentReference, controllerName string, conditions ...metav1.Condition) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
parentStatus := gwapiv1.RouteParentStatus{
|
|
ParentRef: parentRef,
|
|
ControllerName: gwapiv1.GatewayController(controllerName),
|
|
Conditions: conditions,
|
|
}
|
|
route.Status.Parents = append(route.Status.Parents, parentStatus)
|
|
}
|
|
}
|
|
|
|
// WithHTTPRouteReadyStatus sets the HTTPRoute status to ready for all parent refs
|
|
func WithHTTPRouteReadyStatus(controllerName string) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
if len(route.Spec.ParentRefs) > 0 {
|
|
route.Status.Parents = make([]gwapiv1.RouteParentStatus, len(route.Spec.ParentRefs))
|
|
for i, parentRef := range route.Spec.ParentRefs {
|
|
route.Status.Parents[i] = gwapiv1.RouteParentStatus{
|
|
ParentRef: parentRef,
|
|
ControllerName: gwapiv1.GatewayController(controllerName),
|
|
Conditions: []metav1.Condition{
|
|
{
|
|
Type: string(gwapiv1.RouteConditionAccepted),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "HTTPRoute accepted",
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
{
|
|
Type: string(gwapiv1.RouteConditionResolvedRefs),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "ResolvedRefs",
|
|
Message: "HTTPRoute references resolved",
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithGatewayReadyStatus sets the Gateway status to ready (Accepted and Programmed)
|
|
func WithGatewayReadyStatus() GatewayOption {
|
|
return func(gw *gwapiv1.Gateway) {
|
|
gw.Status.Conditions = []metav1.Condition{
|
|
{
|
|
Type: string(gwapiv1.GatewayConditionAccepted),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "Gateway accepted",
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
{
|
|
Type: string(gwapiv1.GatewayConditionProgrammed),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Ready",
|
|
Message: "Gateway is ready",
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithHTTPRouteNotReadyStatus sets the HTTPRoute status to not ready (for negative testing)
|
|
func WithHTTPRouteNotReadyStatus(controllerName, reason, message string) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
if len(route.Spec.ParentRefs) > 0 {
|
|
route.Status.Parents = make([]gwapiv1.RouteParentStatus, len(route.Spec.ParentRefs))
|
|
for i, parentRef := range route.Spec.ParentRefs {
|
|
route.Status.Parents[i] = gwapiv1.RouteParentStatus{
|
|
ParentRef: parentRef,
|
|
ControllerName: gwapiv1.GatewayController(controllerName),
|
|
Conditions: []metav1.Condition{
|
|
{
|
|
Type: string(gwapiv1.RouteConditionAccepted),
|
|
Status: metav1.ConditionFalse,
|
|
Reason: reason,
|
|
Message: message,
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithHTTPRouteV1Alpha2RejectedStatus sets the HTTPRoute status to indicate Gateway rejection
|
|
// of v1alpha2 backendRef (ResolvedRefs=False, Reason=InvalidKind). This simulates what happens
|
|
// when Envoy Gateway or Istio rejects an unsupported InferencePool API group.
|
|
func WithHTTPRouteV1Alpha2RejectedStatus(controllerName string) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
if len(route.Spec.ParentRefs) > 0 {
|
|
route.Status.Parents = make([]gwapiv1.RouteParentStatus, len(route.Spec.ParentRefs))
|
|
for i, parentRef := range route.Spec.ParentRefs {
|
|
route.Status.Parents[i] = gwapiv1.RouteParentStatus{
|
|
ParentRef: parentRef,
|
|
ControllerName: gwapiv1.GatewayController(controllerName),
|
|
Conditions: []metav1.Condition{
|
|
{
|
|
Type: string(gwapiv1.RouteConditionAccepted),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "HTTPRoute accepted",
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
{
|
|
Type: string(gwapiv1.RouteConditionResolvedRefs),
|
|
Status: metav1.ConditionFalse,
|
|
Reason: "InvalidKind",
|
|
Message: "Group is invalid: inference.networking.x-k8s.io",
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advanced fixture patterns for custom conditions
|
|
|
|
// KuadrantControllerStatus creates a RouteParentStatus for Kuadrant policy controller
|
|
func KuadrantControllerStatus(parentRef gwapiv1.ParentReference, generation int64) gwapiv1.RouteParentStatus {
|
|
return gwapiv1.RouteParentStatus{
|
|
ParentRef: parentRef,
|
|
ControllerName: "kuadrant.io/policy-controller",
|
|
Conditions: []metav1.Condition{
|
|
{
|
|
Type: "kuadrant.io/AuthPolicyAffected",
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "Object affected by AuthPolicy [openshift-ingress/gateway-auth-policy]",
|
|
LastTransitionTime: metav1.Now(),
|
|
ObservedGeneration: generation,
|
|
},
|
|
{
|
|
Type: "kuadrant.io/RateLimitPolicyAffected",
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "Object affected by RateLimitPolicy [openshift-ingress/gateway-rate-limits]",
|
|
LastTransitionTime: metav1.Now(),
|
|
ObservedGeneration: generation,
|
|
},
|
|
{
|
|
Type: "kuadrant.io/TokenRateLimitPolicyAffected",
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "Object affected by TokenRateLimitPolicy [openshift-ingress/gateway-token-rate-limits]",
|
|
LastTransitionTime: metav1.Now(),
|
|
ObservedGeneration: generation,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// StatusFunc is a function that creates a RouteParentStatus for a given parent ref and generation.
|
|
type StatusFunc func(parentRef gwapiv1.ParentReference, generation int64) gwapiv1.RouteParentStatus
|
|
|
|
// gatewayAPIControllerStatusWith creates a StatusFunc with the given ResolvedRefs condition.
|
|
func gatewayAPIControllerStatusWith(resolvedRefsStatus metav1.ConditionStatus, reason, message string) StatusFunc {
|
|
return func(parentRef gwapiv1.ParentReference, generation int64) gwapiv1.RouteParentStatus {
|
|
return gwapiv1.RouteParentStatus{
|
|
ParentRef: parentRef,
|
|
ControllerName: "gateway-controller/v1",
|
|
Conditions: []metav1.Condition{
|
|
{
|
|
Type: string(gwapiv1.RouteConditionAccepted),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: "Accepted",
|
|
Message: "Route was valid",
|
|
LastTransitionTime: metav1.Now(),
|
|
ObservedGeneration: generation,
|
|
},
|
|
{
|
|
Type: string(gwapiv1.RouteConditionResolvedRefs),
|
|
Status: resolvedRefsStatus,
|
|
Reason: reason,
|
|
Message: message,
|
|
LastTransitionTime: metav1.Now(),
|
|
ObservedGeneration: generation,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
// GatewayAPIControllerStatus creates a RouteParentStatus with ResolvedRefs=True.
|
|
GatewayAPIControllerStatus = gatewayAPIControllerStatusWith(metav1.ConditionTrue, "ResolvedRefs", "All references resolved")
|
|
|
|
// GatewayAPIControllerStatusInvalidKind creates a RouteParentStatus with ResolvedRefs=False, Reason=InvalidKind.
|
|
GatewayAPIControllerStatusInvalidKind = gatewayAPIControllerStatusWith(metav1.ConditionFalse, string(gwapiv1.RouteReasonInvalidKind), "Backend kind InferencePool is not supported")
|
|
|
|
// GatewayAPIControllerStatusBackendNotFound creates a RouteParentStatus with ResolvedRefs=False, Reason=BackendNotFound.
|
|
GatewayAPIControllerStatusBackendNotFound = gatewayAPIControllerStatusWith(metav1.ConditionFalse, string(gwapiv1.RouteReasonBackendNotFound), "Backend not found")
|
|
)
|
|
|
|
// WithHTTPRouteMultipleControllerStatus sets HTTPRoute status with multiple controllers
|
|
// This simulates real-world scenarios where policy controllers and gateway controllers
|
|
// both add status entries for the same parent ref
|
|
func WithHTTPRouteMultipleControllerStatus(parentRef gwapiv1.ParentReference, statusFuncs ...StatusFunc) HTTPRouteOption {
|
|
return func(route *gwapiv1.HTTPRoute) {
|
|
for _, statusFunc := range statusFuncs {
|
|
status := statusFunc(parentRef, route.Generation)
|
|
route.Status.Parents = append(route.Status.Parents, status)
|
|
}
|
|
}
|
|
}
|
|
|
|
type InferencePoolOption ObjectOption[*igwapi.InferencePool]
|
|
|
|
func InferencePool(name string, opts ...InferencePoolOption) *igwapi.InferencePool {
|
|
pool := &igwapi.InferencePool{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Spec: igwapi.InferencePoolSpec{
|
|
Selector: igwapi.LabelSelector{
|
|
MatchLabels: make(map[igwapi.LabelKey]igwapi.LabelValue),
|
|
},
|
|
TargetPorts: []igwapi.Port{{Number: 8000}},
|
|
},
|
|
Status: igwapi.InferencePoolStatus{
|
|
Parents: []igwapi.ParentStatus{},
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(pool)
|
|
}
|
|
|
|
return pool
|
|
}
|
|
|
|
func WithSelector(key, value string) InferencePoolOption {
|
|
return func(pool *igwapi.InferencePool) {
|
|
if pool.Spec.Selector.MatchLabels == nil {
|
|
pool.Spec.Selector.MatchLabels = make(map[igwapi.LabelKey]igwapi.LabelValue)
|
|
}
|
|
pool.Spec.Selector.MatchLabels[igwapi.LabelKey(key)] = igwapi.LabelValue(value)
|
|
}
|
|
}
|
|
|
|
func WithTargetPort(port int32) InferencePoolOption {
|
|
return func(pool *igwapi.InferencePool) {
|
|
pool.Spec.TargetPorts = []igwapi.Port{{Number: igwapi.PortNumber(port)}}
|
|
}
|
|
}
|
|
|
|
func WithEndpointPickerRef(group, kind, name string, port int32) InferencePoolOption {
|
|
return func(pool *igwapi.InferencePool) {
|
|
pool.Spec.EndpointPickerRef = igwapi.EndpointPickerRef{
|
|
Group: ptr.To(igwapi.Group(group)),
|
|
Kind: igwapi.Kind(kind),
|
|
Name: igwapi.ObjectName(name),
|
|
Port: &igwapi.Port{Number: igwapi.PortNumber(port)},
|
|
FailureMode: igwapi.EndpointPickerFailOpen,
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithExtensionRef is deprecated - use WithEndpointPickerRef instead.
|
|
// Kept for backward compatibility.
|
|
func WithExtensionRef(group, kind, name string, port int32) InferencePoolOption {
|
|
return WithEndpointPickerRef(group, kind, name, port)
|
|
}
|
|
|
|
func WithInferencePoolReadyStatus(gatewayRefs ...igwapi.ParentReference) InferencePoolOption {
|
|
return func(pool *igwapi.InferencePool) {
|
|
if len(gatewayRefs) == 0 {
|
|
gatewayRefs = []igwapi.ParentReference{{
|
|
Group: ptr.To(igwapi.Group("gateway.networking.k8s.io")),
|
|
Kind: igwapi.Kind("Gateway"),
|
|
Name: igwapi.ObjectName("gateway"),
|
|
}}
|
|
}
|
|
pool.Status.Parents = make([]igwapi.ParentStatus, 0, len(gatewayRefs))
|
|
for _, ref := range gatewayRefs {
|
|
pool.Status.Parents = append(pool.Status.Parents, igwapi.ParentStatus{
|
|
ParentRef: ref,
|
|
Conditions: []metav1.Condition{
|
|
{
|
|
Type: string(igwapi.InferencePoolConditionAccepted),
|
|
Status: metav1.ConditionTrue,
|
|
Reason: string(igwapi.InferencePoolReasonAccepted),
|
|
LastTransitionTime: metav1.Now(),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
slices.SortFunc(pool.Status.Parents, func(a, b igwapi.ParentStatus) int {
|
|
if c := cmp.Compare(string(a.ParentRef.Namespace), string(b.ParentRef.Namespace)); c != 0 {
|
|
return c
|
|
}
|
|
return cmp.Compare(string(a.ParentRef.Name), string(b.ParentRef.Name))
|
|
})
|
|
}
|
|
}
|
|
|
|
// InferencePoolParentRefsFromRoutes converts HTTPRoute parentRefs to InferencePool ParentReferences.
|
|
// Namespace is resolved to the route's namespace when omitted, matching Gateway API defaulting.
|
|
func InferencePoolParentRefsFromRoutes(routes []gwapiv1.HTTPRoute) []igwapi.ParentReference {
|
|
var refs []igwapi.ParentReference
|
|
for _, route := range routes {
|
|
for _, pr := range route.Spec.ParentRefs {
|
|
ref := igwapi.ParentReference{
|
|
Name: igwapi.ObjectName(pr.Name),
|
|
Namespace: igwapi.Namespace(ptr.Deref(pr.Namespace, gwapiv1.Namespace(route.Namespace))),
|
|
}
|
|
if pr.Group != nil {
|
|
g := igwapi.Group(*pr.Group)
|
|
ref.Group = &g
|
|
}
|
|
if pr.Kind != nil {
|
|
ref.Kind = igwapi.Kind(*pr.Kind)
|
|
}
|
|
refs = append(refs, ref)
|
|
}
|
|
}
|
|
return refs
|
|
}
|