...
1import re
2from datetime import datetime
3from typing import Generator, Tuple, Union
4
5from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
6from kat.harness import Query
7
8
9class RetryPolicyTest(AmbassadorTest):
10 target: ServiceType
11
12 def init(self) -> None:
13 self.target = HTTP()
14
15 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
16 yield self, self.format(
17 """
18---
19apiVersion: getambassador.io/v3alpha1
20kind: Mapping
21name: {self.name}-normal
22hostname: "*"
23prefix: /{self.name}-normal/
24service: {self.target.path.fqdn}
25timeout_ms: 3000
26"""
27 )
28
29 yield self, self.format(
30 """
31---
32apiVersion: getambassador.io/v3alpha1
33kind: Mapping
34name: {self.name}-target
35hostname: "*"
36prefix: /{self.name}-retry/
37service: {self.target.path.fqdn}
38timeout_ms: 3000
39retry_policy:
40 retry_on: "5xx"
41 num_retries: 4
42"""
43 )
44
45 yield self, self.format(
46 """
47---
48apiVersion: getambassador.io/v3alpha1
49kind: Module
50name: ambassador
51config:
52 retry_policy:
53 retry_on: "retriable-4xx"
54 num_retries: 4
55"""
56 )
57
58 def queries(self):
59 yield Query(
60 self.url(self.name + "-normal/"), headers={"Requested-Backend-Delay": "0"}, expected=200
61 )
62 yield Query(
63 self.url(self.name + "-normal/"), headers={"Requested-Status": "500"}, expected=500
64 )
65 yield Query(
66 self.url(self.name + "-retry/"),
67 headers={"Requested-Status": "500", "Requested-Backend-Delay": "2000"},
68 expected=504,
69 )
70 yield Query(
71 self.url(self.name + "-normal/"),
72 headers={"Requested-Status": "409", "Requested-Backend-Delay": "2000"},
73 expected=504,
74 )
75
76 def get_timestamp(self, hdr):
77 m = re.match(r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6})", hdr)
78
79 if m:
80 return datetime.strptime(m.group(1), "%Y-%m-%dT%H:%M:%S.%f").timestamp()
81 else:
82 assert False, f'header timestamp "{hdr}" is not parseable'
83 return None
84
85 def get_duration(self, result):
86 start_time = self.get_timestamp(result.headers["Client-Start-Date"][0])
87 end_time = self.get_timestamp(result.headers["Client-End-Date"][0])
88
89 return end_time - start_time
90
91 def check(self):
92 ok_result = self.results[0]
93 normal_result = self.results[1]
94 retry_result = self.results[2]
95 conflict_result = self.results[3]
96
97 ok_duration = self.get_duration(ok_result)
98 normal_duration = self.get_duration(normal_result)
99 retry_duration = self.get_duration(retry_result)
100 conflict_duration = self.get_duration(conflict_result)
101
102 assert retry_duration >= 2, f"retry time {retry_duration} must be at least 2 seconds"
103 assert (
104 conflict_duration >= 2
105 ), f"conflict time {conflict_duration} must be at least 2 seconds"
106
107 ok_vs_normal = abs(ok_duration - normal_duration)
108
109 assert (
110 ok_vs_normal <= 1
111 ), f"time to 200 OK {ok_duration} is more than 1 second different from time to 500 {normal_duration}"
112
113 retry_vs_normal = retry_duration - normal_duration
114
115 assert (
116 retry_vs_normal >= 2
117 ), f"retry time {retry_duration} is not at least 2 seconds slower than normal time {normal_duration}"
118
119 conflict_vs_ok = conflict_duration - ok_duration
120
121 assert (
122 conflict_vs_ok >= 2
123 ), f"conflict time {conflict_duration} is not at least 2 seconds slower than ok time {ok_duration}"
View as plain text