1
1
"""Scripts to poll http endpoint."""
2
2
3
- import logging
4
3
import re
5
4
import subprocess
6
5
import sys
13
12
14
13
_ENDPOINT = flags .DEFINE_string ('endpoint' , None , 'Endpoint to use.' )
15
14
_MAX_RETRIES = flags .DEFINE_integer ('max_retries' , 0 , 'Number of retries.' )
16
- _RETRY_INTERVAL = flags .DEFINE_float ('retry_interval' , 0.5 ,
17
- 'Gap in seconds between retries.' )
15
+ _RETRY_INTERVAL = flags .DEFINE_float (
16
+ 'retry_interval' , 0.5 , 'Gap in seconds between retries.'
17
+ )
18
18
_TIMEOUT = flags .DEFINE_integer ('timeout' , 600 , 'Polling deadline in seconds.' )
19
- _EXPECTED_RESPONSE = flags .DEFINE_string ('expected_response' , '' ,
20
- 'Expected response.' )
19
+ _EXPECTED_RESPONSE = flags .DEFINE_string (
20
+ 'expected_response' , '' , 'Expected response.'
21
+ )
21
22
_EXPECTED_RESPONSE_CODE = flags .DEFINE_string (
22
- 'expected_response_code' , r'2\d\d' ,
23
+ 'expected_response_code' ,
24
+ r'2\d\d' ,
23
25
'Expected http response code. Can be either a regexp or '
24
26
'a integer in string format. '
25
- 'By default, any 2xx response code is valid.' )
27
+ 'By default, any 2xx response code is valid.' ,
28
+ )
26
29
_HEADERS = flags .DEFINE_list ('headers' , [], 'Headers applying to requests.' )
27
30
_HTTP_RESPCODE_REGEXP = r'HTTPS?/[\d\.]*\s+(\d+)'
28
31
_CURL_METRICS = '<curl_time_total:%{time_total}>'
33
36
FAILURE_CURL = 'curl_failure'
34
37
FAILURE_INVALID_RESPONSE = 'invalid_response'
35
38
FAILURE_INVALID_RESPONSE_CODE = 'invalid_response_code'
36
- POLL_STATUS = (POLL_SUCCEED , FAILURE_CURL ,
37
- FAILURE_INVALID_RESPONSE , FAILURE_INVALID_RESPONSE_CODE )
39
+ POLL_STATUS = (
40
+ POLL_SUCCEED ,
41
+ FAILURE_CURL ,
42
+ FAILURE_INVALID_RESPONSE ,
43
+ FAILURE_INVALID_RESPONSE_CODE ,
44
+ )
38
45
39
46
40
47
def _generate_file_names ():
@@ -53,77 +60,89 @@ def _popen(curl_cmd, stdout, stderr, shell):
53
60
return subprocess .Popen (curl_cmd , stdout = stdout , stderr = stderr , shell = shell )
54
61
55
62
56
- def call_curl (endpoint , headers = None , expected_response_code = None ,
57
- expected_response = None ):
63
+ def call_curl (
64
+ endpoint , headers = None , expected_response_code = None , expected_response = None
65
+ ):
58
66
"""Curl endpoint once and return response.
59
67
60
68
Args:
61
69
endpoint: string. HTTP(S) endpoint to curl.
62
70
headers: list. A list of strings, each representing a header.
63
- expected_response_code: string. Expected http response code. Can be either
64
- a string or a regex.
65
- expected_response: string. Expected http response. Can be either a string
66
- or a regex.
71
+ expected_response_code: string. Expected http response code. Can be either a
72
+ string or a regex.
73
+ expected_response: string. Expected http response. Can be either a string or
74
+ a regex.
67
75
68
76
Returns:
69
- A tuple of float, string, integer and string:
77
+ A tuple of float, string, string, and string:
70
78
Float indicates latency in second of the polling attempt.
71
- String indicates whether the HTTP endpoint is reachable and respond
79
+ First string indicates whether the HTTP endpoint is reachable and respond
72
80
with expected responses or the reason of failure;
73
81
List of current values are:
74
82
[succeed, curl_failure, invalid_response_code, invalid_response]
75
83
76
- Integer indicates the actual HTTP response code;
77
- String indicates the actual HTTP response .
84
+ Second string is the actual HTTP response.
85
+ Third string is additional logged info .
78
86
"""
79
87
poll_stats = POLL_SUCCEED
80
88
outid , errid = _generate_file_names ()
81
89
header = ''
90
+ logs = ''
82
91
if headers :
83
92
header = '-H %s ' % ' ' .join (headers )
84
93
85
94
curl_cmd = 'curl -w "{metrics}" -v {header}"{endpoint}"' .format (
86
- metrics = _CURL_METRICS ,
87
- header = header ,
88
- endpoint = endpoint )
95
+ metrics = _CURL_METRICS , header = header , endpoint = endpoint
96
+ )
89
97
with open (outid , 'w+' ) as stdout :
90
98
with open (errid , 'w+' ) as stderr :
91
- p = _popen (
92
- curl_cmd , stdout = stdout , stderr = stderr , shell = True )
99
+ p = _popen (curl_cmd , stdout = stdout , stderr = stderr , shell = True )
93
100
retcode = p .wait ()
94
101
http_resp_code = None
95
102
with open (errid , 'r' ) as stderr :
96
103
http_stderr = stderr .read ()
97
104
for line in http_stderr .split ('\n ' ):
98
105
match = re .search (_HTTP_RESPCODE_REGEXP , line )
99
- if match :
106
+ if expected_response_code and match :
100
107
http_resp_code = match .group (1 )
101
108
if re .search (expected_response_code , http_resp_code ) is None :
102
109
poll_stats = FAILURE_INVALID_RESPONSE_CODE
103
110
if retcode :
104
111
poll_stats = FAILURE_CURL
105
112
http_resp_code = '-1'
106
- logging .info (
107
- 'Error: received non-zero return code running %s, stderr: %s' ,
108
- curl_cmd , http_stderr )
113
+ logs += (
114
+ f'Error: received non-zero return code running { curl_cmd } , '
115
+ f'stderr: { http_stderr } ;'
116
+ )
109
117
latency = 0
110
118
with open (outid , 'r' ) as stdout :
111
119
http_stdout = stdout .read ()
112
120
if expected_response and (
113
- re .search (expected_response , http_stdout ) is None ):
121
+ re .search (expected_response , http_stdout ) is None
122
+ ):
114
123
poll_stats = FAILURE_INVALID_RESPONSE
115
- else :
116
- match = re .search (_CURL_METRICS_REGEXP , http_stdout )
117
- if match :
118
- latency = float (match .group (1 ))
119
- # Remove <curl_time_total:##> from the response.
120
- http_stdout = http_stdout .split (match .group (0 ))[0 ]
121
-
122
- return latency or - 1 , poll_stats , int (http_resp_code ), http_stdout
123
-
124
-
125
- def poll (endpoint , headers = None , max_retries = 0 , retry_interval = 0.5 ,
126
- timeout = 600 , expected_response_code = None , expected_response = None ):
124
+ match = re .search (_CURL_METRICS_REGEXP , http_stdout )
125
+ if match :
126
+ latency = float (match .group (1 ))
127
+ # Remove <curl_time_total:##> from the response.
128
+ http_stdout = http_stdout .split (match .group (0 ))[0 ]
129
+
130
+ logs += (
131
+ f'call_curl values - latency: { latency } , pass/fail: { poll_stats } , '
132
+ f'response code: { http_resp_code } , stdout: { http_stdout } ;'
133
+ )
134
+ return latency or - 1 , poll_stats , http_stdout , logs
135
+
136
+
137
+ def poll (
138
+ endpoint ,
139
+ headers = None ,
140
+ max_retries = 0 ,
141
+ retry_interval = 0.5 ,
142
+ timeout = 600 ,
143
+ expected_response_code = None ,
144
+ expected_response = None ,
145
+ ):
127
146
"""Poll HTTP endpoint until expected response code is received or time out.
128
147
129
148
Args:
@@ -132,54 +151,62 @@ def poll(endpoint, headers=None, max_retries=0, retry_interval=0.5,
132
151
max_retries: int. Indicates how many retires before give up.
133
152
retry_interval: float. Indicates interval in sec between retries.
134
153
timeout: int. Deadline before giving up.
135
- expected_response_code: string. Expected http response code. Can be either
136
- a string or a regex.
137
- expected_response: string. Expected http response. Can be either a string
138
- or a regex.
154
+ expected_response_code: string. Expected http response code. Can be either a
155
+ string or a regex.
156
+ expected_response: string. Expected http response. Can be either a string or
157
+ a regex.
139
158
140
159
Returns:
141
- A tuple of float, float, string, string:
142
- First float indicates total time spent in seconds polling before
143
- giving up or succeed.
144
- Second float indicates latency for successful polls. If polling failed,
160
+ A tuple of float, string, string, and string:
161
+ Float indicates latency for successful polls. If polling failed,
145
162
latency is default to -1.
146
- String indicates if HTTP(S) endpoint is reachable and responds with
163
+ First string indicates if HTTP(S) endpoint is reachable and responds with
147
164
expected response and code, or reasons of failure.
148
- String represents actual response.
165
+ Second string represents actual response.
166
+ Third string has additional debugging info worth logging.
149
167
"""
150
168
start = _get_time ()
151
169
end = start
152
170
latency = 0
153
171
attempt = 0
154
172
poll_stats = None
173
+ resp = ''
174
+ logs = ''
155
175
while end - start < timeout and attempt <= max_retries :
156
- attempt_latency , poll_stats , _ , resp = call_curl (endpoint , headers ,
157
- expected_response_code ,
158
- expected_response )
176
+ attempt_latency , poll_stats , resp , logs = call_curl (
177
+ endpoint , headers , expected_response_code , expected_response
178
+ )
159
179
if poll_stats != POLL_SUCCEED :
160
180
time .sleep (retry_interval )
161
181
end = _get_time ()
162
182
attempt += 1
163
183
else :
164
184
latency = attempt_latency
165
185
break
166
- return _get_time () - start - latency , latency or - 1 , poll_stats , resp
186
+ logs += f'Time before request started: { _get_time () - start - latency } ;'
187
+ return latency or - 1 , poll_stats , resp , logs
167
188
168
189
169
190
def main ():
170
191
if not _ENDPOINT .value :
171
- logging .info ('Invalid arguments, no endpoint provided.' )
192
+ print (
193
+ - 1 , 'INVALID_ARGUMENTS' , '' , 'Invalid arguments, no endpoint provided.'
194
+ )
172
195
return 1
173
196
print (
174
- poll (_ENDPOINT .value , _HEADERS .value , _MAX_RETRIES .value ,
175
- _RETRY_INTERVAL .value , _TIMEOUT .value , _EXPECTED_RESPONSE_CODE .value ,
176
- _EXPECTED_RESPONSE .value ))
197
+ poll (
198
+ _ENDPOINT .value ,
199
+ _HEADERS .value ,
200
+ _MAX_RETRIES .value ,
201
+ _RETRY_INTERVAL .value ,
202
+ _TIMEOUT .value ,
203
+ _EXPECTED_RESPONSE_CODE .value ,
204
+ _EXPECTED_RESPONSE .value ,
205
+ )
206
+ )
177
207
return 0
178
208
179
209
180
210
if __name__ == '__main__' :
181
211
FLAGS (sys .argv )
182
- logging .basicConfig (
183
- level = logging .INFO ,
184
- format = '%(asctime)-15s %(message)s' )
185
212
sys .exit (main ())
0 commit comments