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
10 changes: 10 additions & 0 deletions pkg/kgateway/query/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
)

func ProcessBackendError(err error, reporter reports.ParentRefReporter) {
var portNotFoundErr *krtcollections.BackendPortNotFoundError

switch {
case errors.Is(err, krtcollections.ErrUnknownBackendKind):
reporter.SetCondition(reports.RouteCondition{
Expand Down Expand Up @@ -55,6 +57,14 @@ func ProcessBackendError(err error, reporter reports.ParentRefReporter) {
Reason: gwv1.RouteReasonBackendNotFound,
Message: err.Error(),
})

case errors.As(err, &portNotFoundErr):
reporter.SetCondition(reports.RouteCondition{
Type: gwv1.RouteConditionResolvedRefs,
Status: metav1.ConditionFalse,
Reason: gwv1.RouteConditionReason("BackendPortNotFound"),
Message: err.Error(),
})
default:
// setting other errors to not found. not sure if there's a better option.
reporter.SetCondition(reports.RouteCondition{
Expand Down
11 changes: 11 additions & 0 deletions pkg/kgateway/translator/gateway/gateway_translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ func TestBasic(t *testing.T) {
})
})

t.Run("httproute with backend port not found error reports correctly", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "backends/backend-port-not-found-err.yaml",
outputFile: "backends/backend-port-not-found-err.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})

t.Run("httproute with invalid backend reports correctly", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "http-routing-invalid-backend",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: kgateway
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
parentRefs:
- name: example-gateway
hostnames:
- "test.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: example-svc
kind: test-backend-plugin
group: ""
port: 9999

---
apiVersion: v1
kind: Service
metadata:
name: example-svc
spec:
ports:
- protocol: TCP
port: 8080
targetPort: 80
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
Clusters:
- connectTimeout: 5s
edsClusterConfig:
edsConfig:
ads: {}
resourceApiVersion: V3
ignoreHealthOnHostRemoval: true
metadata: {}
name: kube_default_example-svc_8080
type: EDS
- connectTimeout: 5s
metadata: {}
name: test-backend-plugin_default_example-svc_80
Listeners:
- address:
socketAddress:
address: '::'
ipv4Compat: true
portValue: 80
filterChains:
- filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
httpFilters:
- name: envoy.filters.http.router
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
mergeSlashes: true
normalizePath: true
rds:
configSource:
ads: {}
resourceApiVersion: V3
routeConfigName: listener~80
statPrefix: http
useRemoteAddress: true
name: listener~80
name: listener~80
Routes:
- ignorePortInHostMatching: true
name: listener~80
virtualHosts:
- domains:
- test.example.com
name: listener~80~test_example_com
routes:
- match:
prefix: /
name: listener~80~test_example_com-route-0-httproute-example-route-default-0-0-matcher-0
route:
cluster: blackhole-cluster
clusterNotFoundResponseCode: INTERNAL_SERVER_ERROR
Statuses:
gateways:
default/example-gateway:
conditions:
- lastTransitionTime: null
message: ""
reason: ListenerSetsNotAllowed
status: Unknown
type: AttachedListenerSets
- lastTransitionTime: null
message: Successfully accepted Gateway
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: null
message: Successfully programmed Gateway
reason: Programmed
status: "True"
type: Programmed
listeners:
- attachedRoutes: 1
conditions:
- lastTransitionTime: null
message: Successfully accepted Listener
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: null
message: Successfully verified that Listener has no conflicts
reason: NoConflicts
status: "False"
type: Conflicted
- lastTransitionTime: null
message: Successfully resolved all references
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: null
message: Successfully programmed Listener
reason: Programmed
status: "True"
type: Programmed
name: http
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
- group: gateway.networking.k8s.io
kind: GRPCRoute
httpRoutes:
default/example-route:
parents:
- conditions:
- lastTransitionTime: null
message: port 9999 of Backend "example-svc" in namespace "default" is not defined
reason: BackendPortNotFound
status: "False"
type: ResolvedRefs
- lastTransitionTime: null
message: Successfully accepted Route
reason: Accepted
status: "True"
type: Accepted
controllerName: kgateway
parentRef:
group: ""
kind: ""
name: example-gateway
63 changes: 51 additions & 12 deletions pkg/krtcollections/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ func (e *BackendPortNotAllowedError) Error() string {
return fmt.Sprintf("BackendRef to \"%s\" includes a port. Do not specify a port when referencing a Backend resource, as it defines its own port configuration", e.BackendName)
}

type BackendPortNotFoundError struct {
Port int32
BackendName string
Namespace string
}

func (b *BackendPortNotFoundError) Error() string {
return fmt.Sprintf("port %v of Backend %q in namespace %q is not defined", b.Port, b.BackendName, b.Namespace)
}

// ListenerCollection defines an interface that returns the listeners belonging to the implementing struct
type ListenerCollection interface {
GetListeners() []gwv1.Listener
Expand All @@ -75,7 +85,8 @@ type BackendIndex struct {
// availableBackends are the backends as supplied by backend-contributed plugins.
// Any policies here are attached directly at Backend generation and not attached via
// policy index. Use availableBackendsWithPolicy when you need policy.
availableBackends map[schema.GroupKind]krt.Collection[ir.BackendObjectIR]
availableBackends map[schema.GroupKind]krt.Collection[ir.BackendObjectIR]
availableBackendNames map[schema.GroupKind]krt.Collection[krt.Named]
// aliasIndex indexes the availableBackends for a given GK by the BackendObjectIR's Alias
aliasIndex map[schema.GroupKind]krt.Index[backendKey, ir.BackendObjectIR]

Expand Down Expand Up @@ -104,12 +115,13 @@ func NewBackendIndex(
refgrants *RefGrantIndex,
) *BackendIndex {
return &BackendIndex{
policies: policies,
refgrants: refgrants,
availableBackends: map[schema.GroupKind]krt.Collection[ir.BackendObjectIR]{},
aliasIndex: map[schema.GroupKind]krt.Index[backendKey, ir.BackendObjectIR]{},
gkAliases: map[schema.GroupKind][]schema.GroupKind{},
krtopts: krtopts,
policies: policies,
refgrants: refgrants,
availableBackends: map[schema.GroupKind]krt.Collection[ir.BackendObjectIR]{},
aliasIndex: map[schema.GroupKind]krt.Index[backendKey, ir.BackendObjectIR]{},
gkAliases: map[schema.GroupKind][]schema.GroupKind{},
availableBackendNames: map[schema.GroupKind]krt.Collection[krt.Named]{},
krtopts: krtopts,
}
}

Expand All @@ -135,6 +147,11 @@ func (i *BackendIndex) HasSynced() bool {
return false
}
}
for _, col := range i.availableBackendNames {
if !col.HasSynced() {
return false
}
}
return true
}

Expand Down Expand Up @@ -195,6 +212,12 @@ func (i *BackendIndex) AddBackends(gk schema.GroupKind, col krt.Collection[ir.Ba
return aliasKeys
})
i.availableBackends[gk] = col
i.availableBackendNames[gk] = krt.NewCollection(col, func(kctx krt.HandlerContext, backendObj ir.BackendObjectIR) *krt.Named {
return &krt.Named{
Name: backendObj.ObjectSource.Name,
Namespace: backendObj.ObjectSource.Namespace,
}
}, i.krtopts.ToOptions("")...)
i.aliasIndex[gk] = idx
i.availableBackendsWithPolicy = append(i.availableBackendsWithPolicy, backendsWithPoliciesCol)
i.backendsRequiringPolicyStatus = append(i.backendsRequiringPolicyStatus, backendsRequiringPolicyStatus)
Expand Down Expand Up @@ -232,9 +255,19 @@ func (i *BackendIndex) getBackend(kctx krt.HandlerContext, gk schema.GroupKind,
up := krt.FetchOne(kctx, col, krt.FilterKey(ir.BackendResourceName(key, port, "")))
if up == nil {
var err error
nn := krt.Named{Name: n.Name, Namespace: n.Namespace}
nameCol := i.availableBackendNames[gk]
maybeName := krt.FetchOne(kctx, nameCol, krt.FilterKey(nn.ResourceName()))

if maybeName != nil {
return nil, &BackendPortNotFoundError{
Port: port,
BackendName: n.Name,
Namespace: n.Namespace,
}
}

if up, err = i.getBackendFromAlias(kctx, gk, n, port); err != nil {
// getBackendFromAlias returns ErrUnknownBackendKind when there are no aliases
// so return our own NotFoundError here
return nil, &NotFoundError{NotFoundObj: key}
}
}
Expand Down Expand Up @@ -272,6 +305,10 @@ func (i *BackendIndex) getBackendFromAlias(kctx krt.HandlerContext, gk schema.Gr
return nil, ErrUnknownBackendKind
}

if len(results) == 0 {
return nil, &NotFoundError{NotFoundObj: key.ObjectSource}
}

var out *ir.BackendObjectIR

// must return only one
Expand All @@ -286,11 +323,13 @@ func (i *BackendIndex) getBackendFromAlias(kctx krt.HandlerContext, gk schema.Gr
}
}

if out == nil {
return nil, &NotFoundError{NotFoundObj: key.ObjectSource}
for _, res := range results {
if res.Port == port {
return out, nil
}
}

return out, nil
return nil, &BackendPortNotFoundError{Port: port, BackendName: n.Name, Namespace: n.Namespace}
}

func (i *BackendIndex) getBackendFromRef(kctx krt.HandlerContext, localns string, ref gwv1.BackendObjectReference) (*ir.BackendObjectIR, error) {
Expand Down