Skip to content

Commit

Permalink
internal/dag: set Listener condition for invalid route kinds
Browse files Browse the repository at this point in the history
Sets a Listener condition of "ResolvedRefs: false" with
a reason of "InvalidRouteKinds" when a Listener has an
invalid allowed route in its spec.

Closes projectcontour#3529.
Updates projectcontour#4124.

Signed-off-by: Steve Kriss <krisss@vmware.com>
  • Loading branch information
skriss committed Nov 16, 2021
1 parent fa0275b commit ff522ef
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 14 deletions.
37 changes: 24 additions & 13 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
}

// Get a list of the route kinds that the listener accepts.
listenerRouteKinds := p.getListenerRouteKinds(listener)
listenerRouteKinds := p.getListenerRouteKinds(listener, gwAccessor)
gwAccessor.SetListenerSupportedKinds(string(listener.Name), listenerRouteKinds)

attachedRoutes := 0
Expand Down Expand Up @@ -205,7 +205,7 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {

// getListenerRouteKinds gets a list of the valid route kinds that
// the listener accepts.
func (p *GatewayAPIProcessor) getListenerRouteKinds(listener gatewayapi_v1alpha2.Listener) []gatewayapi_v1alpha2.Kind {
func (p *GatewayAPIProcessor) getListenerRouteKinds(listener gatewayapi_v1alpha2.Listener, gwAccessor *status.GatewayStatusUpdate) []gatewayapi_v1alpha2.Kind {
// None specified on the listener: return the default based on
// the listener's protocol.
if len(listener.AllowedRoutes.Kinds) == 0 {
Expand All @@ -221,24 +221,35 @@ func (p *GatewayAPIProcessor) getListenerRouteKinds(listener gatewayapi_v1alpha2

var routeKinds []gatewayapi_v1alpha2.Kind

// TODO if a kind is invalid, set "ResolvedRefs: false" condition
// on the listener with "InvalidRouteKinds" reason.

for _, routeKind := range listener.AllowedRoutes.Kinds {
if routeKind.Group == nil {
p.Errorf("Listener.AllowedRoutes.Group not specified")
continue
}
if *routeKind.Group != gatewayapi_v1alpha2.GroupName {
p.Errorf("Listener.AllowedRoutes.Group %q not supported", *routeKind.Group)
if routeKind.Group != nil && *routeKind.Group != gatewayapi_v1alpha2.GroupName {
gwAccessor.AddListenerCondition(
string(listener.Name),
gatewayapi_v1alpha2.ListenerConditionResolvedRefs,
metav1.ConditionFalse,
gatewayapi_v1alpha2.ListenerReasonInvalidRouteKinds,
fmt.Sprintf("Group %q is not supported", *routeKind.Group),
)
continue
}
if routeKind.Kind != gatewayapi_v1alpha2.Kind(KindHTTPRoute) && routeKind.Kind != gatewayapi_v1alpha2.Kind(KindTLSRoute) {
p.Errorf("Listener.AllowedRoutes.Kind %q not supported", routeKind.Kind)
gwAccessor.AddListenerCondition(
string(listener.Name),
gatewayapi_v1alpha2.ListenerConditionResolvedRefs,
metav1.ConditionFalse,
gatewayapi_v1alpha2.ListenerReasonInvalidRouteKinds,
fmt.Sprintf("Kind %q is not supported", routeKind.Kind),
)
continue
}
if routeKind.Kind == gatewayapi_v1alpha2.Kind(KindTLSRoute) && listener.Protocol != gatewayapi_v1alpha2.TLSProtocolType {
p.Errorf("invalid listener protocol %q for Kind: TLSRoute", listener.Protocol)
gwAccessor.AddListenerCondition(
string(listener.Name),
gatewayapi_v1alpha2.ListenerConditionResolvedRefs,
metav1.ConditionFalse,
gatewayapi_v1alpha2.ListenerReasonInvalidRouteKinds,
fmt.Sprintf("TLSRoutes are incompatible with listener protocol %q", listener.Protocol),
)
continue
}

Expand Down
150 changes: 150 additions & 0 deletions internal/dag/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3738,6 +3738,156 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) {
},
}},
})

run(t, "invalid allowedroutes API group results in a listener condition", testcase{
objs: []interface{}{},
gateway: &gatewayapi_v1alpha2.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha2.GatewaySpec{
Listeners: []gatewayapi_v1alpha2.Listener{{
Name: "http",
Port: 80,
Protocol: gatewayapi_v1alpha2.HTTPProtocolType,
AllowedRoutes: &gatewayapi_v1alpha2.AllowedRoutes{
Kinds: []gatewayapi_v1alpha2.RouteGroupKind{
{Group: gatewayapi.GroupPtr("invalid-group"), Kind: "HTTPRoute"},
},
Namespaces: &gatewayapi_v1alpha2.RouteNamespaces{
From: gatewayapi.FromNamespacesPtr(gatewayapi_v1alpha2.NamespacesFromAll),
},
},
}},
},
},
wantGatewayStatusUpdate: []*status.GatewayStatusUpdate{{
FullName: types.NamespacedName{Namespace: "projectcontour", Name: "contour"},
Conditions: map[gatewayapi_v1alpha2.GatewayConditionType]metav1.Condition{
gatewayapi_v1alpha2.GatewayConditionReady: {
Type: string(gatewayapi_v1alpha2.GatewayConditionReady),
Status: contour_api_v1.ConditionTrue,
Reason: status.ReasonValidGateway,
Message: "Valid Gateway",
},
},
ListenerStatus: map[string]*gatewayapi_v1alpha2.ListenerStatus{
"http": {
Name: "http",
SupportedKinds: nil,
Conditions: []metav1.Condition{
{
Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidRouteKinds),
Message: "Group \"invalid-group\" is not supported",
},
},
},
},
}},
})

run(t, "invalid allowedroutes API kind results in a listener condition", testcase{
objs: []interface{}{},
gateway: &gatewayapi_v1alpha2.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha2.GatewaySpec{
Listeners: []gatewayapi_v1alpha2.Listener{{
Name: "http",
Port: 80,
Protocol: gatewayapi_v1alpha2.HTTPProtocolType,
AllowedRoutes: &gatewayapi_v1alpha2.AllowedRoutes{
Kinds: []gatewayapi_v1alpha2.RouteGroupKind{
{Kind: "FooRoute"},
},
Namespaces: &gatewayapi_v1alpha2.RouteNamespaces{
From: gatewayapi.FromNamespacesPtr(gatewayapi_v1alpha2.NamespacesFromAll),
},
},
}},
},
},
wantGatewayStatusUpdate: []*status.GatewayStatusUpdate{{
FullName: types.NamespacedName{Namespace: "projectcontour", Name: "contour"},
Conditions: map[gatewayapi_v1alpha2.GatewayConditionType]metav1.Condition{
gatewayapi_v1alpha2.GatewayConditionReady: {
Type: string(gatewayapi_v1alpha2.GatewayConditionReady),
Status: contour_api_v1.ConditionTrue,
Reason: status.ReasonValidGateway,
Message: "Valid Gateway",
},
},
ListenerStatus: map[string]*gatewayapi_v1alpha2.ListenerStatus{
"http": {
Name: "http",
SupportedKinds: nil,
Conditions: []metav1.Condition{
{
Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidRouteKinds),
Message: "Kind \"FooRoute\" is not supported",
},
},
},
},
}},
})

run(t, "allowedroute of TLSRoute on a non-TLS listener results in a listener condition", testcase{
objs: []interface{}{},
gateway: &gatewayapi_v1alpha2.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha2.GatewaySpec{
Listeners: []gatewayapi_v1alpha2.Listener{{
Name: "http",
Port: 80,
Protocol: gatewayapi_v1alpha2.HTTPProtocolType,
AllowedRoutes: &gatewayapi_v1alpha2.AllowedRoutes{
Kinds: []gatewayapi_v1alpha2.RouteGroupKind{
{Kind: "TLSRoute"},
},
Namespaces: &gatewayapi_v1alpha2.RouteNamespaces{
From: gatewayapi.FromNamespacesPtr(gatewayapi_v1alpha2.NamespacesFromAll),
},
},
}},
},
},
wantGatewayStatusUpdate: []*status.GatewayStatusUpdate{{
FullName: types.NamespacedName{Namespace: "projectcontour", Name: "contour"},
Conditions: map[gatewayapi_v1alpha2.GatewayConditionType]metav1.Condition{
gatewayapi_v1alpha2.GatewayConditionReady: {
Type: string(gatewayapi_v1alpha2.GatewayConditionReady),
Status: contour_api_v1.ConditionTrue,
Reason: status.ReasonValidGateway,
Message: "Valid Gateway",
},
},
ListenerStatus: map[string]*gatewayapi_v1alpha2.ListenerStatus{
"http": {
Name: "http",
SupportedKinds: nil,
Conditions: []metav1.Condition{
{
Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidRouteKinds),
Message: "TLSRoutes are incompatible with listener protocol \"HTTP\"",
},
},
},
},
}},
})
}

func TestGatewayAPITLSRouteDAGStatus(t *testing.T) {
Expand Down
50 changes: 49 additions & 1 deletion internal/status/gatewaystatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,52 @@ func (gatewayUpdate *GatewayStatusUpdate) SetListenerAttachedRoutes(listenerName
gatewayUpdate.ListenerStatus[listenerName].AttachedRoutes = int32(numRoutes)
}

// AddListenerCondition adds a Condition for the specified listener.
func (gatewayUpdate *GatewayStatusUpdate) AddListenerCondition(
listenerName string,
cond gatewayapi_v1alpha2.ListenerConditionType,
status metav1.ConditionStatus,
reason gatewayapi_v1alpha2.ListenerConditionReason,
message string,
) metav1.Condition {
if gatewayUpdate.ListenerStatus == nil {
gatewayUpdate.ListenerStatus = map[string]*gatewayapi_v1alpha2.ListenerStatus{}
}
if gatewayUpdate.ListenerStatus[listenerName] == nil {
gatewayUpdate.ListenerStatus[listenerName] = &gatewayapi_v1alpha2.ListenerStatus{
Name: gatewayapi_v1alpha2.SectionName(listenerName),
}
}

listenerStatus := gatewayUpdate.ListenerStatus[listenerName]

idx := -1
for i, existing := range listenerStatus.Conditions {
if existing.Type == string(cond) {
idx = i
message = fmt.Sprintf("%s, %s", existing.Message, message)
break
}
}

newCond := metav1.Condition{
Reason: string(reason),
Status: status,
Type: string(cond),
Message: message,
LastTransitionTime: metav1.NewTime(clock.Now()),
ObservedGeneration: gatewayUpdate.Generation,
}

if idx > -1 {
listenerStatus.Conditions[idx] = newCond
} else {
listenerStatus.Conditions = append(listenerStatus.Conditions, newCond)
}

return newCond
}

func getGatewayConditions(gs *gatewayapi_v1alpha2.GatewayStatus) map[gatewayapi_v1alpha2.GatewayConditionType]metav1.Condition {
conditions := make(map[gatewayapi_v1alpha2.GatewayConditionType]metav1.Condition)
for _, cond := range gs.Conditions {
Expand Down Expand Up @@ -156,7 +202,9 @@ func (gatewayUpdate *GatewayStatusUpdate) Mutate(obj client.Object) client.Objec
// for each Gateway status update.
var listenerStatusToWrite []gatewayapi_v1alpha2.ListenerStatus
for _, status := range gatewayUpdate.ListenerStatus {
status.Conditions = []metav1.Condition{} // Conditions is a required field so we have to specify an empty slice here
if status.Conditions == nil {
status.Conditions = []metav1.Condition{} // Conditions is a required field so we have to specify an empty slice here
}
listenerStatusToWrite = append(listenerStatusToWrite, *status)
}

Expand Down

0 comments on commit ff522ef

Please sign in to comment.