diff --git a/docs/gateway-api-compatibility.md b/docs/gateway-api-compatibility.md index a327263fb..d7b510020 100644 --- a/docs/gateway-api-compatibility.md +++ b/docs/gateway-api-compatibility.md @@ -71,7 +71,13 @@ Fields: * `name` - supported. * `supportedKinds` - not supported. * `attachedRoutes` - supported. - * `conditions` - partially supported. + * `conditions` - Supported (Condition/Status/Reason): + * `Accepted/True/Accepted` + * `Accepted/True/ListenersNotValid` + * `Accepted/False/Invalid` + * `Accepted/False/ListenersNotValid` + * `Accepted/False/UnsupportedValue`: Custom reason for when a value of a field in a Gateway is invalid or not supported. + * `Accepted/False/GatewayConflict`: Custom reason for when the Gateway is ignored due to a conflicting Gateway. NKG only supports a single Gateway. ### HTTPRoute diff --git a/internal/state/change_processor_test.go b/internal/state/change_processor_test.go index 3d9217416..e679f3031 100644 --- a/internal/state/change_processor_test.go +++ b/internal/state/change_processor_test.go @@ -293,8 +293,8 @@ var _ = Describe("ChangeProcessor", func() { expectedConf := dataplane.Configuration{} expectedStatuses := state.Statuses{ - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + GatewayStatuses: state.GatewayStatuses{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -310,30 +310,15 @@ var _ = Describe("ChangeProcessor", func() { expectedConf := dataplane.Configuration{} expectedStatuses := state.Statuses{ - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 0, - Conditions: []conditions.Condition{ - conditions.NewListenerResolvedRefs(), - conditions.NewListenerNoConflicts(), - conditions.NewListenerNoValidGatewayClass("GatewayClass doesn't exist"), - }, - }, - "listener-443-1": { - AttachedRoutes: 0, - Conditions: []conditions.Condition{ - conditions.NewListenerResolvedRefs(), - conditions.NewListenerNoConflicts(), - conditions.NewListenerNoValidGatewayClass("GatewayClass doesn't exist"), - }, + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: []conditions.Condition{ + conditions.NewGatewayInvalid("GatewayClass doesn't exist"), }, + ObservedGeneration: gw1.Generation, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1.Generation, ParentStatuses: []state.ParentStatus{ @@ -341,16 +326,16 @@ var _ = Describe("ChangeProcessor", func() { GatewayNsName: client.ObjectKeyFromObject(gw1), SectionName: helpers.GetPointer[v1beta1.SectionName]("listener-80-1"), Conditions: []conditions.Condition{ - conditions.NewRouteInvalidListener(), conditions.NewRouteResolvedRefs(), + conditions.NewRouteInvalidGateway(), }, }, { GatewayNsName: client.ObjectKeyFromObject(gw1), SectionName: helpers.GetPointer[v1beta1.SectionName]("listener-443-1"), Conditions: []conditions.Condition{ - conditions.NewRouteInvalidListener(), conditions.NewRouteResolvedRefs(), + conditions.NewRouteInvalidGateway(), }, }, }, @@ -427,22 +412,23 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw1.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1.Generation, ParentStatuses: []state.ParentStatus{ @@ -540,22 +526,23 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw1.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1Updated.Generation, ParentStatuses: []state.ParentStatus{ @@ -654,22 +641,23 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1Updated.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw1Updated.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1Updated.Generation, ParentStatuses: []state.ParentStatus{ @@ -767,22 +755,23 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gcUpdated.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1Updated.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw1Updated.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1Updated.Generation, ParentStatuses: []state.ParentStatus{ @@ -879,26 +868,27 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gcUpdated.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1Updated.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw1Updated.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, - }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{ {Namespace: "test", Name: "gateway-2"}: { + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, ObservedGeneration: gw2.Generation, }, }, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1Updated.Generation, ParentStatuses: []state.ParentStatus{ @@ -984,26 +974,27 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gcUpdated.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ObservedGeneration: gw1Updated.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-1"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw1Updated.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, - }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{ {Namespace: "test", Name: "gateway-2"}: { ObservedGeneration: gw2.Generation, + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, }, }, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-1"}: { ObservedGeneration: hr1Updated.Generation, ParentStatuses: []state.ParentStatus{ @@ -1113,22 +1104,23 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gcUpdated.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - ObservedGeneration: gw2.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-2"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw2.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "hr-2"}: { ObservedGeneration: hr2.Generation, ParentStatuses: []state.ParentStatus{ @@ -1181,22 +1173,23 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gcUpdated.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - ObservedGeneration: gw2.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 0, - Conditions: conditions.NewDefaultListenerConditions(), - }, - "listener-443-1": { - AttachedRoutes: 0, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-2"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw2.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 0, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-443-1": { + AttachedRoutes: 0, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -1214,30 +1207,15 @@ var _ = Describe("ChangeProcessor", func() { expectedConf := dataplane.Configuration{} expectedStatuses := state.Statuses{ - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - ObservedGeneration: gw2.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 0, - Conditions: []conditions.Condition{ - conditions.NewListenerResolvedRefs(), - conditions.NewListenerNoConflicts(), - conditions.NewListenerNoValidGatewayClass("GatewayClass doesn't exist"), - }, - }, - "listener-443-1": { - AttachedRoutes: 0, - Conditions: []conditions.Condition{ - conditions.NewListenerResolvedRefs(), - conditions.NewListenerNoConflicts(), - conditions.NewListenerNoValidGatewayClass("GatewayClass doesn't exist"), - }, + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway-2"}: { + Conditions: []conditions.Condition{ + conditions.NewGatewayInvalid("GatewayClass doesn't exist"), }, + ObservedGeneration: gw2.Generation, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -1255,8 +1233,8 @@ var _ = Describe("ChangeProcessor", func() { expectedConf := dataplane.Configuration{} expectedStatuses := state.Statuses{ - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + GatewayStatuses: state.GatewayStatuses{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -1274,8 +1252,8 @@ var _ = Describe("ChangeProcessor", func() { expectedConf := dataplane.Configuration{} expectedStatuses := state.Statuses{ - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + GatewayStatuses: state.GatewayStatuses{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -2056,8 +2034,8 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + GatewayStatuses: state.GatewayStatuses{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -2128,18 +2106,19 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: gwNsName, - ObservedGeneration: gw.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + gwNsName: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ hrNsName: { ObservedGeneration: hr.Generation, ParentStatuses: []state.ParentStatus{ @@ -2180,18 +2159,19 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &state.GatewayStatus{ - NsName: gwNsName, - ObservedGeneration: gw.Generation, - ListenerStatuses: map[string]state.ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 0, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: state.GatewayStatuses{ + gwNsName: { + Conditions: conditions.NewDefaultGatewayConditions(), + ObservedGeneration: gw.Generation, + ListenerStatuses: map[string]state.ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 0, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, }, }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) @@ -2215,8 +2195,8 @@ var _ = Describe("ChangeProcessor", func() { ObservedGeneration: gc.Generation, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{}, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{}, + GatewayStatuses: state.GatewayStatuses{}, + HTTPRouteStatuses: state.HTTPRouteStatuses{}, } changed, conf, statuses := processor.Process(context.TODO()) diff --git a/internal/state/conditions/conditions.go b/internal/state/conditions/conditions.go index c610dcb7c..974efcc35 100644 --- a/internal/state/conditions/conditions.go +++ b/internal/state/conditions/conditions.go @@ -8,20 +8,32 @@ import ( ) const ( - // RouteReasonInvalidListener is used with the "Accepted" condition when the Route references an invalid listener. - RouteReasonInvalidListener v1beta1.RouteConditionReason = "InvalidListener" - // ListenerReasonUnsupportedValue is used with the "Accepted" condition when a value of a field in a Listener // is invalid or not supported. ListenerReasonUnsupportedValue v1beta1.ListenerConditionReason = "UnsupportedValue" - // ListenerReasonNoValidGatewayClass is used with the "Accepted" condition when there is no valid GatewayClass - // in the cluster. - ListenerReasonNoValidGatewayClass v1beta1.ListenerConditionReason = "NoValidGatewayClass" - // RouteReasonBackendRefUnsupportedValue is used with the "ResolvedRefs" condition when one of the // Route rules has a backendRef with an unsupported value. RouteReasonBackendRefUnsupportedValue = "UnsupportedValue" + + // RouteReasonInvalidGateway is used with the "Accepted" (false) condition when the Gateway the Route + // references is invalid. + RouteReasonInvalidGateway = "InvalidGateway" + + // RouteReasonInvalidListener is used with the "Accepted" condition when the Route references an invalid listener. + RouteReasonInvalidListener v1beta1.RouteConditionReason = "InvalidListener" + + // GatewayReasonGatewayConflict indicates there are multiple Gateway resources to choose from, + // and we ignored the resource in question and picked another Gateway as the winner. + // This reason is used with GatewayConditionAccepted (false). + GatewayReasonGatewayConflict v1beta1.GatewayConditionReason = "GatewayConflict" + + // GatewayMessageGatewayConflict is message that describes GatewayReasonGatewayConflict. + GatewayMessageGatewayConflict = "The resource is ignored due to a conflicting Gateway resource" + + // GatewayReasonUnsupportedValue is used with GatewayConditionAccepted (false) when a value of a field in a Gateway + // is invalid or not supported. + GatewayReasonUnsupportedValue v1beta1.GatewayConditionReason = "UnsupportedValue" ) // Condition defines a condition to be reported in the status of resources. @@ -64,6 +76,16 @@ func DeduplicateConditions(conds []Condition) []Condition { return result } +// NewTODO returns a Condition that can be used as a placeholder for a condition that is not yet implemented. +func NewTODO(msg string) Condition { + return Condition{ + Type: "TODO", + Status: metav1.ConditionTrue, + Reason: "TODO", + Message: fmt.Sprintf("The condition for this has not been implemented yet: %s", msg), + } +} + // NewDefaultRouteConditions returns the default conditions that must be present in the status of an HTTPRoute. func NewDefaultRouteConditions() []Condition { return []Condition{ @@ -103,16 +125,6 @@ func NewRouteUnsupportedValue(msg string) Condition { } } -// NewTODO returns a Condition that can be used as a placeholder for a condition that is not yet implemented. -func NewTODO(msg string) Condition { - return Condition{ - Type: "TODO", - Status: metav1.ConditionTrue, - Reason: "TODO", - Message: fmt.Sprintf("The condition for this has not been implemented yet: %s", msg), - } -} - // NewRouteInvalidListener returns a Condition that indicates that the HTTPRoute is not accepted because of an // invalid listener. func NewRouteInvalidListener() Condition { @@ -124,6 +136,80 @@ func NewRouteInvalidListener() Condition { } } +// NewRouteResolvedRefs returns a Condition that indicates that all the references on the Route are resolved. +func NewRouteResolvedRefs() Condition { + return Condition{ + Type: string(v1beta1.RouteConditionResolvedRefs), + Status: metav1.ConditionTrue, + Reason: string(v1beta1.RouteReasonResolvedRefs), + Message: "All references are resolved", + } +} + +// NewRouteBackendRefInvalidKind returns a Condition that indicates that the Route has a backendRef with an +// invalid kind. +func NewRouteBackendRefInvalidKind(msg string) Condition { + return Condition{ + Type: string(v1beta1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.RouteReasonInvalidKind), + Message: msg, + } +} + +// NewRouteBackendRefRefNotPermitted returns a Condition that indicates that the Route has a backendRef that +// is not permitted. +func NewRouteBackendRefRefNotPermitted(msg string) Condition { + return Condition{ + Type: string(v1beta1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.RouteReasonRefNotPermitted), + Message: msg, + } +} + +// NewRouteBackendRefRefBackendNotFound returns a Condition that indicates that the Route has a backendRef that +// points to non-existing backend. +func NewRouteBackendRefRefBackendNotFound(msg string) Condition { + return Condition{ + Type: string(v1beta1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.RouteReasonBackendNotFound), + Message: msg, + } +} + +// NewRouteBackendRefUnsupportedValue returns a Condition that indicates that the Route has a backendRef with +// an unsupported value. +func NewRouteBackendRefUnsupportedValue(msg string) Condition { + return Condition{ + Type: string(v1beta1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: RouteReasonBackendRefUnsupportedValue, + Message: msg, + } +} + +// NewRouteInvalidGateway returns a Condition that indicates that the Route is not Accepted because the Gateway it +// references is invalid. +func NewRouteInvalidGateway() Condition { + return Condition{ + Type: string(v1beta1.RouteConditionAccepted), + Status: metav1.ConditionFalse, + Reason: RouteReasonInvalidGateway, + Message: "Gateway is invalid", + } +} + +// NewDefaultListenerConditions returns the default Conditions that must be present in the status of a Listener. +func NewDefaultListenerConditions() []Condition { + return []Condition{ + NewListenerAccepted(), + NewListenerResolvedRefs(), + NewListenerNoConflicts(), + } +} + // NewListenerPortUnavailable returns a Condition that indicates a port is unavailable in a Listener. func NewListenerPortUnavailable(msg string) Condition { return Condition{ @@ -164,15 +250,6 @@ func NewListenerNoConflicts() Condition { } } -// NewDefaultListenerConditions returns the default Conditions that must be present in the status of a Listener. -func NewDefaultListenerConditions() []Condition { - return []Condition{ - NewListenerAccepted(), - NewListenerResolvedRefs(), - NewListenerNoConflicts(), - } -} - // NewListenerUnsupportedValue returns a Condition that indicates that a field of a Listener has an unsupported value. // Unsupported means that the value is not supported by the implementation or invalid. func NewListenerUnsupportedValue(msg string) Condition { @@ -230,89 +307,95 @@ func NewListenerUnsupportedProtocol(msg string) Condition { } } -// NewListenerNoValidGatewayClass returns a Condition that indicates that the Listener is not accepted because -// there is no valid GatewayClass. -func NewListenerNoValidGatewayClass(msg string) Condition { - return Condition{ - Type: string(v1beta1.ListenerConditionAccepted), - Status: metav1.ConditionFalse, - Reason: string(ListenerReasonNoValidGatewayClass), - Message: msg, +// NewDefaultGatewayClassConditions returns the default Conditions that must be present in the status of a GatewayClass. +func NewDefaultGatewayClassConditions() []Condition { + return []Condition{ + { + Type: string(v1beta1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionTrue, + Reason: string(v1beta1.GatewayClassReasonAccepted), + Message: "GatewayClass is accepted", + }, } } -// NewRouteBackendRefInvalidKind returns a Condition that indicates that the Route has a backendRef with an -// invalid kind. -func NewRouteBackendRefInvalidKind(msg string) Condition { +// NewGatewayClassInvalidParameters returns a Condition that indicates that the GatewayClass has invalid parameters. +func NewGatewayClassInvalidParameters(msg string) Condition { return Condition{ - Type: string(v1beta1.RouteConditionResolvedRefs), + Type: string(v1beta1.GatewayClassConditionStatusAccepted), Status: metav1.ConditionFalse, - Reason: string(v1beta1.RouteReasonInvalidKind), + Reason: string(v1beta1.GatewayClassReasonInvalidParameters), Message: msg, } } -// NewRouteBackendRefRefNotPermitted returns a Condition that indicates that the Route has a backendRef that -// is not permitted. -func NewRouteBackendRefRefNotPermitted(msg string) Condition { - return Condition{ - Type: string(v1beta1.RouteConditionResolvedRefs), - Status: metav1.ConditionFalse, - Reason: string(v1beta1.RouteReasonRefNotPermitted), - Message: msg, +// NewDefaultGatewayConditions returns the default Condition that must be present in the status of a Gateway. +func NewDefaultGatewayConditions() []Condition { + return []Condition{ + NewGatewayAccepted(), } } -// NewRouteBackendRefRefBackendNotFound returns a Condition that indicates that the Route has a backendRef that -// points to non-existing backend. -func NewRouteBackendRefRefBackendNotFound(msg string) Condition { +// NewGatewayAccepted returns a Condition that indicates the Gateway is accepted. +func NewGatewayAccepted() Condition { return Condition{ - Type: string(v1beta1.RouteConditionResolvedRefs), - Status: metav1.ConditionFalse, - Reason: string(v1beta1.RouteReasonBackendNotFound), - Message: msg, + Type: string(v1beta1.GatewayConditionAccepted), + Status: metav1.ConditionTrue, + Reason: string(v1beta1.GatewayReasonAccepted), + Message: "Gateway is accepted", } } -// NewRouteBackendRefUnsupportedValue returns a Condition that indicates that the Route has a backendRef with -// an unsupported value. -func NewRouteBackendRefUnsupportedValue(msg string) Condition { +// NewGatewayConflict returns a Condition that indicates the Gateway has a conflict with another Gateway. +func NewGatewayConflict() Condition { return Condition{ - Type: string(v1beta1.RouteConditionResolvedRefs), + Type: string(v1beta1.GatewayConditionAccepted), Status: metav1.ConditionFalse, - Reason: RouteReasonBackendRefUnsupportedValue, - Message: msg, + Reason: string(GatewayReasonGatewayConflict), + Message: GatewayMessageGatewayConflict, } } -// NewRouteResolvedRefs returns a Condition that indicates that all the references on the Route are resolved. -func NewRouteResolvedRefs() Condition { +// NewGatewayAcceptedListenersNotValid returns a Condition that indicates the Gateway is accepted, +// but has at least one listener that is invalid. +func NewGatewayAcceptedListenersNotValid() Condition { return Condition{ - Type: string(v1beta1.RouteConditionResolvedRefs), + Type: string(v1beta1.GatewayConditionAccepted), Status: metav1.ConditionTrue, - Reason: string(v1beta1.RouteReasonResolvedRefs), - Message: "All references are resolved", + Reason: string(v1beta1.GatewayReasonListenersNotValid), + Message: "Gateway has at least one valid listener", } } -// NewGatewayClassInvalidParameters returns a Condition that indicates that the GatewayClass has invalid parameters. -func NewGatewayClassInvalidParameters(msg string) Condition { +// NewGatewayNotAcceptedListenersNotValid returns a Condition that indicates the Gateway is not accepted, +// because all listeners are invalid. +func NewGatewayNotAcceptedListenersNotValid() Condition { return Condition{ - Type: string(v1beta1.GatewayClassConditionStatusAccepted), + Type: string(v1beta1.GatewayConditionAccepted), Status: metav1.ConditionFalse, - Reason: string(v1beta1.GatewayClassReasonInvalidParameters), + Reason: string(v1beta1.GatewayReasonListenersNotValid), + Message: "Gateway has no valid listeners", + } +} + +// NewGatewayInvalid returns a Condition that indicates the Gateway is not accepted because it is +// semantically or syntactically invalid. The provided message contains the details of why the Gateway is invalid. +func NewGatewayInvalid(msg string) Condition { + return Condition{ + Type: string(v1beta1.GatewayConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.GatewayReasonInvalid), Message: msg, } } -// NewDefaultGatewayClassConditions returns the default Conditions that must be present in the status of a GatewayClass. -func NewDefaultGatewayClassConditions() []Condition { - return []Condition{ - { - Type: string(v1beta1.GatewayClassConditionStatusAccepted), - Status: metav1.ConditionTrue, - Reason: string(v1beta1.GatewayClassReasonAccepted), - Message: "GatewayClass is accepted", - }, +// NewGatewayUnsupportedValue returns a Condition that indicates that a field of the Gateway has an unsupported value. +// Unsupported means that the value is not supported by the implementation or invalid. +func NewGatewayUnsupportedValue(msg string) Condition { + return Condition{ + Type: string(v1beta1.GatewayConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(GatewayReasonUnsupportedValue), + Message: msg, } } diff --git a/internal/state/graph/gateway.go b/internal/state/graph/gateway.go index 90a47fb3e..e83cc0c69 100644 --- a/internal/state/graph/gateway.go +++ b/internal/state/graph/gateway.go @@ -4,10 +4,12 @@ import ( "sort" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/gateway-api/apis/v1beta1" nkgsort "github.com/nginxinc/nginx-kubernetes-gateway/internal/sort" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/state/conditions" "github.com/nginxinc/nginx-kubernetes-gateway/internal/state/secrets" ) @@ -17,6 +19,10 @@ type Gateway struct { Source *v1beta1.Gateway // Listeners include the listeners of the Gateway. Listeners map[string]*Listener + // Conditions holds the conditions for the Gateway. + Conditions []conditions.Condition + // Valid indicates whether the Gateway Spec is valid. + Valid bool } // processedGateways holds the resources that belong to NKG. @@ -89,8 +95,38 @@ func buildGateway(gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryM return nil } + conds := validateGateway(gw, gc) + + if len(conds) > 0 { + return &Gateway{ + Source: gw, + Valid: false, + Conditions: conds, + } + } + return &Gateway{ Source: gw, - Listeners: buildListeners(gw, secretMemoryMgr, gc), + Listeners: buildListeners(gw, secretMemoryMgr), + Valid: true, + } +} + +func validateGateway(gw *v1beta1.Gateway, gc *GatewayClass) []conditions.Condition { + var conds []conditions.Condition + + if gc == nil { + conds = append(conds, conditions.NewGatewayInvalid("GatewayClass doesn't exist")) + } else if !gc.Valid { + conds = append(conds, conditions.NewGatewayInvalid("GatewayClass is invalid")) + } + + if len(gw.Spec.Addresses) > 0 { + path := field.NewPath("spec", "addresses") + valErr := field.Forbidden(path, "addresses are not supported") + + conds = append(conds, conditions.NewGatewayUnsupportedValue(valErr.Error())) } + + return conds } diff --git a/internal/state/graph/gateway_listener.go b/internal/state/graph/gateway_listener.go index c359d6c0a..7c97c6ae3 100644 --- a/internal/state/graph/gateway_listener.go +++ b/internal/state/graph/gateway_listener.go @@ -34,11 +34,10 @@ type Listener struct { func buildListeners( gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryManager, - gc *GatewayClass, ) map[string]*Listener { listeners := make(map[string]*Listener) - listenerFactory := newListenerConfiguratorFactory(gw, secretMemoryMgr, gc) + listenerFactory := newListenerConfiguratorFactory(gw, secretMemoryMgr) for _, gl := range gw.Spec.Listeners { configurator := listenerFactory.getConfiguratorForListener(gl) @@ -66,7 +65,6 @@ func (f *listenerConfiguratorFactory) getConfiguratorForListener(l v1beta1.Liste func newListenerConfiguratorFactory( gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryManager, - gc *GatewayClass, ) *listenerConfiguratorFactory { return &listenerConfiguratorFactory{ unsupportedProtocol: &listenerConfigurator{ @@ -84,8 +82,6 @@ func newListenerConfiguratorFactory( http: &listenerConfigurator{ validators: []listenerValidator{ validateListenerHostname, - createAddressesValidator(gw), - createNoValidGatewayClassValidator(gc), validateHTTPListener, }, conflictResolvers: []listenerConflictResolver{ @@ -95,8 +91,6 @@ func newListenerConfiguratorFactory( https: &listenerConfigurator{ validators: []listenerValidator{ validateListenerHostname, - createAddressesValidator(gw), - createNoValidGatewayClassValidator(gc), createHTTPSListenerValidator(gw.Namespace), }, conflictResolvers: []listenerConflictResolver{ @@ -192,31 +186,6 @@ func validateListenerHostname(listener v1beta1.Listener) []conditions.Condition return nil } -func createAddressesValidator(gw *v1beta1.Gateway) listenerValidator { - return func(listener v1beta1.Listener) []conditions.Condition { - if len(gw.Spec.Addresses) > 0 { - path := field.NewPath("spec", "addresses") - valErr := field.Forbidden(path, "addresses are not supported") - return []conditions.Condition{conditions.NewListenerUnsupportedValue(valErr.Error())} - } - return nil - } -} - -func createNoValidGatewayClassValidator(gc *GatewayClass) listenerValidator { - return func(listener v1beta1.Listener) []conditions.Condition { - if gc == nil { - return []conditions.Condition{conditions.NewListenerNoValidGatewayClass("GatewayClass doesn't exist")} - } - - if !gc.Valid { - return []conditions.Condition{conditions.NewListenerNoValidGatewayClass("GatewayClass is invalid")} - } - - return nil - } -} - func validateHTTPListener(listener v1beta1.Listener) []conditions.Condition { if listener.Port != 80 { path := field.NewPath("port") diff --git a/internal/state/graph/gateway_test.go b/internal/state/graph/gateway_test.go index 00e2a314f..20ad44e8d 100644 --- a/internal/state/graph/gateway_test.go +++ b/internal/state/graph/gateway_test.go @@ -304,6 +304,7 @@ func TestBuildGateway(t *testing.T) { AcceptedHostnames: map[string]struct{}{}, }, }, + Valid: true, }, name: "valid http listener", }, @@ -321,6 +322,7 @@ func TestBuildGateway(t *testing.T) { SecretPath: secretPath, }, }, + Valid: true, }, name: "valid https listener", }, @@ -340,6 +342,7 @@ func TestBuildGateway(t *testing.T) { }, }, }, + Valid: true, }, name: "invalid listener protocol", }, @@ -359,6 +362,7 @@ func TestBuildGateway(t *testing.T) { }, }, }, + Valid: true, }, name: "invalid http listener", }, @@ -378,6 +382,7 @@ func TestBuildGateway(t *testing.T) { }, }, }, + Valid: true, }, name: "invalid https listener", }, @@ -402,6 +407,7 @@ func TestBuildGateway(t *testing.T) { }, }, }, + Valid: true, }, name: "invalid hostnames", }, @@ -421,6 +427,7 @@ func TestBuildGateway(t *testing.T) { ), }, }, + Valid: true, }, name: "invalid https listener (secret does not exist)", }, @@ -459,6 +466,7 @@ func TestBuildGateway(t *testing.T) { SecretPath: secretPath, }, }, + Valid: true, }, name: "multiple valid http/https listeners", }, @@ -501,6 +509,7 @@ func TestBuildGateway(t *testing.T) { SecretPath: "/etc/nginx/secrets/test_secret", }, }, + Valid: true, }, name: "collisions", }, @@ -514,26 +523,9 @@ func TestBuildGateway(t *testing.T) { gatewayClass: validGC, expected: &Gateway{ Source: getLastCreatedGetaway(), - Listeners: map[string]*Listener{ - "listener-80-1": { - Source: listener801, - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue( - "spec.addresses: Forbidden: addresses are not supported", - ), - }, - }, - "listener-443-1": { - Source: listener4431, - Valid: false, - SecretPath: "", - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue( - "spec.addresses: Forbidden: addresses are not supported", - ), - }, - }, + Valid: false, + Conditions: []conditions.Condition{ + conditions.NewGatewayUnsupportedValue("spec.addresses: Forbidden: addresses are not supported"), }, }, name: "gateway addresses are not supported", @@ -544,35 +536,25 @@ func TestBuildGateway(t *testing.T) { name: "nil gateway", }, { - gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{listener801}}), + gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{listener801, listener802}}), gatewayClass: invalidGC, expected: &Gateway{ Source: getLastCreatedGetaway(), - Listeners: map[string]*Listener{ - "listener-80-1": { - Source: listener801, - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerNoValidGatewayClass("GatewayClass is invalid"), - }, - }, + Valid: false, + Conditions: []conditions.Condition{ + conditions.NewGatewayInvalid("GatewayClass is invalid"), }, }, name: "invalid gatewayclass", }, { - gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{listener801}}), + gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{listener801, listener802}}), gatewayClass: nil, expected: &Gateway{ Source: getLastCreatedGetaway(), - Listeners: map[string]*Listener{ - "listener-80-1": { - Source: listener801, - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerNoValidGatewayClass("GatewayClass doesn't exist"), - }, - }, + Valid: false, + Conditions: []conditions.Condition{ + conditions.NewGatewayInvalid("GatewayClass doesn't exist"), }, }, name: "nil gatewayclass", diff --git a/internal/state/graph/graph_test.go b/internal/state/graph/graph_test.go index ed51f38c0..7b292a88b 100644 --- a/internal/state/graph/graph_test.go +++ b/internal/state/graph/graph_test.go @@ -234,6 +234,7 @@ func TestBuildGraph(t *testing.T) { SecretPath: secretPath, }, }, + Valid: true, }, IgnoredGateways: map[types.NamespacedName]*v1beta1.Gateway{ {Namespace: "test", Name: "gateway-2"}: gw2, diff --git a/internal/state/graph/httproute.go b/internal/state/graph/httproute.go index dd1088299..02990edb0 100644 --- a/internal/state/graph/httproute.go +++ b/internal/state/graph/httproute.go @@ -281,57 +281,100 @@ func bindRouteToListeners(r *Route, gw *Gateway) { continue } - // Case 3 - winning Gateway + // Case 3: Attachment is not possible because Gateway is invalid - // Find a listener + if !gw.Valid { + attachment.FailedCondition = conditions.NewRouteInvalidGateway() + continue + } - // FIXME(pleshakov) - // For now, let's do simple matching. - // However, we need to also support wildcard matching. + // Case 4 - winning Gateway - bind := func(l *Listener) (valid bool) { - if !l.Valid { - return false - } + // Try to attach Route to all matching listeners + cond, attached := tryToAttachRouteToListeners(routeRef, r, gw.Listeners) + if !attached { + attachment.FailedCondition = cond + continue + } - hostnames := findAcceptedHostnames(l.Source.Hostname, r.Source.Spec.Hostnames) - if len(hostnames) == 0 { - return true // listener is valid, but return without attaching due to no matching hostnames - } + attachment.Attached = true + } +} - attachment.Attached = true - for _, h := range hostnames { - l.AcceptedHostnames[h] = struct{}{} - } - l.Routes[client.ObjectKeyFromObject(r.Source)] = r +// tryToAttachRouteToListeners tries to attach the route to the listeners that match the parentRef and the hostnames. +// If it succeeds in attaching at least one listener it will return true and the condition will be empty. +// If it fails to attach the route, it will return false and the failure condition. +// FIXME(pleshakov) +// For now, let's do simple matching. +// However, we need to also support wildcard matching. +func tryToAttachRouteToListeners( + ref v1beta1.ParentReference, + route *Route, + listeners map[string]*Listener, +) (conditions.Condition, bool) { + validListeners, listenerExists := findValidListeners(getSectionName(ref.SectionName), listeners) - return true + if !listenerExists { + // FIXME(pleshakov): Add a proper condition once it is available in the Gateway API. + // https://github.com/nginxinc/nginx-kubernetes-gateway/issues/306 + return conditions.NewTODO("listener is not found"), false + } + + if len(validListeners) == 0 { + return conditions.NewRouteInvalidListener(), false + } + + bind := func(l *Listener) (attached bool) { + hostnames := findAcceptedHostnames(l.Source.Hostname, route.Source.Spec.Hostnames) + if len(hostnames) == 0 { + return false } - var validListener bool - if getSectionName(routeRef.SectionName) == "" { - for _, l := range gw.Listeners { - validListener = bind(l) || validListener - } - } else { - l, exists := gw.Listeners[string(*routeRef.SectionName)] - 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 - attachment.FailedCondition = conditions.NewTODO("listener is not found") - continue - } - - validListener = bind(l) + for _, h := range hostnames { + l.AcceptedHostnames[h] = struct{}{} } - if !validListener { - attachment.FailedCondition = conditions.NewRouteInvalidListener() - continue + l.Routes[client.ObjectKeyFromObject(route.Source)] = route + + return true + } + + attached := false + for _, l := range validListeners { + attached = attached || bind(l) + } + + if !attached { + return conditions.NewRouteNoMatchingListenerHostname(), false + } + + return conditions.Condition{}, true +} + +// findValidListeners returns a list of valid listeners and whether the listener exists for a non-empty sectionName. +func findValidListeners(sectionName string, listeners map[string]*Listener) ([]*Listener, bool) { + if sectionName != "" { + l, exists := listeners[sectionName] + if !exists { + return nil, false } - if !attachment.Attached { - attachment.FailedCondition = conditions.NewRouteNoMatchingListenerHostname() + + if l.Valid { + return []*Listener{l}, true } + + return nil, true } + + validListeners := make([]*Listener, 0, len(listeners)) + for _, l := range listeners { + if !l.Valid { + continue + } + + validListeners = append(validListeners, l) + } + + return validListeners, true } func findAcceptedHostnames(listenerHostname *v1beta1.Hostname, routeHostnames []v1beta1.Hostname) []string { diff --git a/internal/state/graph/httproute_test.go b/internal/state/graph/httproute_test.go index 48300041e..80f96f4ce 100644 --- a/internal/state/graph/httproute_test.go +++ b/internal/state/graph/httproute_test.go @@ -688,6 +688,7 @@ func TestBindRouteToListeners(t *testing.T) { route: createNormalRoute(), gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -717,6 +718,7 @@ func TestBindRouteToListeners(t *testing.T) { route: routeWithMissingSectionName, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -746,6 +748,7 @@ func TestBindRouteToListeners(t *testing.T) { route: routeWithEmptySectionName, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -775,6 +778,7 @@ func TestBindRouteToListeners(t *testing.T) { route: routeWithEmptySectionName, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": notValidListener, }, @@ -798,6 +802,7 @@ func TestBindRouteToListeners(t *testing.T) { route: routeWithPort, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -823,6 +828,7 @@ func TestBindRouteToListeners(t *testing.T) { route: routeWithNonExistingListener, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -846,6 +852,7 @@ func TestBindRouteToListeners(t *testing.T) { route: createNormalRoute(), gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": notValidListener, }, @@ -869,6 +876,7 @@ func TestBindRouteToListeners(t *testing.T) { route: createNormalRoute(), gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": nonMatchingHostnameListener, }, @@ -892,6 +900,7 @@ func TestBindRouteToListeners(t *testing.T) { route: routeWithIgnoredGateway, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -915,6 +924,7 @@ func TestBindRouteToListeners(t *testing.T) { route: notValidRoute, gateway: &Gateway{ Source: gw, + Valid: true, Listeners: map[string]*Listener{ "listener-80-1": createListener(), }, @@ -931,6 +941,30 @@ func TestBindRouteToListeners(t *testing.T) { }, name: "route isn't valid", }, + { + route: createNormalRoute(), + gateway: &Gateway{ + Source: gw, + Valid: false, + Listeners: map[string]*Listener{ + "listener-80-1": createListener(), + }, + }, + expectedSectionNameRefs: []ParentRef{ + { + Idx: 0, + Gateway: client.ObjectKeyFromObject(gw), + Attachment: &ParentRefAttachmentStatus{ + Attached: false, + FailedCondition: conditions.NewRouteInvalidGateway(), + }, + }, + }, + expectedGatewayListeners: map[string]*Listener{ + "listener-80-1": createListener(), + }, + name: "invalid gateway", + }, } for _, test := range tests { diff --git a/internal/state/statuses.go b/internal/state/statuses.go index 50c2a7be1..e1f9b78d8 100644 --- a/internal/state/statuses.go +++ b/internal/state/statuses.go @@ -15,32 +15,26 @@ type ListenerStatuses map[string]ListenerStatus // HTTPRouteStatuses holds the statuses of HTTPRoutes where the key is the namespaced name of an HTTPRoute. type HTTPRouteStatuses map[types.NamespacedName]HTTPRouteStatus +// GatewayStatuses holds the statuses of Gateways where the key is the namespaced name of a Gateway. +type GatewayStatuses map[types.NamespacedName]GatewayStatus + // Statuses holds the status-related information about Gateway API resources. type Statuses struct { - GatewayClassStatus *GatewayClassStatus - GatewayStatus *GatewayStatus - IgnoredGatewayStatuses IgnoredGatewayStatuses - HTTPRouteStatuses HTTPRouteStatuses + GatewayClassStatus *GatewayClassStatus + GatewayStatuses GatewayStatuses + HTTPRouteStatuses HTTPRouteStatuses } // GatewayStatus holds the status of the winning Gateway resource. type GatewayStatus struct { // ListenerStatuses holds the statuses of listeners defined on the Gateway. ListenerStatuses ListenerStatuses - // NsName is the namespaced name of the winning Gateway resource. - NsName types.NamespacedName + // Conditions is the list of conditions for this Gateway. + Conditions []conditions.Condition // ObservedGeneration is the generation of the resource that was processed. ObservedGeneration int64 } -// IgnoredGatewayStatuses holds the statuses of the ignored Gateway resources. -type IgnoredGatewayStatuses map[types.NamespacedName]IgnoredGatewayStatus - -// IgnoredGatewayStatus holds the status of an ignored Gateway resource. -type IgnoredGatewayStatus struct { - ObservedGeneration int64 -} - // ListenerStatus holds the status-related information about a listener in the Gateway resource. type ListenerStatus struct { // Conditions is the list of conditions for this listener. @@ -76,8 +70,7 @@ type GatewayClassStatus struct { // buildStatuses builds statuses from a Graph. func buildStatuses(graph *graph.Graph) Statuses { statuses := Statuses{ - HTTPRouteStatuses: make(map[types.NamespacedName]HTTPRouteStatus), - IgnoredGatewayStatuses: make(map[types.NamespacedName]IgnoredGatewayStatus), + HTTPRouteStatuses: make(HTTPRouteStatuses), } if graph.GatewayClass != nil { @@ -96,35 +89,7 @@ func buildStatuses(graph *graph.Graph) Statuses { } } - if graph.Gateway != nil { - listenerStatuses := make(map[string]ListenerStatus) - - defaultConds := conditions.NewDefaultListenerConditions() - - for name, l := range graph.Gateway.Listeners { - conds := make([]conditions.Condition, 0, len(l.Conditions)+len(defaultConds)) - - // We add default conds first, so that any additional conditions will override them, which is - // ensured by DeduplicateConditions. - conds = append(conds, defaultConds...) - conds = append(conds, l.Conditions...) - - listenerStatuses[name] = ListenerStatus{ - AttachedRoutes: int32(len(l.Routes)), - Conditions: conditions.DeduplicateConditions(conds), - } - } - - statuses.GatewayStatus = &GatewayStatus{ - NsName: client.ObjectKeyFromObject(graph.Gateway.Source), - ListenerStatuses: listenerStatuses, - ObservedGeneration: graph.Gateway.Source.Generation, - } - } - - for nsname, gw := range graph.IgnoredGateways { - statuses.IgnoredGatewayStatuses[nsname] = IgnoredGatewayStatus{ObservedGeneration: gw.Generation} - } + statuses.GatewayStatuses = buildGatewayStatuses(graph.Gateway, graph.IgnoredGateways) for nsname, r := range graph.Routes { parentStatuses := make([]ParentStatus, 0, len(r.ParentRefs)) @@ -163,3 +128,64 @@ func buildStatuses(graph *graph.Graph) Statuses { return statuses } + +func buildGatewayStatuses( + gateway *graph.Gateway, + ignoredGateways map[types.NamespacedName]*v1beta1.Gateway, +) GatewayStatuses { + statuses := make(GatewayStatuses) + + if gateway != nil { + statuses[client.ObjectKeyFromObject(gateway.Source)] = buildGatewayStatus(gateway) + } + + for nsname, gw := range ignoredGateways { + statuses[nsname] = GatewayStatus{ + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, + ObservedGeneration: gw.Generation, + } + } + + return statuses +} + +func buildGatewayStatus(gateway *graph.Gateway) GatewayStatus { + if !gateway.Valid { + return GatewayStatus{ + Conditions: conditions.DeduplicateConditions(gateway.Conditions), + ObservedGeneration: gateway.Source.Generation, + } + } + + listenerStatuses := make(map[string]ListenerStatus) + + validListenerCount := 0 + for name, l := range gateway.Listeners { + var conds []conditions.Condition + + if l.Valid { + conds = conditions.NewDefaultListenerConditions() + validListenerCount++ + } else { + conds = l.Conditions + } + + listenerStatuses[name] = ListenerStatus{ + AttachedRoutes: int32(len(l.Routes)), + Conditions: conditions.DeduplicateConditions(conds), + } + } + + gwConds := conditions.NewDefaultGatewayConditions() + if validListenerCount == 0 { + gwConds = append(gwConds, conditions.NewGatewayNotAcceptedListenersNotValid()) + } else if validListenerCount < len(gateway.Listeners) { + gwConds = append(gwConds, conditions.NewGatewayAcceptedListenersNotValid()) + } + + return GatewayStatus{ + Conditions: conditions.DeduplicateConditions(gwConds), + ListenerStatuses: listenerStatuses, + ObservedGeneration: gateway.Source.Generation, + } +} diff --git a/internal/state/statuses_test.go b/internal/state/statuses_test.go index ad8e52fb3..aff1dd70e 100644 --- a/internal/state/statuses_test.go +++ b/internal/state/statuses_test.go @@ -14,17 +14,8 @@ import ( "github.com/nginxinc/nginx-kubernetes-gateway/internal/state/graph" ) -func TestBuildStatuses(t *testing.T) { - invalidRouteCondition := conditions.Condition{ - Type: "TestInvalidRoute", - Status: metav1.ConditionTrue, - } - invalidAttachmentCondition := conditions.Condition{ - Type: "TestInvalidAttachment", - Status: metav1.ConditionTrue, - } - - gw := &v1beta1.Gateway{ +var ( + gw = &v1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "gateway", @@ -32,13 +23,24 @@ func TestBuildStatuses(t *testing.T) { }, } - ignoredGw := &v1beta1.Gateway{ + ignoredGw = &v1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "ignored-gateway", Generation: 1, }, } +) + +func TestBuildStatuses(t *testing.T) { + invalidRouteCondition := conditions.Condition{ + Type: "TestInvalidRoute", + Status: metav1.ConditionTrue, + } + invalidAttachmentCondition := conditions.Condition{ + Type: "TestInvalidAttachment", + Status: metav1.ConditionTrue, + } routes := map[types.NamespacedName]*graph.Route{ {Namespace: "test", Name: "hr-valid"}: { @@ -122,6 +124,7 @@ func TestBuildStatuses(t *testing.T) { }, }, }, + Valid: true, }, IgnoredGateways: map[types.NamespacedName]*v1beta1.Gateway{ client.ObjectKeyFromObject(ignoredGw): ignoredGw, @@ -134,20 +137,23 @@ func TestBuildStatuses(t *testing.T) { ObservedGeneration: 1, Conditions: conditions.NewDefaultGatewayClassConditions(), }, - GatewayStatus: &GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway"}, - ListenerStatuses: map[string]ListenerStatus{ - "listener-80-1": { - AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + GatewayStatuses: GatewayStatuses{ + {Namespace: "test", Name: "gateway"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ListenerStatuses: map[string]ListenerStatus{ + "listener-80-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, }, + ObservedGeneration: 2, + }, + {Namespace: "test", Name: "ignored-gateway"}: { + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, + ObservedGeneration: 1, }, - ObservedGeneration: 2, - }, - IgnoredGatewayStatuses: map[types.NamespacedName]IgnoredGatewayStatus{ - {Namespace: "test", Name: "ignored-gateway"}: {ObservedGeneration: 1}, }, - HTTPRouteStatuses: map[types.NamespacedName]HTTPRouteStatus{ + HTTPRouteStatuses: HTTPRouteStatuses{ {Namespace: "test", Name: "hr-valid"}: { ObservedGeneration: 3, ParentStatuses: []ParentStatus{ @@ -187,3 +193,179 @@ func TestBuildStatuses(t *testing.T) { result := buildStatuses(graph) g.Expect(helpers.Diff(expected, result)).To(BeEmpty()) } + +func TestBuildGatewayStatuses(t *testing.T) { + tests := []struct { + gateway *graph.Gateway + ignoredGateways map[types.NamespacedName]*v1beta1.Gateway + expected GatewayStatuses + name string + }{ + { + name: "nil gateway and no ignored gateways", + expected: GatewayStatuses{}, + }, + { + name: "nil gateway and ignored gateways", + ignoredGateways: map[types.NamespacedName]*v1beta1.Gateway{ + {Namespace: "test", Name: "ignored-1"}: { + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + }, + {Namespace: "test", Name: "ignored-2"}: { + ObjectMeta: metav1.ObjectMeta{ + Generation: 2, + }, + }, + }, + expected: GatewayStatuses{ + {Namespace: "test", Name: "ignored-1"}: { + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, + ObservedGeneration: 1, + }, + {Namespace: "test", Name: "ignored-2"}: { + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, + ObservedGeneration: 2, + }, + }, + }, + { + name: "valid gateway; all valid listeners", + gateway: &graph.Gateway{ + Source: gw, + Listeners: map[string]*graph.Listener{ + "listener-valid-1": { + Valid: true, + Routes: map[types.NamespacedName]*graph.Route{ + {Namespace: "test", Name: "hr-1"}: {}, + }, + }, + "listener-valid-2": { + Valid: true, + Routes: map[types.NamespacedName]*graph.Route{ + {Namespace: "test", Name: "hr-1"}: {}, + }, + }, + }, + Valid: true, + }, + expected: GatewayStatuses{ + {Namespace: "test", Name: "gateway"}: { + Conditions: conditions.NewDefaultGatewayConditions(), + ListenerStatuses: map[string]ListenerStatus{ + "listener-valid-1": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-valid-2": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + }, + ObservedGeneration: 2, + }, + }, + }, + { + name: "valid gateway; some valid listeners", + gateway: &graph.Gateway{ + Source: gw, + Listeners: map[string]*graph.Listener{ + "listener-valid": { + Valid: true, + Routes: map[types.NamespacedName]*graph.Route{ + {Namespace: "test", Name: "hr-1"}: {}, + }, + }, + "listener-invalid": { + Valid: false, + Conditions: []conditions.Condition{ + conditions.NewListenerUnsupportedValue("unsupported value"), + }, + }, + }, + Valid: true, + }, + expected: GatewayStatuses{ + {Namespace: "test", Name: "gateway"}: { + Conditions: []conditions.Condition{conditions.NewGatewayAcceptedListenersNotValid()}, + ListenerStatuses: map[string]ListenerStatus{ + "listener-valid": { + AttachedRoutes: 1, + Conditions: conditions.NewDefaultListenerConditions(), + }, + "listener-invalid": { + Conditions: []conditions.Condition{ + conditions.NewListenerUnsupportedValue("unsupported value"), + }, + }, + }, + ObservedGeneration: 2, + }, + }, + }, + { + name: "valid gateway; no valid listeners", + gateway: &graph.Gateway{ + Source: gw, + Listeners: map[string]*graph.Listener{ + "listener-invalid-1": { + Valid: false, + Conditions: []conditions.Condition{ + conditions.NewListenerPortUnavailable("port unavailable"), + }, + }, + "listener-invalid-2": { + Valid: false, + Conditions: []conditions.Condition{ + conditions.NewListenerUnsupportedValue("unsupported value"), + }, + }, + }, + Valid: true, + }, + expected: GatewayStatuses{ + {Namespace: "test", Name: "gateway"}: { + Conditions: []conditions.Condition{conditions.NewGatewayNotAcceptedListenersNotValid()}, + ListenerStatuses: map[string]ListenerStatus{ + "listener-invalid-1": { + Conditions: []conditions.Condition{ + conditions.NewListenerPortUnavailable("port unavailable"), + }, + }, + "listener-invalid-2": { + Conditions: []conditions.Condition{ + conditions.NewListenerUnsupportedValue("unsupported value"), + }, + }, + }, + ObservedGeneration: 2, + }, + }, + }, + { + name: "invalid gateway", + gateway: &graph.Gateway{ + Source: gw, + Valid: false, + Conditions: []conditions.Condition{conditions.NewGatewayInvalid("no gateway class")}, + }, + expected: GatewayStatuses{ + {Namespace: "test", Name: "gateway"}: { + Conditions: []conditions.Condition{conditions.NewGatewayInvalid("no gateway class")}, + ObservedGeneration: 2, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + result := buildGatewayStatuses(test.gateway, test.ignoredGateways) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) + } +} diff --git a/internal/status/conditions_test.go b/internal/status/conditions_test.go index c38216b33..8b52896ff 100644 --- a/internal/status/conditions_test.go +++ b/internal/status/conditions_test.go @@ -11,16 +11,16 @@ import ( "github.com/nginxinc/nginx-kubernetes-gateway/internal/state/conditions" ) -func CreateTestConditions() []conditions.Condition { +func CreateTestConditions(condType string) []conditions.Condition { return []conditions.Condition{ { - Type: "Test", + Type: condType, Status: metav1.ConditionTrue, Reason: "TestReason1", Message: "Test message1", }, { - Type: "Test", + Type: condType, Status: metav1.ConditionFalse, Reason: "TestReason2", Message: "Test message2", @@ -28,10 +28,14 @@ func CreateTestConditions() []conditions.Condition { } } -func CreateExpectedAPIConditions(observedGeneration int64, transitionTime metav1.Time) []metav1.Condition { +func CreateExpectedAPIConditions( + condType string, + observedGeneration int64, + transitionTime metav1.Time, +) []metav1.Condition { return []metav1.Condition{ { - Type: "Test", + Type: condType, Status: metav1.ConditionTrue, ObservedGeneration: observedGeneration, LastTransitionTime: transitionTime, @@ -39,7 +43,7 @@ func CreateExpectedAPIConditions(observedGeneration int64, transitionTime metav1 Message: "Test message1", }, { - Type: "Test", + Type: condType, Status: metav1.ConditionFalse, ObservedGeneration: observedGeneration, LastTransitionTime: transitionTime, @@ -55,8 +59,8 @@ func TestConvertRouteConditions(t *testing.T) { var generation int64 = 1 transitionTime := metav1.NewTime(time.Now()) - expected := CreateExpectedAPIConditions(generation, transitionTime) + expected := CreateExpectedAPIConditions("Test", generation, transitionTime) - result := convertConditions(CreateTestConditions(), generation, transitionTime) + result := convertConditions(CreateTestConditions("Test"), generation, transitionTime) g.Expect(helpers.Diff(expected, result)).To(BeEmpty()) } diff --git a/internal/status/gateway.go b/internal/status/gateway.go index bd04e623b..349785de0 100644 --- a/internal/status/gateway.go +++ b/internal/status/gateway.go @@ -9,16 +9,6 @@ import ( "github.com/nginxinc/nginx-kubernetes-gateway/internal/state" ) -const ( - // GetawayReasonGatewayConflict indicates there are multiple Gateway resources for NGINX Gateway to choose from, - // and NGINX Gateway ignored the resource in question and picked another Gateway as the winner. - // NGINX Gateway will use this reason with GatewayConditionReady (false). - GetawayReasonGatewayConflict v1beta1.GatewayConditionReason = "GatewayConflict" - - // GatewayMessageGatewayConflict is message that describes GetawayReasonGatewayConflict. - GatewayMessageGatewayConflict = "The resource is ignored due to a conflicting Gateway resource" -) - // prepareGatewayStatus prepares the status for a Gateway resource. // FIXME(pleshakov): Be compliant with in the Gateway API. // Currently, we only support simple valid/invalid status per each listener. @@ -61,25 +51,6 @@ func prepareGatewayStatus( return v1beta1.GatewayStatus{ Listeners: listenerStatuses, Addresses: []v1beta1.GatewayAddress{gwPodIP}, - Conditions: nil, // FIXME(pleshakov) Create conditions for the Gateway resource. - } -} - -// prepareIgnoredGatewayStatus prepares the status for an ignored Gateway resource. -// TODO: is it reasonable to not set the listener statuses? -// FIXME(pleshakov): Simplify the code, so that we don't need a separate prepareIgnoredGatewayStatus -// and state.IgnoredGatewayStatus. -func prepareIgnoredGatewayStatus(status state.IgnoredGatewayStatus, transitionTime metav1.Time) v1beta1.GatewayStatus { - return v1beta1.GatewayStatus{ - Conditions: []metav1.Condition{ - { - Type: string(v1beta1.GatewayConditionReady), - Status: metav1.ConditionFalse, - ObservedGeneration: status.ObservedGeneration, - LastTransitionTime: transitionTime, - Reason: string(GetawayReasonGatewayConflict), - Message: GatewayMessageGatewayConflict, - }, - }, + Conditions: convertConditions(gatewayStatus.Conditions, gatewayStatus.ObservedGeneration, transitionTime), } } diff --git a/internal/status/gateway_test.go b/internal/status/gateway_test.go index 42cc85337..5be4f53f5 100644 --- a/internal/status/gateway_test.go +++ b/internal/status/gateway_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -21,10 +20,11 @@ func TestPrepareGatewayStatus(t *testing.T) { } status := state.GatewayStatus{ + Conditions: CreateTestConditions("GatewayTest"), ListenerStatuses: state.ListenerStatuses{ "listener": { AttachedRoutes: 3, - Conditions: CreateTestConditions(), + Conditions: CreateTestConditions("ListenerTest"), }, }, ObservedGeneration: 1, @@ -33,6 +33,7 @@ func TestPrepareGatewayStatus(t *testing.T) { transitionTime := metav1.NewTime(time.Now()) expected := v1beta1.GatewayStatus{ + Conditions: CreateExpectedAPIConditions("GatewayTest", 1, transitionTime), Listeners: []v1beta1.ListenerStatus{ { Name: "listener", @@ -42,7 +43,7 @@ func TestPrepareGatewayStatus(t *testing.T) { }, }, AttachedRoutes: 3, - Conditions: CreateExpectedAPIConditions(1, transitionTime), + Conditions: CreateExpectedAPIConditions("ListenerTest", 1, transitionTime), }, }, Addresses: []v1beta1.GatewayAddress{podIP}, @@ -53,29 +54,3 @@ func TestPrepareGatewayStatus(t *testing.T) { result := prepareGatewayStatus(status, "1.2.3.4", transitionTime) g.Expect(helpers.Diff(expected, result)).To(BeEmpty()) } - -func TestPrepareIgnoredGatewayStatus(t *testing.T) { - status := state.IgnoredGatewayStatus{ - ObservedGeneration: 1, - } - - transitionTime := metav1.NewTime(time.Now()) - - expected := v1beta1.GatewayStatus{ - Conditions: []metav1.Condition{ - { - Type: string(v1beta1.GatewayConditionReady), - Status: metav1.ConditionFalse, - ObservedGeneration: status.ObservedGeneration, - LastTransitionTime: transitionTime, - Reason: string(GetawayReasonGatewayConflict), - Message: GatewayMessageGatewayConflict, - }, - }, - } - - result := prepareIgnoredGatewayStatus(status, transitionTime) - if diff := cmp.Diff(expected, result); diff != "" { - t.Errorf("prepareIgnoredGatewayStatus() mismatch (-want +got):\n%s", diff) - } -} diff --git a/internal/status/gatewayclass_test.go b/internal/status/gatewayclass_test.go index 5987727e2..d208f2235 100644 --- a/internal/status/gatewayclass_test.go +++ b/internal/status/gatewayclass_test.go @@ -17,10 +17,10 @@ func TestPrepareGatewayClassStatus(t *testing.T) { status := state.GatewayClassStatus{ ObservedGeneration: 1, - Conditions: CreateTestConditions(), + Conditions: CreateTestConditions("Test"), } expected := v1beta1.GatewayClassStatus{ - Conditions: CreateExpectedAPIConditions(1, transitionTime), + Conditions: CreateExpectedAPIConditions("Test", 1, transitionTime), } g := NewGomegaWithT(t) diff --git a/internal/status/httproute_test.go b/internal/status/httproute_test.go index 3804eddd6..a56451c87 100644 --- a/internal/status/httproute_test.go +++ b/internal/status/httproute_test.go @@ -23,12 +23,12 @@ func TestPrepareHTTPRouteStatus(t *testing.T) { { GatewayNsName: gwNsName1, SectionName: helpers.GetPointer[v1beta1.SectionName]("http"), - Conditions: CreateTestConditions(), + Conditions: CreateTestConditions("Test"), }, { GatewayNsName: gwNsName2, SectionName: nil, - Conditions: CreateTestConditions(), + Conditions: CreateTestConditions("Test"), }, }, } @@ -46,7 +46,7 @@ func TestPrepareHTTPRouteStatus(t *testing.T) { SectionName: helpers.GetPointer[v1beta1.SectionName]("http"), }, ControllerName: v1beta1.GatewayController(gatewayCtlrName), - Conditions: CreateExpectedAPIConditions(1, transitionTime), + Conditions: CreateExpectedAPIConditions("Test", 1, transitionTime), }, { ParentRef: v1beta1.ParentReference{ @@ -55,7 +55,7 @@ func TestPrepareHTTPRouteStatus(t *testing.T) { SectionName: nil, }, ControllerName: v1beta1.GatewayController(gatewayCtlrName), - Conditions: CreateExpectedAPIConditions(1, transitionTime), + Conditions: CreateExpectedAPIConditions("Test", 1, transitionTime), }, }, }, diff --git a/internal/status/updater.go b/internal/status/updater.go index 879a1ccd4..0859bb4f3 100644 --- a/internal/status/updater.go +++ b/internal/status/updater.go @@ -100,23 +100,10 @@ func (upd *updaterImpl) Update(ctx context.Context, statuses state.Statuses) { ) } - if statuses.GatewayStatus != nil { - upd.update(ctx, statuses.GatewayStatus.NsName, &v1beta1.Gateway{}, func(object client.Object) { - gw := object.(*v1beta1.Gateway) - gw.Status = prepareGatewayStatus(*statuses.GatewayStatus, upd.cfg.PodIP, upd.cfg.Clock.Now()) - }) - } - - for nsname, gs := range statuses.IgnoredGatewayStatuses { - select { - case <-ctx.Done(): - return - default: - } - + for nsname, gs := range statuses.GatewayStatuses { upd.update(ctx, nsname, &v1beta1.Gateway{}, func(object client.Object) { gw := object.(*v1beta1.Gateway) - gw.Status = prepareIgnoredGatewayStatus(gs, upd.cfg.Clock.Now()) + gw.Status = prepareGatewayStatus(gs, upd.cfg.PodIP, upd.cfg.Clock.Now()) }) } diff --git a/internal/status/updater_test.go b/internal/status/updater_test.go index ebd47956b..aac7df883 100644 --- a/internal/status/updater_test.go +++ b/internal/status/updater_test.go @@ -17,6 +17,7 @@ import ( "github.com/nginxinc/nginx-kubernetes-gateway/internal/helpers" "github.com/nginxinc/nginx-kubernetes-gateway/internal/state" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/state/conditions" "github.com/nginxinc/nginx-kubernetes-gateway/internal/status" "github.com/nginxinc/nginx-kubernetes-gateway/internal/status/statusfakes" ) @@ -69,36 +70,42 @@ var _ = Describe("Updater", func() { gc *v1beta1.GatewayClass gw, ignoredGw *v1beta1.Gateway hr *v1beta1.HTTPRoute + ipAddrType = v1beta1.IPAddressType + addr = v1beta1.GatewayAddress{ + Type: &ipAddrType, + Value: "1.2.3.4", + } createStatuses = func(gens generations) state.Statuses { return state.Statuses{ GatewayClassStatus: &state.GatewayClassStatus{ ObservedGeneration: gens.gatewayClass, - Conditions: status.CreateTestConditions(), + Conditions: status.CreateTestConditions("Test"), }, - GatewayStatus: &state.GatewayStatus{ - NsName: types.NamespacedName{Namespace: "test", Name: "gateway"}, - ListenerStatuses: map[string]state.ListenerStatus{ - "http": { - AttachedRoutes: 1, - Conditions: status.CreateTestConditions(), + GatewayStatuses: state.GatewayStatuses{ + {Namespace: "test", Name: "gateway"}: { + Conditions: status.CreateTestConditions("Test"), + ListenerStatuses: map[string]state.ListenerStatus{ + "http": { + AttachedRoutes: 1, + Conditions: status.CreateTestConditions("Test"), + }, }, + ObservedGeneration: gens.gateways, }, - ObservedGeneration: gens.gateways, - }, - IgnoredGatewayStatuses: map[types.NamespacedName]state.IgnoredGatewayStatus{ {Namespace: "test", Name: "ignored-gateway"}: { - ObservedGeneration: gens.gateways, + Conditions: []conditions.Condition{conditions.NewGatewayConflict()}, + ObservedGeneration: 1, }, }, - HTTPRouteStatuses: map[types.NamespacedName]state.HTTPRouteStatus{ + HTTPRouteStatuses: state.HTTPRouteStatuses{ {Namespace: "test", Name: "route1"}: { ObservedGeneration: 5, ParentStatuses: []state.ParentStatus{ { GatewayNsName: types.NamespacedName{Namespace: "test", Name: "gateway"}, SectionName: helpers.GetPointer[v1beta1.SectionName]("http"), - Conditions: status.CreateTestConditions(), + Conditions: status.CreateTestConditions("Test"), }, }, }, @@ -116,18 +123,12 @@ var _ = Describe("Updater", func() { APIVersion: "gateway.networking.k8s.io/v1beta1", }, Status: v1beta1.GatewayClassStatus{ - Conditions: status.CreateExpectedAPIConditions(generation, fakeClockTime), + Conditions: status.CreateExpectedAPIConditions("Test", generation, fakeClockTime), }, } } createExpectedGwWithGeneration = func(generation int64) *v1beta1.Gateway { - ipAddrType := v1beta1.IPAddressType - addr := v1beta1.GatewayAddress{ - Type: &ipAddrType, - Value: "1.2.3.4", - } - return &v1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", @@ -138,6 +139,7 @@ var _ = Describe("Updater", func() { APIVersion: "gateway.networking.k8s.io/v1beta1", }, Status: v1beta1.GatewayStatus{ + Conditions: status.CreateExpectedAPIConditions("Test", generation, fakeClockTime), Listeners: []gatewayv1beta1.ListenerStatus{ { Name: "http", @@ -147,7 +149,7 @@ var _ = Describe("Updater", func() { }, }, AttachedRoutes: 1, - Conditions: status.CreateExpectedAPIConditions(generation, fakeClockTime), + Conditions: status.CreateExpectedAPIConditions("Test", generation, fakeClockTime), }, }, Addresses: []v1beta1.GatewayAddress{addr}, @@ -168,14 +170,15 @@ var _ = Describe("Updater", func() { Status: v1beta1.GatewayStatus{ Conditions: []metav1.Condition{ { - Type: string(v1beta1.GatewayConditionReady), + Type: string(v1beta1.GatewayConditionAccepted), Status: metav1.ConditionFalse, ObservedGeneration: 1, LastTransitionTime: fakeClockTime, - Reason: string(status.GetawayReasonGatewayConflict), - Message: status.GatewayMessageGatewayConflict, + Reason: string(conditions.GatewayReasonGatewayConflict), + Message: conditions.GatewayMessageGatewayConflict, }, }, + Addresses: []v1beta1.GatewayAddress{addr}, }, } } @@ -200,7 +203,7 @@ var _ = Describe("Updater", func() { Name: "gateway", SectionName: (*v1beta1.SectionName)(helpers.GetStringPointer("http")), }, - Conditions: status.CreateExpectedAPIConditions(5, fakeClockTime), + Conditions: status.CreateExpectedAPIConditions("Test", 5, fakeClockTime), }, }, },