14
14
from dataclasses import dataclass
15
15
from functools import cache
16
16
from io import BytesIO
17
+ from urllib .parse import urlparse
17
18
18
19
import boto3
19
20
import botocore .config
29
30
retries = {"max_attempts" : 0 }, read_timeout = 60 * 15
30
31
)
31
32
session = boto3 .Session (region_name = region )
33
+ mock_sales_index_id = "mock-sales"
34
+ hdfs_logs_index_id = "hdfs-logs"
32
35
33
36
34
37
@cache
@@ -39,19 +42,27 @@ def _get_cloudformation_output_value(stack_name: str, export_name: str) -> str:
39
42
print (f"Stack { stack_name } not identified uniquely, found { stacks } " )
40
43
outputs = stacks [0 ]["Outputs" ]
41
44
for output in outputs :
42
- if output ["ExportName" ] == export_name :
45
+ if "ExportName" in output and output ["ExportName" ] == export_name :
43
46
return output ["OutputValue" ]
44
47
else :
45
48
print (f"Export name { export_name } not found in stack { stack_name } " )
46
49
exit (1 )
47
50
48
51
52
+ def _decompress_if_gzip (payload : bytes , headers : dict ) -> str :
53
+ if headers .get ("content-encoding" , "" ) == "gzip" :
54
+ return gzip .GzipFile (mode = "rb" , fileobj = BytesIO (payload )).read ().decode ()
55
+ else :
56
+ return payload .decode ()
57
+
58
+
49
59
@dataclass
50
60
class LambdaResult :
51
61
function_error : str
52
62
log_tail : str
53
63
payload : str
54
64
raw_size_bytes : int
65
+ status_code : int
55
66
56
67
@staticmethod
57
68
def from_lambda_response (lambda_resp : dict ) -> "LambdaResult" :
@@ -61,28 +72,28 @@ def from_lambda_response(lambda_resp: dict) -> "LambdaResult":
61
72
log_tail = base64 .b64decode (lambda_resp ["LogResult" ]).decode (),
62
73
payload = payload ,
63
74
raw_size_bytes = len (payload ),
75
+ status_code = 0 ,
64
76
)
65
77
66
78
@staticmethod
67
79
def from_lambda_gateway_response (lambda_resp : dict ) -> "LambdaResult" :
68
80
gw_str = lambda_resp ["Payload" ].read ().decode ()
69
81
gw_obj = json .loads (gw_str )
70
- payload = gw_obj ["body" ]
71
- if gw_obj ["isBase64Encoded" ]:
82
+ if "body" in gw_obj :
83
+ payload = gw_obj ["body" ]
84
+ status_code = gw_obj ["statusCode" ]
85
+ else :
86
+ payload = gw_str
87
+ status_code = - 1
88
+ if gw_obj .get ("isBase64Encoded" , False ):
72
89
dec_payload = base64 .b64decode (payload )
73
- if gw_obj .get ("headers" , {}).get ("content-encoding" , "" ) == "gzip" :
74
- payload = (
75
- gzip .GzipFile (mode = "rb" , fileobj = BytesIO (dec_payload ))
76
- .read ()
77
- .decode ()
78
- )
79
- else :
80
- payload = dec_payload .decode ()
90
+ payload = _decompress_if_gzip (dec_payload , gw_obj .get ("headers" , {}))
81
91
return LambdaResult (
82
92
function_error = lambda_resp .get ("FunctionError" , "" ),
83
93
log_tail = base64 .b64decode (lambda_resp ["LogResult" ]).decode (),
84
94
payload = payload ,
85
95
raw_size_bytes = len (gw_str ),
96
+ status_code = status_code ,
86
97
)
87
98
88
99
def extract_report (self ) -> str :
@@ -108,12 +119,13 @@ def _format_lambda_output(
108
119
if lambda_result .function_error != "" :
109
120
print ("\n ## FUNCTION ERROR:" )
110
121
print (lambda_result .function_error )
111
- print ("\n ## LOG TAIL:" )
112
- print (lambda_result .log_tail )
113
122
print ("\n ## RAW RESPONSE SIZE (BYTES):" )
114
- ratio = lambda_result .raw_size_bytes / len (lambda_result .payload )
115
- print (f"{ lambda_result .raw_size_bytes } ({ ratio :.1f} x the final payload)" )
116
- print ("\n ## RESPONSE:" )
123
+ if len (lambda_result .payload ) == 0 :
124
+ ratio = "empty payload"
125
+ else :
126
+ ratio = f"{ (lambda_result .raw_size_bytes / len (lambda_result .payload )):.1f} x the final payload"
127
+ print (f"{ lambda_result .raw_size_bytes } ({ ratio } )" )
128
+ print (f"\n ## RESPONSE [{ lambda_result .status_code } ]:" )
117
129
payload_size = len (lambda_result .payload )
118
130
print (lambda_result .payload [:max_resp_size ])
119
131
if payload_size > max_resp_size :
@@ -184,6 +196,7 @@ def invoke_hdfs_indexer() -> LambdaResult:
184
196
185
197
def _invoke_searcher (
186
198
stack_name : str ,
199
+ index_id : str ,
187
200
function_export_name : str ,
188
201
payload : str ,
189
202
download_logs : bool ,
@@ -198,9 +211,14 @@ def _invoke_searcher(
198
211
LogType = "Tail" ,
199
212
Payload = json .dumps (
200
213
{
201
- "headers" : {"Content-Type" : "application/json" },
214
+ "resource" : f"/api/v1/{ index_id } /search" ,
215
+ "path" : f"/api/v1/{ index_id } /search" ,
216
+ "httpMethod" : "POST" ,
217
+ "headers" : {
218
+ "Content-Type" : "application/json" ,
219
+ },
202
220
"requestContext" : {
203
- "http " : { "method" : " POST"} ,
221
+ "httpMethod " : " POST" ,
204
222
},
205
223
"body" : payload ,
206
224
"isBase64Encoded" : False ,
@@ -218,6 +236,7 @@ def _invoke_searcher(
218
236
def invoke_hdfs_searcher (payload : str , download_logs : bool = True ) -> LambdaResult :
219
237
return _invoke_searcher (
220
238
app .HDFS_STACK_NAME ,
239
+ hdfs_logs_index_id ,
221
240
hdfs_stack .SEARCHER_FUNCTION_NAME_EXPORT_NAME ,
222
241
payload ,
223
242
download_logs ,
@@ -249,7 +268,6 @@ def get_logs(
249
268
last_event_id = event ["eventId" ]
250
269
yield event ["message" ]
251
270
if event ["message" ].startswith ("REPORT" ):
252
- print (event ["message" ])
253
271
lower_time_bound = int (event ["timestamp" ])
254
272
last_event_id = "REPORT"
255
273
break
@@ -277,13 +295,15 @@ def download_logs_to_file(request_id: str, function_name: str, invoke_start: flo
277
295
int (invoke_start * 1000 ),
278
296
):
279
297
f .write (log )
298
+ print (f"Logs written to lambda.{ request_id } .log" )
280
299
except Exception as e :
281
300
print (f"Failed to download logs: { e } " )
282
301
283
302
284
303
def invoke_mock_data_searcher ():
285
304
_invoke_searcher (
286
305
app .MOCK_DATA_STACK_NAME ,
306
+ mock_sales_index_id ,
287
307
mock_data_stack .SEARCHER_FUNCTION_NAME_EXPORT_NAME ,
288
308
"""{"query": "id:1", "sort_by": "ts", "max_hits": 10}""" ,
289
309
True ,
@@ -321,7 +341,9 @@ def print_mock_data_metastore():
321
341
app .MOCK_DATA_STACK_NAME , mock_data_stack .INDEX_STORE_BUCKET_NAME_EXPORT_NAME
322
342
)
323
343
s3 = session .client ("s3" )
324
- response = s3 .get_object (Bucket = bucket_name , Key = "index/mock-sales/metastore.json" )
344
+ response = s3 .get_object (
345
+ Bucket = bucket_name , Key = f"index/{ mock_sales_index_id } /metastore.json"
346
+ )
325
347
print (response ["Body" ].read ().decode ())
326
348
327
349
@@ -387,3 +409,48 @@ def benchmark_hdfs_search(payload: str):
387
409
with open (f"lambda-bench.log" , "a+" ) as f :
388
410
f .write (json .dumps (bench_result ))
389
411
f .write ("\n " )
412
+
413
+
414
+ def test_mock_data_endpoints ():
415
+ apigw_url = _get_cloudformation_output_value (
416
+ app .MOCK_DATA_STACK_NAME , mock_data_stack .API_GATEWAY_EXPORT_NAME
417
+ )
418
+
419
+ def req (method , path , body = None , expected_status = 200 ):
420
+ conn = http .client .HTTPSConnection (urlparse (apigw_url ).netloc )
421
+ conn .request (
422
+ method ,
423
+ path ,
424
+ body ,
425
+ headers = {"x-api-key" : os .getenv ("SEARCHER_API_KEY" )},
426
+ )
427
+ response = conn .getresponse ()
428
+ print (f"{ method } { path } " )
429
+ headers = {k : v for (k , v ) in response .getheaders ()}
430
+ body = _decompress_if_gzip (response .read (), headers )
431
+ if response .status != expected_status :
432
+ print (f"[{ response .status } ] => { body } " )
433
+ exit (1 )
434
+ else :
435
+ print (f"[{ response .status } ] => { json .dumps (json .loads (body ))[0 :100 ]} " )
436
+
437
+ req ("GET" , f"/api/v1/{ mock_sales_index_id } /search?query=animal" )
438
+ req (
439
+ "POST" ,
440
+ f"/api/v1/{ mock_sales_index_id } /search" ,
441
+ '{"query":"quantity:>5", "max_hits": 10}' ,
442
+ )
443
+ req ("GET" , f"/api/v1/_elastic/{ mock_sales_index_id } /_search?q=animal" )
444
+ req (
445
+ "POST" ,
446
+ f"/api/v1/_elastic/{ mock_sales_index_id } /_search" ,
447
+ '{"query":{"bool":{"must":[{"range":{"quantity":{"gt":5}}}]}},"size":10}' ,
448
+ )
449
+ req ("GET" , f"/api/v1/_elastic/{ mock_sales_index_id } /_field_caps?fields=quantity" )
450
+ # expected errors
451
+ req (
452
+ "GET" ,
453
+ f"/api/v1/_elastic/{ mock_sales_index_id } /_search?query=animal" ,
454
+ expected_status = 400 ,
455
+ )
456
+ req ("GET" , f"/api/v1/_elastic/_search?q=animal" , expected_status = 501 )
0 commit comments