...
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/"),
61 headers={"Kat-Req-Http-Requested-Backend-Delay": "0"},
62 expected=200,
63 )
64 yield Query(
65 self.url(self.name + "-normal/"),
66 headers={"Kat-Req-Http-Requested-Status": "500"},
67 expected=500,
68 )
69 yield Query(
70 self.url(self.name + "-retry/"),
71 headers={
72 "Kat-Req-Http-Requested-Status": "500",
73 "Kat-Req-Http-Requested-Backend-Delay": "2000",
74 },
75 expected=504,
76 )
77 yield Query(
78 self.url(self.name + "-normal/"),
79 headers={
80 "Kat-Req-Http-Requested-Status": "409",
81 "Kat-Req-Http-Requested-Backend-Delay": "2000",
82 },
83 expected=504,
84 )
85
86 def get_timestamp(self, hdr):
87 m = re.match(r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6})", hdr)
88
89 if m:
90 return datetime.strptime(m.group(1), "%Y-%m-%dT%H:%M:%S.%f").timestamp()
91 else:
92 assert False, f'header timestamp "{hdr}" is not parseable'
93 return None
94
95 def get_duration(self, result):
96 start_time = self.get_timestamp(result.headers["Client-Start-Date"][0])
97 end_time = self.get_timestamp(result.headers["Client-End-Date"][0])
98
99 return end_time - start_time
100
101 def check(self):
102 ok_result = self.results[0]
103 normal_result = self.results[1]
104 retry_result = self.results[2]
105 conflict_result = self.results[3]
106
107 ok_duration = self.get_duration(ok_result)
108 normal_duration = self.get_duration(normal_result)
109 retry_duration = self.get_duration(retry_result)
110 conflict_duration = self.get_duration(conflict_result)
111
112 assert retry_duration >= 2, f"retry time {retry_duration} must be at least 2 seconds"
113 assert (
114 conflict_duration >= 2
115 ), f"conflict time {conflict_duration} must be at least 2 seconds"
116
117 ok_vs_normal = abs(ok_duration - normal_duration)
118
119 assert (
120 ok_vs_normal <= 1
121 ), f"time to 200 OK {ok_duration} is more than 1 second different from time to 500 {normal_duration}"
122
123 retry_vs_normal = retry_duration - normal_duration
124
125 assert (
126 retry_vs_normal >= 2
127 ), f"retry time {retry_duration} is not at least 2 seconds slower than normal time {normal_duration}"
128
129 conflict_vs_ok = conflict_duration - ok_duration
130
131 assert (
132 conflict_vs_ok >= 2
133 ), f"conflict time {conflict_duration} is not at least 2 seconds slower than ok time {ok_duration}"
View as plain text