Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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 @@ -237,6 +237,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
37 changes: 32 additions & 5 deletions pkg/krtcollections/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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 Down Expand Up @@ -231,9 +241,20 @@ 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
var found bool
for _, backend := range col.List() {
if backend.GetNamespace() == n.Namespace &&
backend.GetName() == n.Name &&
backend.GetGroupKind().Kind == gk.Kind {
found = true
break
}
}
if found {
return nil, &BackendPortNotFoundError{Port: port, BackendName: n.Name, Namespace: n.Namespace}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to account for alias logic below (see the call to getBackendFromAlias) - do the error logic if getBackendFromAlias fails

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the error logic already there - https://github.com/kgateway-dev/kgateway/pull/13178/changes#diff-9760745752b7c20a2c9236474a1a5c82973b1399cbd0bd814e67a1ace8997c2dR270-R271 ?
Correct me if I'm wrong here, I didn't fully understand this comment!

}

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 @@ -271,6 +292,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 @@ -285,11 +310,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
Loading