Skip to content

Commit

Permalink
Fix status reporting for invalid HTTPRoutes (#582)
Browse files Browse the repository at this point in the history
918d650 introduced a bug which
made NKG fail to report status conditions for an invalid HTTPRoute.
It would try to report an empty condition in the status and fail
because of the status CRD validation (example error):

{"level":"error","ts":"2023-04-18T15:37:49Z","logger":"statusUpdater","msg":"Failed to update status","namespace":"default","name":"coffee","kind":"HTTPRoute","error":"HTTPRoute.gateway.networking.k8s.io \"coffee\" is invalid: [status.parents[0].conditions[1].reason: Invalid value: \"\": status.parents[0].conditions[1].reason in body should be at least 1 chars long, status.parents[0].conditions[1].status: Unsupported value: \"\": supported values: \"True\", \"False\", \"Unknown\", status.parents[0].conditions[1].type: Invalid value: \"\": status.parents[0].conditions[1].type in body should match '^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$']","stacktrace":"github.com/nginxinc/nginx-kubernetes-gateway/internal/status.(*updaterImpl).update\n\t/home/runner/work/nginx-kubernetes-gateway/nginx-kubernetes-gateway/internal/status/updater.go:168\ngithub.com/nginxinc/nginx-kubernetes-gateway/internal/status.(*updaterImpl).Update\n\t/home/runner/work/nginx-kubernetes-gateway/nginx-kubernetes-gateway/internal/status/updater.go:128\ngithub.com/nginxinc/nginx-kubernetes-gateway/internal/events.(*EventHandlerImpl).HandleEventBatch\n\t/home/runner/work/nginx-kubernetes-gateway/nginx-kubernetes-gateway/internal/events/handler.go:90\ngithub.com/nginxinc/nginx-kubernetes-gateway/internal/events.(*EventLoop).Start.func1.1\n\t/home/runner/work/nginx-kubernetes-gateway/nginx-kubernetes-gateway/internal/events/loop.go:67"}

This commit fixes that bug.
  • Loading branch information
pleshakov authored Apr 19, 2023
1 parent 2f4de14 commit 9673c9e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 76 deletions.
8 changes: 5 additions & 3 deletions internal/state/graph/backend_refs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,11 @@ func TestAddBackendGroupsToRouteTest(t *testing.T) {

sectionNameRefs := []ParentRef{
{
Idx: 0,
Gateway: types.NamespacedName{Namespace: "test", Name: "gateway"},
Attached: true,
Idx: 0,
Gateway: types.NamespacedName{Namespace: "test", Name: "gateway"},
Attachment: &ParentRefAttachmentStatus{
Attached: true,
},
},
}

Expand Down
16 changes: 10 additions & 6 deletions internal/state/graph/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,11 @@ func TestBuildGraph(t *testing.T) {
Source: hr1,
ParentRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw1),
Attached: true,
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw1),
Attachment: &ParentRefAttachmentStatus{
Attached: true,
},
},
},
Rules: []Rule{createValidRuleWithBackendGroup(hr1Group)},
Expand All @@ -193,9 +195,11 @@ func TestBuildGraph(t *testing.T) {
Source: hr3,
ParentRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw1),
Attached: true,
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw1),
Attachment: &ParentRefAttachmentStatus{
Attached: true,
},
},
},
Rules: []Rule{createValidRuleWithBackendGroup(hr3Group)},
Expand Down
29 changes: 20 additions & 9 deletions internal/state/graph/httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ type Rule struct {

// ParentRef describes a reference to a parent in an HTTPRoute.
type ParentRef struct {
// FailedAttachmentCondition describes the failure of the attachment when Attached is false.
FailedAttachmentCondition conditions.Condition
// Attachment is the attachment status of the ParentRef. It could be nil. In that case, NGK didn't attempt to
// attach because of problems with the Route.
Attachment *ParentRefAttachmentStatus
// Gateway is the NamespacedName of the referenced Gateway
Gateway types.NamespacedName
// Idx is the index of the corresponding ParentReference in the HTTPRoute.
Idx int
}

// ParentRefAttachmentStatus describes the attachment status of a ParentRef.
type ParentRefAttachmentStatus struct {
// FailedCondition is the condition that describes why the ParentRef is not attached to the Gateway. It is set
// when Attached is false.
FailedCondition conditions.Condition
// Attached indicates if the ParentRef is attached to the Gateway.
Attached bool
}
Expand Down Expand Up @@ -280,7 +288,10 @@ func bindRouteToListeners(r *Route, gw *Gateway) {
}

for i := 0; i < len(r.ParentRefs); i++ {
attachment := &ParentRefAttachmentStatus{}
ref := &r.ParentRefs[i]
ref.Attachment = attachment

routeRef := r.Source.Spec.ParentRefs[ref.Idx]

path := field.NewPath("spec").Child("parentRefs").Index(ref.Idx)
Expand All @@ -289,13 +300,13 @@ func bindRouteToListeners(r *Route, gw *Gateway) {

if routeRef.SectionName == nil || *routeRef.SectionName == "" {
valErr := field.Required(path.Child("sectionName"), "cannot be empty")
ref.FailedAttachmentCondition = conditions.NewRouteUnsupportedValue(valErr.Error())
attachment.FailedCondition = conditions.NewRouteUnsupportedValue(valErr.Error())
continue
}

if routeRef.Port != nil {
valErr := field.Forbidden(path.Child("port"), "cannot be set")
ref.FailedAttachmentCondition = conditions.NewRouteUnsupportedValue(valErr.Error())
attachment.FailedCondition = conditions.NewRouteUnsupportedValue(valErr.Error())
continue
}

Expand All @@ -304,7 +315,7 @@ func bindRouteToListeners(r *Route, gw *Gateway) {
referencesWinningGw := ref.Gateway.Namespace == gw.Source.Namespace && ref.Gateway.Name == gw.Source.Name

if !referencesWinningGw {
ref.FailedAttachmentCondition = conditions.NewTODO("Gateway is ignored")
attachment.FailedCondition = conditions.NewTODO("Gateway is ignored")
continue
}

Expand All @@ -327,23 +338,23 @@ func bindRouteToListeners(r *Route, gw *Gateway) {
if !exists {
// FIXME(pleshakov): Add a proper condition once it is available in the Gateway API.
// https://github.com/nginxinc/nginx-kubernetes-gateway/issues/306
ref.FailedAttachmentCondition = conditions.NewTODO("listener is not found")
attachment.FailedCondition = conditions.NewTODO("listener is not found")
continue
}

if !l.Valid {
ref.FailedAttachmentCondition = conditions.NewRouteInvalidListener()
attachment.FailedCondition = conditions.NewRouteInvalidListener()
continue
}

accepted := findAcceptedHostnames(l.Source.Hostname, r.Source.Spec.Hostnames)

if len(accepted) == 0 {
ref.FailedAttachmentCondition = conditions.NewRouteNoMatchingListenerHostname()
attachment.FailedCondition = conditions.NewRouteNoMatchingListenerHostname()
continue
}

ref.Attached = true
attachment.Attached = true

for _, h := range accepted {
l.AcceptedHostnames[h] = struct{}{}
Expand Down
115 changes: 71 additions & 44 deletions internal/state/graph/httproute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,12 @@ func TestBindRouteToListeners(t *testing.T) {
}
notValidRoute := &Route{
Valid: false,
ParentRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
},
},
}

notValidListener := createModifiedListener(func(l *Listener) {
Expand All @@ -773,12 +779,11 @@ func TestBindRouteToListeners(t *testing.T) {
})

tests := []struct {
route *Route
gateway *Gateway
expectedRouteUnattachedSectionNameRefs map[string]conditions.Condition
expectedGatewayListeners map[string]*Listener
name string
expectedSectionNameRefs []ParentRef
route *Route
gateway *Gateway
expectedGatewayListeners map[string]*Listener
name string
expectedSectionNameRefs []ParentRef
}{
{
route: createNormalRoute(),
Expand All @@ -790,9 +795,11 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: true,
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: true,
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -817,12 +824,14 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: false,
FailedAttachmentCondition: conditions.NewRouteUnsupportedValue(
`spec.parentRefs[0].sectionName: Required value: cannot be empty`,
),
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewRouteUnsupportedValue(
`spec.parentRefs[0].sectionName: Required value: cannot be empty`,
),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -840,12 +849,14 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: false,
FailedAttachmentCondition: conditions.NewRouteUnsupportedValue(
`spec.parentRefs[0].sectionName: Required value: cannot be empty`,
),
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewRouteUnsupportedValue(
`spec.parentRefs[0].sectionName: Required value: cannot be empty`,
),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -863,12 +874,14 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: false,
FailedAttachmentCondition: conditions.NewRouteUnsupportedValue(
`spec.parentRefs[0].port: Forbidden: cannot be set`,
),
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewRouteUnsupportedValue(
`spec.parentRefs[0].port: Forbidden: cannot be set`,
),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -886,10 +899,12 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: false,
FailedAttachmentCondition: conditions.NewTODO("listener is not found"),
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewTODO("listener is not found"),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -907,10 +922,12 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: false,
FailedAttachmentCondition: conditions.NewRouteInvalidListener(),
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewRouteInvalidListener(),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -928,10 +945,12 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attached: false,
FailedAttachmentCondition: conditions.NewRouteNoMatchingListenerHostname(),
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewRouteNoMatchingListenerHostname(),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -949,10 +968,12 @@ func TestBindRouteToListeners(t *testing.T) {
},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: ignoredGwNsName,
Attached: false,
FailedAttachmentCondition: conditions.NewTODO("Gateway is ignored"),
Idx: 0,
Gateway: ignoredGwNsName,
Attachment: &ParentRefAttachmentStatus{
Attached: false,
FailedCondition: conditions.NewTODO("Gateway is ignored"),
},
},
},
expectedGatewayListeners: map[string]*Listener{
Expand All @@ -968,7 +989,13 @@ func TestBindRouteToListeners(t *testing.T) {
"listener-80-1": createListener(),
},
},
expectedRouteUnattachedSectionNameRefs: map[string]conditions.Condition{},
expectedSectionNameRefs: []ParentRef{
{
Idx: 0,
Gateway: client.ObjectKeyFromObject(gw),
Attachment: nil,
},
},
expectedGatewayListeners: map[string]*Listener{
"listener-80-1": createListener(),
},
Expand Down
4 changes: 2 additions & 2 deletions internal/state/statuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func buildStatuses(graph *graph.Graph) Statuses {

for _, ref := range r.ParentRefs {
failedAttachmentCondCount := 0
if !ref.Attached {
if ref.Attachment != nil && !ref.Attachment.Attached {
failedAttachmentCondCount = 1
}
allConds := make([]conditions.Condition, 0, len(r.Conditions)+len(defaultConds)+failedAttachmentCondCount)
Expand All @@ -143,7 +143,7 @@ func buildStatuses(graph *graph.Graph) Statuses {
allConds = append(allConds, defaultConds...)
allConds = append(allConds, r.Conditions...)
if failedAttachmentCondCount == 1 {
allConds = append(allConds, ref.FailedAttachmentCondition)
allConds = append(allConds, ref.Attachment.FailedCondition)
}

routeRef := r.Source.Spec.ParentRefs[ref.Idx]
Expand Down
Loading

0 comments on commit 9673c9e

Please sign in to comment.