1import re
2from typing import Generator, Tuple, Union
3
4import yaml
5
6from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
7from kat.harness import EDGE_STACK, Query
8from tests.integration.manifests import namespace_manifest
9
10
11class Empty(AmbassadorTest):
12 single_namespace = True
13 namespace = "empty-namespace"
14 extra_ports = [8877]
15
16 def init(self):
17 if EDGE_STACK:
18 self.xfail = "XFailing for now"
19 # Specify a non-default readiness port for test coverage purposes.
20 # All other tests will use the default 8006 port.
21 self.manifest_envs += """
22 - name: AMBASSADOR_READY_PORT
23 value: "8500"
24"""
25
26 @classmethod
27 def variants(cls) -> Generator[Node, None, None]:
28 yield cls()
29
30 def manifests(self) -> str:
31 return namespace_manifest("empty-namespace") + super().manifests()
32
33 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
34 yield from ()
35
36 def queries(self):
37 yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"), phase=2)
38 yield Query(self.url("_internal/v0/ping", scheme="http", port=8877), expected=403)
39 yield Query(self.url("ambassador/v0/check_ready", scheme="http", port=8877))
40
41 def check(self):
42 # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
43 errors = self.results[0].json or []
44
45 # We shouldn't have any missing-CRD-types errors any more.
46 for source, error in errors:
47 if ("could not find" in error) and ("CRD definitions" in error):
48 assert False, f"Missing CRDs: {error}"
49
50 if "Ingress resources" in error:
51 assert False, f"Ingress resource error: {error}"
52
53 # The default errors assume that we have missing CRDs, and that's not correct any more,
54 # so don't try to use assert_default_errors here.
55
56
57class AmbassadorIDTest(AmbassadorTest):
58
59 target: ServiceType
60
61 def init(self):
62 self.target = HTTP()
63
64 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
65 yield self, """
66---
67apiVersion: getambassador.io/v3alpha1
68kind: Module
69name: ambassador
70config:
71 use_ambassador_namespace_for_service_resolution: true
72"""
73 for prefix, amb_id in (
74 ("findme", "[{self.ambassador_id}]"),
75 ("findme-array", "[{self.ambassador_id}, missme]"),
76 ("findme-array2", "[missme, {self.ambassador_id}]"),
77 ("missme", "[missme]"),
78 ("missme-array", "[missme1, missme2]"),
79 ):
80 yield self.target, self.format(
81 """
82---
83apiVersion: getambassador.io/v3alpha1
84kind: Mapping
85name: {self.path.k8s}-{prefix}
86hostname: "*"
87prefix: /{prefix}/
88service: {self.target.path.fqdn}
89ambassador_id: {amb_id}
90 """,
91 prefix=self.format(prefix),
92 amb_id=self.format(amb_id),
93 )
94
95 def queries(self):
96 yield Query(self.url("findme/"))
97 yield Query(self.url("findme-array/"))
98 yield Query(self.url("findme-array2/"))
99 yield Query(self.url("missme/"), expected=404)
100 yield Query(self.url("missme-array/"), expected=404)
101
102
103class InvalidResources(AmbassadorTest):
104 target: ServiceType
105
106 def init(self):
107 self.target = HTTP()
108 self.resource_names = []
109
110 self.models = [
111 """
112apiVersion: getambassador.io/v3alpha1
113kind: AuthService
114metadata:
115 name: {self.path.k8s}-as-bad1-<<WHICH>>
116spec:
117 ambassador_id: ["{self.ambassador_id}"]
118 service_bad: {self.target.path.fqdn}
119""",
120 """
121apiVersion: getambassador.io/v3alpha1
122kind: Mapping
123metadata:
124 name: {self.path.k8s}-m-good-<<WHICH>>
125spec:
126 ambassador_id: ["{self.ambassador_id}"]
127 hostname: "*"
128 prefix: /good-<<WHICH>>/
129 service: {self.target.path.fqdn}
130""",
131 """
132apiVersion: getambassador.io/v3alpha1
133kind: Mapping
134metadata:
135 name: {self.path.k8s}-m-bad-<<WHICH>>
136spec:
137 ambassador_id: ["{self.ambassador_id}"]
138 hostname: "*"
139 prefix_bad: /bad-<<WHICH>>/
140 service: {self.target.path.fqdn}
141""",
142 """
143kind: Mapping
144metadata:
145 name: {self.path.k8s}-m-bad-no-apiversion-<<WHICH>>
146spec:
147 ambassador_id: ["{self.ambassador_id}"]
148 hostname: "*"
149 prefix_bad: /bad-<<WHICH>>/
150 service: {self.target.path.fqdn}
151""",
152 """
153apiVersion: getambassador.io/v3alpha1
154kind: Module
155metadata:
156 name: {self.path.k8s}-md-bad-<<WHICH>>
157spec:
158 ambassador_id: ["{self.ambassador_id}"]
159 config_bad: []
160""",
161 """
162apiVersion: getambassador.io/v3alpha1
163kind: RateLimitService
164metadata:
165 name: {self.path.k8s}-r-bad-<<WHICH>>
166spec:
167 ambassador_id: ["{self.ambassador_id}"]
168 service_bad: {self.target.path.fqdn}
169""",
170 """
171apiVersion: getambassador.io/v3alpha1
172kind: TCPMapping
173metadata:
174 name: {self.path.k8s}-tm-bad1-<<WHICH>>
175spec:
176 ambassador_id: ["{self.ambassador_id}"]
177 service: {self.target.path.fqdn}
178 port_bad: 8888
179""",
180 """
181apiVersion: getambassador.io/v3alpha1
182kind: TCPMapping
183metadata:
184 name: {self.path.k8s}-tm-bad2-<<WHICH>>
185spec:
186 ambassador_id: ["{self.ambassador_id}"]
187 service_bad: {self.target.path.fqdn}
188 port: 8888
189""",
190 """
191apiVersion: getambassador.io/v3alpha1
192kind: TracingService
193metadata:
194 name: {self.path.k8s}-ts-bad1-<<WHICH>>
195spec:
196 ambassador_id: ["{self.ambassador_id}"]
197 driver_bad: zipkin
198 service: {self.target.path.fqdn}
199""",
200 """
201apiVersion: getambassador.io/v3alpha1
202kind: TracingService
203metadata:
204 name: {self.path.k8s}-ts-bad2-<<WHICH>>
205spec:
206 ambassador_id: ["{self.ambassador_id}"]
207 driver: zipkin
208 service_bad: {self.target.path.fqdn}
209""",
210 ]
211
212 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
213 counter = 0
214
215 for m_yaml in self.models:
216 counter += 1
217 m = yaml.safe_load(self.format(m_yaml.replace("<<WHICH>>", "annotation")))
218
219 for k in m["metadata"].keys():
220 m[k] = m["metadata"][k]
221 del m["metadata"]
222
223 for k in m["spec"].keys():
224 if k == "ambassador_id":
225 continue
226
227 m[k] = m["spec"][k]
228 del m["spec"]
229
230 if "good" not in m["name"]:
231 # These all show up as "invalidresources.default.N" because they're
232 # annotations.
233 self.resource_names.append(f"invalidresources.default.{counter}")
234
235 yield self, yaml.dump(m)
236
237 def manifests(self):
238 manifests = []
239
240 for m in self.models:
241 m_yaml = self.format(m.replace("<<WHICH>>", "crd"))
242 m_obj = yaml.safe_load(m_yaml)
243 if "apiVersion" not in m_obj:
244 continue
245
246 manifests.append("---")
247 manifests.append(m_yaml)
248
249 if "good" not in m_obj["metadata"]["name"]:
250 self.resource_names.append(m_obj["metadata"]["name"] + ".default.1")
251
252 return super().manifests() + "\n".join(manifests)
253
254 def queries(self):
255 yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"))
256
257 yield Query(self.url("good-annotation/"), expected=200)
258 yield Query(self.url("bad-annotation/"), expected=404)
259 yield Query(self.url("good-crd/"), expected=200)
260 yield Query(self.url("bad-crd/"), expected=404)
261
262 def check(self):
263 # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
264 errors = self.results[0].json or []
265
266 assert errors, "Invalid resources must generate errors, but we didn't get any"
267
268 error_dict = {}
269
270 for resource, error in errors:
271 error_dict[resource] = error
272
273 for name in self.resource_names:
274 assert name in error_dict, f"no error found for {name}"
275
276 error = error_dict[name]
277
278 # Check that the error is one of the errors that we expect.
279 expected_errors = [
280 re.compile(r"^.* in body is required$"),
281 re.compile(r"^apiVersion None/ unsupported$"),
282 re.compile(r'^spec\.config in body must be of type object: "null"$'),
283 ]
284 assert any(
285 pat.match(error) for pat in expected_errors
286 ), f"error for {name} should match one of the expected errors: {repr(error)}"
287
288
289class ServerNameTest(AmbassadorTest):
290
291 target: ServiceType
292
293 def init(self):
294 self.target = HTTP()
295
296 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
297 yield self, self.format(
298 """
299---
300apiVersion: getambassador.io/v3alpha1
301kind: Module
302name: ambassador
303config:
304 server_name: "test-server"
305---
306apiVersion: getambassador.io/v3alpha1
307kind: Mapping
308name: {self.path.k8s}/server-name
309hostname: "*"
310prefix: /server-name
311service: {self.target.path.fqdn}
312"""
313 )
314
315 def queries(self):
316 yield Query(self.url("server-name/"), expected=301)
317
318 def check(self):
319 assert self.results[0].headers["Server"] == ["test-server"]
320
321
322class SafeRegexMapping(AmbassadorTest):
323
324 target: ServiceType
325
326 def init(self):
327 self.target = HTTP()
328
329 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
330 yield self, self.format(
331 """
332---
333apiVersion: getambassador.io/v3alpha1
334kind: Mapping
335name: {self.name}
336prefix: /{self.name}/
337prefix_regex: true
338host: "[a-zA-Z].*"
339host_regex: true
340regex_headers:
341 X-Foo: "^[a-z].*"
342service: http://{self.target.path.fqdn}
343"""
344 )
345
346 def queries(self):
347 yield Query(self.url(self.name + "/"), headers={"X-Foo": "hello"})
348 yield Query(self.url(f"need-normalization/../{self.name}/"), headers={"X-Foo": "hello"})
349 yield Query(self.url(self.name + "/"), expected=404)
350 yield Query(self.url(f"need-normalization/../{self.name}/"), expected=404)
351
352 def check(self):
353 for r in self.results:
354 if r.backend:
355 assert r.backend.name == self.target.path.k8s, (
356 r.backend.name,
357 self.target.path.k8s,
358 )
359 assert r.backend.request
360 assert r.backend.request.headers["x-envoy-original-path"][0] == f"/{self.name}/"
View as plain text