1 package gateway_test
2
3 import (
4 "io/ioutil"
5 "net/http"
6 "testing"
7 "time"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11 gw "sigs.k8s.io/gateway-api/apis/v1alpha1"
12
13 "github.com/datawire/ambassador/v2/pkg/envoytest"
14 "github.com/datawire/ambassador/v2/pkg/gateway"
15 "github.com/datawire/ambassador/v2/pkg/kates"
16 "github.com/datawire/dlib/dlog"
17 )
18
19 func TestGatewayMatches(t *testing.T) {
20 t.Parallel()
21 ctx := dlog.NewTestContext(t, false)
22 envoytest.SetupRequestLogger(t, ":9000", ":9002")
23 e := envoytest.SetupEnvoyController(t, ":8003")
24 addr, err := envoytest.GetLoopbackAddr(ctx, 8003)
25 require.NoError(t, err)
26 envoytest.SetupEnvoy(t, addr, "8080:8080")
27
28 d := makeDispatcher(t)
29
30
31
32 err = d.UpsertYaml(`
33 ---
34 kind: Gateway
35 apiVersion: networking.x-k8s.io/v1alpha1
36 metadata:
37 name: my-gateway
38 namespace: default
39 spec:
40 listeners:
41 - protocol: HTTP
42 port: 8080
43 ---
44 kind: HTTPRoute
45 apiVersion: networking.x-k8s.io/v1alpha1
46 metadata:
47 name: my-route
48 namespace: default
49
50 spec:
51 rules:
52 - matches:
53 - path:
54 type: Exact
55 value: /exact
56 forwardTo:
57 - serviceName: foo-backend-1
58 port: 9000
59 weight: 100
60 - matches:
61 - path:
62 type: Prefix
63 value: /prefix
64 forwardTo:
65 - serviceName: foo-backend-1
66 weight: 100
67 - matches:
68 - path:
69 type: RegularExpression
70 value: "/regular_expression(_[aA]+)?"
71 forwardTo:
72 - serviceName: foo-backend-1
73 weight: 100
74 - matches:
75 - headers:
76 type: Exact
77 values:
78 exact: foo
79 forwardTo:
80 - serviceName: foo-backend-1
81 weight: 100
82 - matches:
83 - headers:
84 type: RegularExpression
85 values:
86 regular_expression: "foo(_[aA]+)?"
87 forwardTo:
88 - serviceName: foo-backend-1
89 weight: 100
90 `)
91
92 require.NoError(t, err)
93
94 loopbackIp, err := envoytest.GetLoopbackIp(ctx)
95 require.NoError(t, err)
96
97 err = d.Upsert(makeEndpoint("default", "foo-backend-1", loopbackIp, 9000))
98 require.NoError(t, err)
99 err = d.Upsert(makeEndpoint("default", "foo-backend-2", loopbackIp, 9001))
100 require.NoError(t, err)
101
102 version, snapshot := d.GetSnapshot(ctx)
103 status, err := e.Configure("test-id", version, *snapshot)
104 require.NoError(t, err)
105 if status != nil {
106 t.Fatalf("envoy error: %s", status.Message)
107 }
108
109
110
111
112
113 checkReady(t, "http://127.0.0.1:8080/")
114
115 assertGet(t, "http://127.0.0.1:8080/exact", 200, "Hello World")
116 assertGet(t, "http://127.0.0.1:8080/exact/foo", 404, "")
117 assertGet(t, "http://127.0.0.1:8080/prefix", 200, "Hello World")
118 assertGet(t, "http://127.0.0.1:8080/prefix/foo", 200, "Hello World")
119
120 assertGet(t, "http://127.0.0.1:8080/regular_expression", 200, "Hello World")
121 assertGet(t, "http://127.0.0.1:8080/regular_expression_a", 200, "Hello World")
122 assertGet(t, "http://127.0.0.1:8080/regular_expression_aaaaaaaa", 200, "Hello World")
123 assertGet(t, "http://127.0.0.1:8080/regular_expression_aaAaaaAa", 200, "Hello World")
124 assertGet(t, "http://127.0.0.1:8080/regular_expression_aaAaaaAab", 404, "")
125
126 assertGetHeader(t, "http://127.0.0.1:8080", "exact", "foo", 200, "Hello World")
127 assertGetHeader(t, "http://127.0.0.1:8080", "exact", "bar", 404, "")
128 assertGetHeader(t, "http://127.0.0.1:8080", "regular_expression", "foo", 200, "Hello World")
129 assertGetHeader(t, "http://127.0.0.1:8080", "regular_expression", "foo_aaaaAaaaa", 200, "Hello World")
130 assertGetHeader(t, "http://127.0.0.1:8080", "regular_expression", "foo_aaaaAaaaab", 404, "")
131 assertGetHeader(t, "http://127.0.0.1:8080", "regular_expression", "bar", 404, "")
132 }
133
134 func TestBadMatchTypes(t *testing.T) {
135 t.Parallel()
136 d := makeDispatcher(t)
137
138
139
140 err := d.UpsertYaml(`
141 ---
142 kind: HTTPRoute
143 apiVersion: networking.x-k8s.io/v1alpha1
144 metadata:
145 name: my-route
146 namespace: default
147 spec:
148 rules:
149 - matches:
150 - path:
151 type: Blah
152 value: /exact
153 forwardTo:
154 - serviceName: foo-backend-1
155 port: 9000
156 weight: 100
157 `)
158 assertErrorContains(t, err, `processing HTTPRoute:default:my-route: unknown path match type: "Blah"`)
159
160 err = d.UpsertYaml(`
161 ---
162 kind: HTTPRoute
163 apiVersion: networking.x-k8s.io/v1alpha1
164 metadata:
165 name: my-route
166 namespace: default
167 spec:
168 rules:
169 - matches:
170 - headers:
171 type: Bleh
172 values:
173 exact: foo
174 forwardTo:
175 - serviceName: foo-backend-1
176 weight: 100
177 `)
178 assertErrorContains(t, err, `processing HTTPRoute:default:my-route: unknown header match type: Bleh`)
179 }
180
181 func makeDispatcher(t *testing.T) *gateway.Dispatcher {
182 d := gateway.NewDispatcher()
183 err := d.Register("Gateway", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
184 return gateway.Compile_Gateway(untyped.(*gw.Gateway))
185 })
186 require.NoError(t, err)
187 err = d.Register("HTTPRoute", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
188 return gateway.Compile_HTTPRoute(untyped.(*gw.HTTPRoute))
189 })
190 require.NoError(t, err)
191 err = d.Register("Endpoints", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
192 return gateway.Compile_Endpoints(untyped.(*kates.Endpoints))
193 })
194 require.NoError(t, err)
195 return d
196 }
197
198 func makeEndpoint(namespace, name, ip string, port int) *kates.Endpoints {
199 ports := []kates.EndpointPort{{Port: int32(port)}}
200 addrs := []kates.EndpointAddress{{IP: ip}}
201
202 return &kates.Endpoints{
203 TypeMeta: kates.TypeMeta{Kind: "Endpoints"},
204 ObjectMeta: kates.ObjectMeta{Namespace: namespace, Name: name},
205 Subsets: []kates.EndpointSubset{{Addresses: addrs, Ports: ports}},
206 }
207 }
208
209 func checkReady(t *testing.T, url string) {
210 delay := 10 * time.Millisecond
211 for {
212 if delay > 10*time.Second {
213 require.Fail(t, "url never became ready", url)
214 }
215 _, err := http.Get(url)
216 if err != nil {
217 t.Logf("error %v, retrying...", err)
218 time.Sleep(delay)
219 delay = delay * 2
220 }
221 return
222 }
223 }
224
225 func assertGet(t *testing.T, url string, code int, expected string) {
226 resp, err := http.Get(url)
227 require.NoError(t, err)
228 require.Equal(t, code, resp.StatusCode)
229 actual, err := ioutil.ReadAll(resp.Body)
230 require.NoError(t, err)
231 assert.Equal(t, expected, string(actual))
232 }
233
234 func assertGetHeader(t *testing.T, url, header, value string, code int, expected string) {
235 req, err := http.NewRequest(http.MethodGet, url, nil)
236 require.NoError(t, err)
237 req.Header.Set(header, value)
238 resp, err := http.DefaultClient.Do(req)
239 require.NoError(t, err)
240 require.Equal(t, code, resp.StatusCode)
241 actual, err := ioutil.ReadAll(resp.Body)
242 require.NoError(t, err)
243 assert.Equal(t, expected, string(actual))
244 }
245
View as plain text