1 package gateway_test
2
3 import (
4 "context"
5 "fmt"
6 "io/ioutil"
7 "net/http"
8 "testing"
9 "time"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 gw "sigs.k8s.io/gateway-api/apis/v1alpha1"
14
15 "github.com/datawire/dlib/dgroup"
16 "github.com/datawire/dlib/dlog"
17 "github.com/emissary-ingress/emissary/v3/pkg/envoytest"
18 "github.com/emissary-ingress/emissary/v3/pkg/gateway"
19 "github.com/emissary-ingress/emissary/v3/pkg/kates"
20 )
21
22 func TestGatewayMatches(t *testing.T) {
23 t.Parallel()
24
25 ctx := dlog.NewTestContext(t, false)
26 grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
27 EnableWithSoftness: true,
28 ShutdownOnNonError: true,
29 })
30
31 grp.Go("upstream", func(ctx context.Context) error {
32 var reqLogger envoytest.RequestLogger
33 return reqLogger.ListenAndServeHTTP(ctx, ":9000", ":9002")
34 })
35 e := envoytest.NewEnvoyController(":8003")
36 grp.Go("envoyController", func(ctx context.Context) error {
37 return e.Run(ctx)
38 })
39 grp.Go("envoy", func(ctx context.Context) error {
40 addr, err := envoytest.GetLoopbackAddr(ctx, 8003)
41 if err != nil {
42 return err
43 }
44 return envoytest.RunEnvoy(ctx, addr, "8080:8080")
45 })
46 grp.Go("downstream", func(ctx context.Context) error {
47 d, err := makeDispatcher()
48 if err != nil {
49 return err
50 }
51
52
53
54 if err := d.UpsertYaml(`
55 ---
56 kind: Gateway
57 apiVersion: networking.x-k8s.io/v1alpha1
58 metadata:
59 name: my-gateway
60 namespace: default
61 spec:
62 listeners:
63 - protocol: HTTP
64 port: 8080
65 ---
66 kind: HTTPRoute
67 apiVersion: networking.x-k8s.io/v1alpha1
68 metadata:
69 name: my-route
70 namespace: default
71
72 spec:
73 rules:
74 - matches:
75 - path:
76 type: Exact
77 value: /exact
78 forwardTo:
79 - serviceName: foo-backend-1
80 port: 9000
81 weight: 100
82 - matches:
83 - path:
84 type: Prefix
85 value: /prefix
86 forwardTo:
87 - serviceName: foo-backend-1
88 weight: 100
89 - matches:
90 - path:
91 type: RegularExpression
92 value: "/regular_expression(_[aA]+)?"
93 forwardTo:
94 - serviceName: foo-backend-1
95 weight: 100
96 - matches:
97 - headers:
98 type: Exact
99 values:
100 exact: foo
101 forwardTo:
102 - serviceName: foo-backend-1
103 weight: 100
104 - matches:
105 - headers:
106 type: RegularExpression
107 values:
108 regular_expression: "foo(_[aA]+)?"
109 forwardTo:
110 - serviceName: foo-backend-1
111 weight: 100
112 `); err != nil {
113 return err
114 }
115
116 loopbackIp, err := envoytest.GetLoopbackIp(ctx)
117 if err != nil {
118 return err
119 }
120
121 if err := d.Upsert(makeEndpoint("default", "foo-backend-1", loopbackIp, 9000)); err != nil {
122 return err
123 }
124 if err := d.Upsert(makeEndpoint("default", "foo-backend-2", loopbackIp, 9001)); err != nil {
125 return err
126 }
127
128
129
130 version, snapshot := d.GetSnapshot(ctx)
131 if status, err := e.Configure(ctx, "test-id", version, snapshot); err != nil {
132 return err
133 } else if status != nil {
134 return fmt.Errorf("envoy error: %s", status.Message)
135 }
136
137
138
139
140
141 if err := checkReady(ctx, "http://127.0.0.1:8080/"); err != nil {
142 return err
143 }
144
145 assertGet(&err, ctx, "http://127.0.0.1:8080/exact", 200, "Hello World")
146 assertGet(&err, ctx, "http://127.0.0.1:8080/exact/foo", 404, "")
147 assertGet(&err, ctx, "http://127.0.0.1:8080/prefix", 200, "Hello World")
148 assertGet(&err, ctx, "http://127.0.0.1:8080/prefix/foo", 200, "Hello World")
149
150 assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression", 200, "Hello World")
151 assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_a", 200, "Hello World")
152 assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_aaaaaaaa", 200, "Hello World")
153 assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_aaAaaaAa", 200, "Hello World")
154 assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_aaAaaaAab", 404, "")
155
156 assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "exact", "foo", 200, "Hello World")
157 assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "exact", "bar", 404, "")
158 assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "foo", 200, "Hello World")
159 assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "foo_aaaaAaaaa", 200, "Hello World")
160 assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "foo_aaaaAaaaab", 404, "")
161 assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "bar", 404, "")
162
163 return err
164 })
165 assert.NoError(t, grp.Wait())
166 }
167
168 func TestBadMatchTypes(t *testing.T) {
169 t.Parallel()
170 d, err := makeDispatcher()
171 require.NoError(t, err)
172
173
174
175 err = d.UpsertYaml(`
176 ---
177 kind: HTTPRoute
178 apiVersion: networking.x-k8s.io/v1alpha1
179 metadata:
180 name: my-route
181 namespace: default
182 spec:
183 rules:
184 - matches:
185 - path:
186 type: Blah
187 value: /exact
188 forwardTo:
189 - serviceName: foo-backend-1
190 port: 9000
191 weight: 100
192 `)
193 assertErrorContains(t, err, `processing HTTPRoute:default:my-route: unknown path match type: "Blah"`)
194
195 err = d.UpsertYaml(`
196 ---
197 kind: HTTPRoute
198 apiVersion: networking.x-k8s.io/v1alpha1
199 metadata:
200 name: my-route
201 namespace: default
202 spec:
203 rules:
204 - matches:
205 - headers:
206 type: Bleh
207 values:
208 exact: foo
209 forwardTo:
210 - serviceName: foo-backend-1
211 weight: 100
212 `)
213 assertErrorContains(t, err, `processing HTTPRoute:default:my-route: unknown header match type: Bleh`)
214 }
215
216 func makeDispatcher() (*gateway.Dispatcher, error) {
217 d := gateway.NewDispatcher()
218
219 if err := d.Register("Gateway", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
220 return gateway.Compile_Gateway(untyped.(*gw.Gateway))
221 }); err != nil {
222 return nil, err
223 }
224
225 if err := d.Register("HTTPRoute", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
226 return gateway.Compile_HTTPRoute(untyped.(*gw.HTTPRoute))
227 }); err != nil {
228 return nil, err
229 }
230
231 if err := d.Register("Endpoints", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
232 return gateway.Compile_Endpoints(untyped.(*kates.Endpoints))
233 }); err != nil {
234 return nil, err
235 }
236
237 return d, nil
238 }
239
240 func makeEndpoint(namespace, name, ip string, port int) *kates.Endpoints {
241 ports := []kates.EndpointPort{{Port: int32(port)}}
242 addrs := []kates.EndpointAddress{{IP: ip}}
243
244 return &kates.Endpoints{
245 TypeMeta: kates.TypeMeta{Kind: "Endpoints"},
246 ObjectMeta: kates.ObjectMeta{Namespace: namespace, Name: name},
247 Subsets: []kates.EndpointSubset{{Addresses: addrs, Ports: ports}},
248 }
249 }
250
251 func checkReady(ctx context.Context, url string) error {
252 delay := 10 * time.Millisecond
253 for {
254 if delay > 10*time.Second {
255 return fmt.Errorf("url never became ready: %v", url)
256 }
257 _, err := http.Get(url)
258 if err != nil {
259 dlog.Infof(ctx, "error %v, retrying...", err)
260 delay = delay * 2
261 time.Sleep(delay)
262 continue
263 }
264 return nil
265 }
266 }
267
268 func get(ctx context.Context, url string, expectedCode int, expectedBody string, headers map[string]string) error {
269 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
270 if err != nil {
271 return err
272 }
273 for k, v := range headers {
274 req.Header.Set(k, v)
275 }
276 resp, err := http.DefaultClient.Do(req)
277 if err != nil {
278 return err
279 }
280 if resp.StatusCode != expectedCode {
281 return fmt.Errorf("expected HTTP status code %d but got %d",
282 expectedCode, resp.StatusCode)
283 }
284 actualBody, err := ioutil.ReadAll(resp.Body)
285 if err != nil {
286 return err
287 }
288 if string(actualBody) != expectedBody {
289 return fmt.Errorf("expected body %q but got %q",
290 expectedBody, string(actualBody))
291 }
292 return nil
293 }
294
295 func assertGet(errPtr *error, ctx context.Context, url string, code int, expected string) {
296 if *errPtr != nil {
297 return
298 }
299 err := get(ctx, url, code, expected, nil)
300 if err != nil && *errPtr == nil {
301 *errPtr = err
302 }
303 }
304
305 func assertGetHeader(errPtr *error, ctx context.Context, url, header, value string, code int, expected string) {
306 if *errPtr != nil {
307 return
308 }
309 err := get(ctx, url, code, expected, map[string]string{
310 header: value,
311 })
312 if err != nil && *errPtr == nil {
313 *errPtr = err
314 }
315 }
316
View as plain text