Skip to content

Commit cd6d20c

Browse files
authored
Merge pull request #4625 from zac-nixon/main
[Gateway API] ListenerSet Loader
2 parents 54b5940 + bb33ac0 commit cd6d20c

File tree

7 files changed

+892
-56
lines changed

7 files changed

+892
-56
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package routeutils
2+
3+
import (
4+
"context"
5+
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
8+
)
9+
10+
// doesResourceAttachToGateway checks if a target resource (route, listenerset) wishes to connect to the gateway.
11+
// this is done by following Gateway API conventions on the parent reference found within target resource.
12+
func doesResourceAttachToGateway(parentRef gwv1.ParentReference, resourceNamespace string, gw gwv1.Gateway) bool {
13+
// Default for kind is Gateway.
14+
if parentRef.Kind != nil && *parentRef.Kind != gatewayKind {
15+
return false
16+
}
17+
18+
var namespaceToCompare string
19+
20+
if parentRef.Namespace != nil {
21+
namespaceToCompare = string(*parentRef.Namespace)
22+
} else {
23+
namespaceToCompare = resourceNamespace
24+
}
25+
26+
nameCheck := string(parentRef.Name) == gw.Name
27+
nsCheck := gw.Namespace == namespaceToCompare
28+
return nameCheck && nsCheck
29+
}
30+
31+
func doesResourceAllowNamespace(ctx context.Context, fromNamespaces gwv1.FromNamespaces, labelSelector *metav1.LabelSelector, nsSelector namespaceSelector, resourceNamespace string, gw gwv1.Gateway) (bool, error) {
32+
switch fromNamespaces {
33+
case gwv1.NamespacesFromNone:
34+
return false, nil
35+
case gwv1.NamespacesFromSame:
36+
return gw.Namespace == resourceNamespace, nil
37+
case gwv1.NamespacesFromAll:
38+
return true, nil
39+
case gwv1.NamespacesFromSelector:
40+
if labelSelector == nil {
41+
return false, nil
42+
}
43+
// This should be executed off the client-go cache, hence we do not need to perform local caching.
44+
namespaces, err := nsSelector.getNamespacesFromSelector(ctx, labelSelector)
45+
if err != nil {
46+
return false, err
47+
}
48+
49+
if !namespaces.Has(resourceNamespace) {
50+
return false, nil
51+
}
52+
return true, nil
53+
default:
54+
// Unclear what to do in this case, we'll just filter out this route.
55+
return false, nil
56+
}
57+
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package routeutils
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
awssdk "github.com/aws/aws-sdk-go-v2/aws"
8+
"github.com/pkg/errors"
9+
"github.com/stretchr/testify/assert"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/util/sets"
12+
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
13+
)
14+
15+
func Test_doesResourceAttachToGateway(t *testing.T) {
16+
testCases := []struct {
17+
name string
18+
parentRef gwv1.ParentReference
19+
resourceNamespace string
20+
gw gwv1.Gateway
21+
expected bool
22+
}{
23+
{
24+
name: "nil kind defaults to Gateway, matching name and namespace",
25+
parentRef: gwv1.ParentReference{
26+
Name: "my-gw",
27+
},
28+
resourceNamespace: "ns1",
29+
gw: gwv1.Gateway{
30+
ObjectMeta: metav1.ObjectMeta{
31+
Name: "my-gw",
32+
Namespace: "ns1",
33+
},
34+
},
35+
expected: true,
36+
},
37+
{
38+
name: "explicit Gateway kind, matching name and explicit namespace",
39+
parentRef: gwv1.ParentReference{
40+
Name: "my-gw",
41+
Kind: (*gwv1.Kind)(awssdk.String("Gateway")),
42+
Namespace: (*gwv1.Namespace)(awssdk.String("ns1")),
43+
},
44+
resourceNamespace: "other-ns",
45+
gw: gwv1.Gateway{
46+
ObjectMeta: metav1.ObjectMeta{
47+
Name: "my-gw",
48+
Namespace: "ns1",
49+
},
50+
},
51+
expected: true,
52+
},
53+
{
54+
name: "non-Gateway kind returns false",
55+
parentRef: gwv1.ParentReference{
56+
Name: "my-gw",
57+
Kind: (*gwv1.Kind)(awssdk.String("Service")),
58+
},
59+
resourceNamespace: "ns1",
60+
gw: gwv1.Gateway{
61+
ObjectMeta: metav1.ObjectMeta{
62+
Name: "my-gw",
63+
Namespace: "ns1",
64+
},
65+
},
66+
expected: false,
67+
},
68+
{
69+
name: "name mismatch returns false",
70+
parentRef: gwv1.ParentReference{
71+
Name: "other-gw",
72+
},
73+
resourceNamespace: "ns1",
74+
gw: gwv1.Gateway{
75+
ObjectMeta: metav1.ObjectMeta{
76+
Name: "my-gw",
77+
Namespace: "ns1",
78+
},
79+
},
80+
expected: false,
81+
},
82+
{
83+
name: "namespace mismatch via explicit namespace returns false",
84+
parentRef: gwv1.ParentReference{
85+
Name: "my-gw",
86+
Namespace: (*gwv1.Namespace)(awssdk.String("ns2")),
87+
},
88+
resourceNamespace: "ns1",
89+
gw: gwv1.Gateway{
90+
ObjectMeta: metav1.ObjectMeta{
91+
Name: "my-gw",
92+
Namespace: "ns1",
93+
},
94+
},
95+
expected: false,
96+
},
97+
{
98+
name: "namespace mismatch via resource namespace returns false",
99+
parentRef: gwv1.ParentReference{
100+
Name: "my-gw",
101+
},
102+
resourceNamespace: "ns2",
103+
gw: gwv1.Gateway{
104+
ObjectMeta: metav1.ObjectMeta{
105+
Name: "my-gw",
106+
Namespace: "ns1",
107+
},
108+
},
109+
expected: false,
110+
},
111+
{
112+
name: "nil kind with explicit namespace matching",
113+
parentRef: gwv1.ParentReference{
114+
Name: "my-gw",
115+
Namespace: (*gwv1.Namespace)(awssdk.String("ns1")),
116+
},
117+
resourceNamespace: "different-ns",
118+
gw: gwv1.Gateway{
119+
ObjectMeta: metav1.ObjectMeta{
120+
Name: "my-gw",
121+
Namespace: "ns1",
122+
},
123+
},
124+
expected: true,
125+
},
126+
}
127+
128+
for _, tc := range testCases {
129+
t.Run(tc.name, func(t *testing.T) {
130+
result := doesResourceAttachToGateway(tc.parentRef, tc.resourceNamespace, tc.gw)
131+
assert.Equal(t, tc.expected, result)
132+
})
133+
}
134+
}
135+
136+
func Test_doesResourceAllowNamespace(t *testing.T) {
137+
testCases := []struct {
138+
name string
139+
fromNamespaces gwv1.FromNamespaces
140+
labelSelector *metav1.LabelSelector
141+
nsSelector namespaceSelector
142+
resourceNamespace string
143+
gw gwv1.Gateway
144+
expected bool
145+
expectErr bool
146+
}{
147+
{
148+
name: "NamespacesFromNone returns false",
149+
fromNamespaces: gwv1.NamespacesFromNone,
150+
resourceNamespace: "ns1",
151+
gw: gwv1.Gateway{
152+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"},
153+
},
154+
expected: false,
155+
},
156+
{
157+
name: "NamespacesFromSame with same namespace returns true",
158+
fromNamespaces: gwv1.NamespacesFromSame,
159+
resourceNamespace: "ns1",
160+
gw: gwv1.Gateway{
161+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"},
162+
},
163+
expected: true,
164+
},
165+
{
166+
name: "NamespacesFromSame with different namespace returns false",
167+
fromNamespaces: gwv1.NamespacesFromSame,
168+
resourceNamespace: "ns2",
169+
gw: gwv1.Gateway{
170+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"},
171+
},
172+
expected: false,
173+
},
174+
{
175+
name: "NamespacesFromAll returns true",
176+
fromNamespaces: gwv1.NamespacesFromAll,
177+
resourceNamespace: "any-ns",
178+
gw: gwv1.Gateway{
179+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"},
180+
},
181+
expected: true,
182+
},
183+
{
184+
name: "NamespacesFromSelector with nil selector returns false",
185+
fromNamespaces: gwv1.NamespacesFromSelector,
186+
labelSelector: nil,
187+
resourceNamespace: "ns1",
188+
gw: gwv1.Gateway{
189+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"},
190+
},
191+
expected: false,
192+
},
193+
{
194+
name: "NamespacesFromSelector with matching namespace returns true",
195+
fromNamespaces: gwv1.NamespacesFromSelector,
196+
labelSelector: &metav1.LabelSelector{},
197+
nsSelector: &mockNamespaceSelector{
198+
nss: sets.New("ns1", "ns3"),
199+
},
200+
resourceNamespace: "ns1",
201+
gw: gwv1.Gateway{
202+
ObjectMeta: metav1.ObjectMeta{Namespace: "gw-ns"},
203+
},
204+
expected: true,
205+
},
206+
{
207+
name: "NamespacesFromSelector with non-matching namespace returns false",
208+
fromNamespaces: gwv1.NamespacesFromSelector,
209+
labelSelector: &metav1.LabelSelector{},
210+
nsSelector: &mockNamespaceSelector{
211+
nss: sets.New("ns3", "ns5"),
212+
},
213+
resourceNamespace: "ns1",
214+
gw: gwv1.Gateway{
215+
ObjectMeta: metav1.ObjectMeta{Namespace: "gw-ns"},
216+
},
217+
expected: false,
218+
},
219+
{
220+
name: "NamespacesFromSelector with error returns error",
221+
fromNamespaces: gwv1.NamespacesFromSelector,
222+
labelSelector: &metav1.LabelSelector{},
223+
nsSelector: &mockNamespaceSelector{
224+
err: errors.New("k8s error"),
225+
},
226+
resourceNamespace: "ns1",
227+
gw: gwv1.Gateway{
228+
ObjectMeta: metav1.ObjectMeta{Namespace: "gw-ns"},
229+
},
230+
expectErr: true,
231+
},
232+
{
233+
name: "unknown FromNamespaces value returns false",
234+
fromNamespaces: gwv1.FromNamespaces("Unknown"),
235+
resourceNamespace: "ns1",
236+
gw: gwv1.Gateway{
237+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1"},
238+
},
239+
expected: false,
240+
},
241+
}
242+
243+
for _, tc := range testCases {
244+
t.Run(tc.name, func(t *testing.T) {
245+
result, err := doesResourceAllowNamespace(context.Background(), tc.fromNamespaces, tc.labelSelector, tc.nsSelector, tc.resourceNamespace, tc.gw)
246+
if tc.expectErr {
247+
assert.Error(t, err)
248+
return
249+
}
250+
assert.NoError(t, err)
251+
assert.Equal(t, tc.expected, result)
252+
})
253+
}
254+
}

pkg/gateway/routeutils/listener_attachment_helper.go

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/go-logr/logr"
88
"github.com/pkg/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
910
"k8s.io/apimachinery/pkg/types"
1011
"k8s.io/apimachinery/pkg/util/sets"
1112
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -87,38 +88,14 @@ func (attachmentHelper *listenerAttachmentHelperImpl) listenerAllowsAttachment(c
8788
// and route to determine compatibility.
8889
func (attachmentHelper *listenerAttachmentHelperImpl) namespaceCheck(ctx context.Context, gw gwv1.Gateway, listener gwv1.Listener, route preLoadRouteDescriptor) (bool, error) {
8990
var allowedNamespaces gwv1.FromNamespaces
90-
91+
var labelSelector *metav1.LabelSelector
9192
if listener.AllowedRoutes == nil || listener.AllowedRoutes.Namespaces == nil || listener.AllowedRoutes.Namespaces.From == nil {
9293
allowedNamespaces = gwv1.NamespacesFromSame
9394
} else {
9495
allowedNamespaces = *listener.AllowedRoutes.Namespaces.From
96+
labelSelector = listener.AllowedRoutes.Namespaces.Selector
9597
}
96-
97-
namespacedName := route.GetRouteNamespacedName()
98-
99-
switch allowedNamespaces {
100-
case gwv1.NamespacesFromSame:
101-
return gw.Namespace == namespacedName.Namespace, nil
102-
case gwv1.NamespacesFromAll:
103-
return true, nil
104-
case gwv1.NamespacesFromSelector:
105-
if listener.AllowedRoutes.Namespaces.Selector == nil {
106-
return false, nil
107-
}
108-
// This should be executed off the client-go cache, hence we do not need to perform local caching.
109-
namespaces, err := attachmentHelper.namespaceSelector.getNamespacesFromSelector(ctx, listener.AllowedRoutes.Namespaces.Selector)
110-
if err != nil {
111-
return false, err
112-
}
113-
114-
if !namespaces.Has(namespacedName.Namespace) {
115-
return false, nil
116-
}
117-
return true, nil
118-
default:
119-
// Unclear what to do in this case, we'll just filter out this route.
120-
return false, nil
121-
}
98+
return doesResourceAllowNamespace(ctx, allowedNamespaces, labelSelector, attachmentHelper.namespaceSelector, route.GetRouteNamespacedName().Namespace, gw)
12299
}
123100

124101
// kindCheck kind check implements the Gateway API spec for kindCheck matching between listener

0 commit comments

Comments
 (0)