diff --git a/docs/gateway-api-compatibility.md b/docs/gateway-api-compatibility.md index 4b9f4b4415..72a580910f 100644 --- a/docs/gateway-api-compatibility.md +++ b/docs/gateway-api-compatibility.md @@ -89,6 +89,8 @@ Fields: * `Accepted/False/ProtocolConflict` * `Accepted/False/UnsupportedValue`: Custom reason for when a value of a field in a Listener 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. + * `Programmed/True/Programmed` + * `Programmed/False/Invalid` * `ResolvedRefs/True/ResolvedRefs` * `ResolvedRefs/False/InvalidCertificateRef` * `Conflicted/True/ProtocolConflict` diff --git a/internal/state/conditions/conditions.go b/internal/state/conditions/conditions.go index 687d37b4b6..c5ce697f88 100644 --- a/internal/state/conditions/conditions.go +++ b/internal/state/conditions/conditions.go @@ -12,6 +12,11 @@ const ( // is invalid or not supported. ListenerReasonUnsupportedValue v1beta1.ListenerConditionReason = "UnsupportedValue" + // ListenerMessageFailedNginxReload is a message used with ListenerConditionProgrammed (false) + // when nginx fails to reload. + ListenerMessageFailedNginxReload = "The Listener is not programmed due to a failure to " + + "reload nginx with the configuration" + // RouteReasonBackendRefUnsupportedValue is used with the "ResolvedRefs" condition when one of the // Route rules has a backendRef with an unsupported value. RouteReasonBackendRefUnsupportedValue = "UnsupportedValue" @@ -253,6 +258,7 @@ func NewRouteGatewayNotProgrammed(msg string) Condition { func NewDefaultListenerConditions() []Condition { return []Condition{ NewListenerAccepted(), + NewListenerProgrammed(), NewListenerResolvedRefs(), NewListenerNoConflicts(), } @@ -268,6 +274,16 @@ func NewListenerAccepted() Condition { } } +// NewListenerProgrammed returns a Condition that indicates the Listener is programmed. +func NewListenerProgrammed() Condition { + return Condition{ + Type: string(v1beta1.ListenerConditionProgrammed), + Status: metav1.ConditionTrue, + Reason: string(v1beta1.ListenerReasonProgrammed), + Message: "Listener is programmed", + } +} + // NewListenerResolvedRefs returns a Condition that indicates that all references in a Listener are resolved. func NewListenerResolvedRefs() Condition { return Condition{ @@ -288,17 +304,31 @@ func NewListenerNoConflicts() Condition { } } -// 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 { +// NewListenerNotProgrammedInvalid returns a Condition that indicates the Listener is not programmed because it is +// semantically or syntactically invalid. The provided message contains the details of why the Listener is invalid. +func NewListenerNotProgrammedInvalid(msg string) Condition { return Condition{ - Type: string(v1beta1.ListenerConditionAccepted), + Type: string(v1beta1.ListenerConditionProgrammed), Status: metav1.ConditionFalse, - Reason: string(ListenerReasonUnsupportedValue), + Reason: string(v1beta1.ListenerReasonInvalid), Message: msg, } } +// NewListenerUnsupportedValue returns Conditions that indicate 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 { + return []Condition{ + { + Type: string(v1beta1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(ListenerReasonUnsupportedValue), + Message: msg, + }, + NewListenerNotProgrammedInvalid(msg), + } +} + // NewListenerInvalidCertificateRef returns Conditions that indicate that a CertificateRef of a Listener is invalid. func NewListenerInvalidCertificateRef(msg string) []Condition { return []Condition{ @@ -314,6 +344,7 @@ func NewListenerInvalidCertificateRef(msg string) []Condition { Reason: string(v1beta1.ListenerReasonInvalidCertificateRef), Message: msg, }, + NewListenerNotProgrammedInvalid(msg), } } @@ -333,16 +364,20 @@ func NewListenerProtocolConflict(msg string) []Condition { Reason: string(v1beta1.ListenerReasonProtocolConflict), Message: msg, }, + NewListenerNotProgrammedInvalid(msg), } } -// NewListenerUnsupportedProtocol returns a Condition that indicates that the protocol of a Listener is unsupported. -func NewListenerUnsupportedProtocol(msg string) Condition { - return Condition{ - Type: string(v1beta1.ListenerConditionAccepted), - Status: metav1.ConditionFalse, - Reason: string(v1beta1.ListenerReasonUnsupportedProtocol), - Message: msg, +// NewListenerUnsupportedProtocol returns Conditions that indicate that the protocol of a Listener is unsupported. +func NewListenerUnsupportedProtocol(msg string) []Condition { + return []Condition{ + { + Type: string(v1beta1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.ListenerReasonUnsupportedProtocol), + Message: msg, + }, + NewListenerNotProgrammedInvalid(msg), } } @@ -368,7 +403,7 @@ func NewGatewayClassInvalidParameters(msg string) Condition { } } -// NewDefaultGatewayConditions returns the default Condition that must be present in the status of a Gateway. +// NewDefaultGatewayConditions returns the default Conditions that must be present in the status of a Gateway. func NewDefaultGatewayConditions() []Condition { return []Condition{ NewGatewayAccepted(), @@ -386,7 +421,7 @@ func NewGatewayAccepted() Condition { } } -// NewGatewayConflict returns a Condition that indicates the Gateway has a conflict with another Gateway. +// NewGatewayConflict returns Conditions that indicate the Gateway has a conflict with another Gateway. func NewGatewayConflict() []Condition { return []Condition{ { @@ -410,7 +445,7 @@ func NewGatewayAcceptedListenersNotValid() Condition { } } -// NewGatewayNotAcceptedListenersNotValid returns a Condition that indicates the Gateway is not accepted, +// NewGatewayNotAcceptedListenersNotValid returns Conditions that indicate the Gateway is not accepted, // because all listeners are invalid. func NewGatewayNotAcceptedListenersNotValid() []Condition { msg := "Gateway has no valid listeners" @@ -425,7 +460,7 @@ func NewGatewayNotAcceptedListenersNotValid() []Condition { } } -// NewGatewayInvalid returns a Condition that indicates the Gateway is not accepted and programmed because it is +// NewGatewayInvalid returns Conditions that indicate the Gateway is not accepted and programmed 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{ @@ -439,7 +474,7 @@ func NewGatewayInvalid(msg string) []Condition { } } -// NewGatewayUnsupportedValue returns a Condition that indicates that a field of the Gateway has an unsupported value. +// NewGatewayUnsupportedValue returns Conditions that indicate 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{ diff --git a/internal/state/graph/gateway_listener.go b/internal/state/graph/gateway_listener.go index 7c0172172c..c2a6a400b6 100644 --- a/internal/state/graph/gateway_listener.go +++ b/internal/state/graph/gateway_listener.go @@ -79,7 +79,7 @@ func newListenerConfiguratorFactory( listener.Protocol, []string{string(v1beta1.HTTPProtocolType), string(v1beta1.HTTPSProtocolType)}, ) - return []conditions.Condition{conditions.NewListenerUnsupportedProtocol(valErr.Error())} + return conditions.NewListenerUnsupportedProtocol(valErr.Error()) }, }, }, @@ -152,7 +152,7 @@ func (c *listenerConfigurator) configure(listener v1beta1.Listener) *Listener { allowedRouteSelector, err = metav1.LabelSelectorAsSelector(selector) if err != nil { msg := fmt.Sprintf("invalid label selector: %s", err.Error()) - conds = append(conds, conditions.NewListenerUnsupportedValue(msg)) + conds = append(conds, conditions.NewListenerUnsupportedValue(msg)...) } } @@ -199,7 +199,7 @@ func validateListenerHostname(listener v1beta1.Listener) []conditions.Condition if err != nil { path := field.NewPath("hostname") valErr := field.Invalid(path, listener.Hostname, err.Error()) - return []conditions.Condition{conditions.NewListenerUnsupportedValue(valErr.Error())} + return conditions.NewListenerUnsupportedValue(valErr.Error()) } return nil } @@ -221,7 +221,7 @@ func validateListenerAllowedRouteKind(listener v1beta1.Listener) []conditions.Co for _, kind := range listener.AllowedRoutes.Kinds { if !validHTTPRouteKind(kind) { msg := fmt.Sprintf("Unsupported route kind \"%s/%s\"", *kind.Group, kind.Kind) - return []conditions.Condition{conditions.NewListenerUnsupportedValue(msg)} + return conditions.NewListenerUnsupportedValue(msg) } } } @@ -237,7 +237,7 @@ func validateListenerLabelSelector(listener v1beta1.Listener) []conditions.Condi *listener.AllowedRoutes.Namespaces.From == v1beta1.NamespacesFromSelector && listener.AllowedRoutes.Namespaces.Selector == nil { msg := "Listener's AllowedRoutes Selector must be set when From is set to type Selector" - return []conditions.Condition{conditions.NewListenerUnsupportedValue(msg)} + return conditions.NewListenerUnsupportedValue(msg) } return nil @@ -247,7 +247,7 @@ func validateHTTPListener(listener v1beta1.Listener) []conditions.Condition { if err := validateListenerPort(listener.Port); err != nil { path := field.NewPath("port") valErr := field.Invalid(path, listener.Port, err.Error()) - return []conditions.Condition{conditions.NewListenerUnsupportedValue(valErr.Error())} + return conditions.NewListenerUnsupportedValue(valErr.Error()) } if listener.TLS != nil { @@ -272,7 +272,7 @@ func createHTTPSListenerValidator(gwNsName string) listenerValidator { if err := validateListenerPort(listener.Port); err != nil { path := field.NewPath("port") valErr := field.Invalid(path, listener.Port, err.Error()) - conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())) + conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())...) } if listener.TLS == nil { @@ -287,13 +287,13 @@ func createHTTPSListenerValidator(gwNsName string) listenerValidator { *listener.TLS.Mode, []string{string(v1beta1.TLSModeTerminate)}, ) - conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())) + conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())...) } if len(listener.TLS.Options) > 0 { path := tlsPath.Child("options") valErr := field.Forbidden(path, "options are not supported") - conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())) + conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())...) } if len(listener.TLS.CertificateRefs) == 0 { @@ -328,7 +328,7 @@ func createHTTPSListenerValidator(gwNsName string) listenerValidator { if l := len(listener.TLS.CertificateRefs); l > 1 { path := tlsPath.Child("certificateRefs") valErr := field.TooMany(path, l, 1) - conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())) + conds = append(conds, conditions.NewListenerUnsupportedValue(valErr.Error())...) } return conds diff --git a/internal/state/graph/gateway_listener_test.go b/internal/state/graph/gateway_listener_test.go index 6f229c34b8..ac79452f83 100644 --- a/internal/state/graph/gateway_listener_test.go +++ b/internal/state/graph/gateway_listener_test.go @@ -29,10 +29,8 @@ func TestValidateHTTPListener(t *testing.T) { l: v1beta1.Listener{ Port: 0, }, - expected: []conditions.Condition{ - conditions.NewListenerUnsupportedValue(`port: Invalid value: 0: port must be between 1-65535`), - }, - name: "invalid port", + expected: conditions.NewListenerUnsupportedValue(`port: Invalid value: 0: port must be between 1-65535`), + name: "invalid port", }, } @@ -98,10 +96,8 @@ func TestValidateHTTPSListener(t *testing.T) { CertificateRefs: []v1beta1.SecretObjectReference{validSecretRef}, }, }, - expected: []conditions.Condition{ - conditions.NewListenerUnsupportedValue(`port: Invalid value: 0: port must be between 1-65535`), - }, - name: "invalid port", + expected: conditions.NewListenerUnsupportedValue(`port: Invalid value: 0: port must be between 1-65535`), + name: "invalid port", }, { l: v1beta1.Listener{ @@ -112,10 +108,8 @@ func TestValidateHTTPSListener(t *testing.T) { Options: map[v1beta1.AnnotationKey]v1beta1.AnnotationValue{"key": "val"}, }, }, - expected: []conditions.Condition{ - conditions.NewListenerUnsupportedValue("tls.options: Forbidden: options are not supported"), - }, - name: "invalid options", + expected: conditions.NewListenerUnsupportedValue("tls.options: Forbidden: options are not supported"), + name: "invalid options", }, { l: v1beta1.Listener{ @@ -125,11 +119,9 @@ func TestValidateHTTPSListener(t *testing.T) { CertificateRefs: []v1beta1.SecretObjectReference{validSecretRef}, }, }, - expected: []conditions.Condition{ - conditions.NewListenerUnsupportedValue( - `tls.mode: Unsupported value: "Passthrough": supported values: "Terminate"`, - ), - }, + expected: conditions.NewListenerUnsupportedValue( + `tls.mode: Unsupported value: "Passthrough": supported values: "Terminate"`, + ), name: "invalid tls mode", }, { @@ -180,10 +172,8 @@ func TestValidateHTTPSListener(t *testing.T) { CertificateRefs: []v1beta1.SecretObjectReference{validSecretRef, validSecretRef}, }, }, - expected: []conditions.Condition{ - conditions.NewListenerUnsupportedValue("tls.certificateRefs: Too many: 2: must have at most 1 items"), - }, - name: "too many cert refs", + expected: conditions.NewListenerUnsupportedValue("tls.certificateRefs: Too many: 2: must have at most 1 items"), + name: "too many cert refs", }, } diff --git a/internal/state/graph/gateway_test.go b/internal/state/graph/gateway_test.go index 584309f7a6..e7e7d68503 100644 --- a/internal/state/graph/gateway_test.go +++ b/internal/state/graph/gateway_test.go @@ -370,11 +370,9 @@ func TestBuildGateway(t *testing.T) { "listener-with-invalid-selector": { Source: listenerInvalidSelector, Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue( - `invalid label selector: "invalid" is not a valid label selector operator`, - ), - }, + Conditions: conditions.NewListenerUnsupportedValue( + `invalid label selector: "invalid" is not a valid label selector operator`, + ), }, }, Valid: true, @@ -390,11 +388,9 @@ func TestBuildGateway(t *testing.T) { "invalid-protocol": { Source: invalidProtocolListener, Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedProtocol( - `protocol: Unsupported value: "TCP": supported values: "HTTP", "HTTPS"`, - ), - }, + Conditions: conditions.NewListenerUnsupportedProtocol( + `protocol: Unsupported value: "TCP": supported values: "HTTP", "HTTPS"`, + ), }, }, Valid: true, @@ -412,20 +408,16 @@ func TestBuildGateway(t *testing.T) { "invalid-port": { Source: invalidPortListener, Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue( - `port: Invalid value: 0: port must be between 1-65535`, - ), - }, + Conditions: conditions.NewListenerUnsupportedValue( + `port: Invalid value: 0: port must be between 1-65535`, + ), }, "invalid-https-port": { Source: invalidHTTPSPortListener, Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue( - `port: Invalid value: 0: port must be between 1-65535`, - ), - }, + Conditions: conditions.NewListenerUnsupportedValue( + `port: Invalid value: 0: port must be between 1-65535`, + ), }, }, Valid: true, @@ -441,18 +433,14 @@ func TestBuildGateway(t *testing.T) { Source: getLastCreatedGetaway(), Listeners: map[string]*Listener{ "invalid-hostname": { - Source: invalidHostnameListener, - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue(invalidHostnameMsg), - }, + Source: invalidHostnameListener, + Valid: false, + Conditions: conditions.NewListenerUnsupportedValue(invalidHostnameMsg), }, "invalid-https-hostname": { - Source: invalidHTTPSHostnameListener, - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue(invalidHostnameMsg), - }, + Source: invalidHTTPSHostnameListener, + Valid: false, + Conditions: conditions.NewListenerUnsupportedValue(invalidHostnameMsg), }, }, Valid: true, diff --git a/internal/status/statuses.go b/internal/status/statuses.go index e624367c9b..1e917c168c 100644 --- a/internal/status/statuses.go +++ b/internal/status/statuses.go @@ -179,6 +179,10 @@ func buildGatewayStatus(gateway *graph.Gateway, nginxReloadRes NginxReloadResult conds = l.Conditions } + if nginxReloadRes.Error != nil { + conds = append(conds, conditions.NewListenerNotProgrammedInvalid(conditions.ListenerMessageFailedNginxReload)) + } + listenerStatuses[name] = ListenerStatus{ AttachedRoutes: int32(len(l.Routes)), Conditions: conditions.DeduplicateConditions(conds), diff --git a/internal/status/statuses_test.go b/internal/status/statuses_test.go index b9e1427e98..26779498f5 100644 --- a/internal/status/statuses_test.go +++ b/internal/status/statuses_test.go @@ -252,7 +252,12 @@ func TestBuildStatusesNginxErr(t *testing.T) { ListenerStatuses: map[string]ListenerStatus{ "listener-80-1": { AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + Conditions: []conditions.Condition{ + conditions.NewListenerAccepted(), + conditions.NewListenerResolvedRefs(), + conditions.NewListenerNoConflicts(), + conditions.NewListenerNotProgrammedInvalid(conditions.ListenerMessageFailedNginxReload), + }, }, }, ObservedGeneration: 2, @@ -368,10 +373,8 @@ func TestBuildGatewayStatuses(t *testing.T) { }, }, "listener-invalid": { - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue("unsupported value"), - }, + Valid: false, + Conditions: conditions.NewListenerUnsupportedValue("unsupported value"), }, }, Valid: true, @@ -388,9 +391,7 @@ func TestBuildGatewayStatuses(t *testing.T) { Conditions: conditions.NewDefaultListenerConditions(), }, "listener-invalid": { - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue("unsupported value"), - }, + Conditions: conditions.NewListenerUnsupportedValue("unsupported value"), }, }, ObservedGeneration: 2, @@ -403,16 +404,12 @@ func TestBuildGatewayStatuses(t *testing.T) { Source: gw, Listeners: map[string]*graph.Listener{ "listener-invalid-1": { - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedProtocol("unsupported protocol"), - }, + Valid: false, + Conditions: conditions.NewListenerUnsupportedProtocol("unsupported protocol"), }, "listener-invalid-2": { - Valid: false, - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue("unsupported value"), - }, + Valid: false, + Conditions: conditions.NewListenerUnsupportedValue("unsupported value"), }, }, Valid: true, @@ -422,14 +419,10 @@ func TestBuildGatewayStatuses(t *testing.T) { Conditions: conditions.NewGatewayNotAcceptedListenersNotValid(), ListenerStatuses: map[string]ListenerStatus{ "listener-invalid-1": { - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedProtocol("unsupported protocol"), - }, + Conditions: conditions.NewListenerUnsupportedProtocol("unsupported protocol"), }, "listener-invalid-2": { - Conditions: []conditions.Condition{ - conditions.NewListenerUnsupportedValue("unsupported value"), - }, + Conditions: conditions.NewListenerUnsupportedValue("unsupported value"), }, }, ObservedGeneration: 2, @@ -451,7 +444,7 @@ func TestBuildGatewayStatuses(t *testing.T) { }, }, { - name: "error reloading nginx; gateway not programmed", + name: "error reloading nginx; gateway/listener not programmed", gateway: &graph.Gateway{ Source: gw, Valid: true, @@ -474,7 +467,12 @@ func TestBuildGatewayStatuses(t *testing.T) { ListenerStatuses: map[string]ListenerStatus{ "listener-valid": { AttachedRoutes: 1, - Conditions: conditions.NewDefaultListenerConditions(), + Conditions: []conditions.Condition{ + conditions.NewListenerAccepted(), + conditions.NewListenerResolvedRefs(), + conditions.NewListenerNoConflicts(), + conditions.NewListenerNotProgrammedInvalid(conditions.ListenerMessageFailedNginxReload), + }, }, }, ObservedGeneration: 2,