test_nginx_latency.py (3188B)
1 #!/usr/bin/env python3 2 """Exercise the exchange latency classifier with the Lua interpreter.""" 3 import json 4 from pathlib import Path 5 import re 6 import subprocess 7 import unittest 8 9 from jinja2 import Environment, StrictUndefined 10 11 REPO = Path(__file__).resolve().parents[2] 12 ROUTES = json.loads((REPO / 'roles/monitoring/files/exchange-latency-routes.json').read_text()) 13 14 15 def render(relative, **values): 16 env = Environment(undefined=StrictUndefined) 17 env.filters['to_json'] = json.dumps 18 return env.from_string((REPO / relative).read_text()).render( 19 monitoring_nginx_latency_routes=ROUTES, **values) 20 21 22 class LatencyTest(unittest.TestCase): 23 def test_route_allowlist(self): 24 seen = set() 25 for row in ROUTES: 26 key = (row['method'], row['pattern']) 27 self.assertNotIn(key, seen) 28 seen.add(key) 29 self.assertRegex(row['operation'], r'^[a-z][a-z0-9-]*$') 30 re.compile('^' + row['pattern'] + '$') 31 self.assertEqual(sum(row['polling'] > 1 and row['method'] == 'GET' for row in ROUTES), 6) 32 33 def test_classifier(self): 34 source = render('roles/monitoring/templates/exchange-latency.lua.j2') 35 cases = [ 36 ('POST', '/batch-deposit', '200', '0.005', '0.001', '0.004', 'deposit'), 37 ('GET', '/config', '304', '0.000', '-', '-', 'config'), 38 ('GET', '/reserves/SECRET', '404', '0.1', '-', '-', 'reserves'), 39 ('POST', '/withdraw', '502', '0.5', '0.001, 0.002', '0.1, 0.3', 'withdraw'), 40 ('POST', '/coins/SECRET/refund', '200', '11.001', '0.001 : 0.002', '0.2 : 10.1', 'coins-refund'), 41 ('PATCH', '/unknown/SECRET', '400', '0.2', '-', '-', 'other'), 42 ('DELETE', '/purses/SECRET', '204', '0.001', '-', '-', 'purses'), 43 ] 44 for method, uri, status, duration, uct, urt, operation in cases: 45 line = f'm={method} uri={uri} s={status} uct={uct} urt={urt} rt={duration} rl=10 bs=20' 46 source += '\ndo\nlocal _, _, r = exchange_latency("test", 42, {log=' + json.dumps(line) + '})\n' 47 source += f'assert(r.valid == "true" and r.operation == "{operation}")\n' 48 source += f'assert(r.duration == {duration} and r.method == "{method}" and r.status == "{status}")\n' 49 source += 'assert(r.uri == nil and r.log == nil)\nend\n' 50 for line in ['garbage', 'm=GET uri=/unknown s=200 uct=- urt=- rt=0.1 rl=1 bs=1', 51 'm=HEAD uri=/config s=200 uct=- urt=- rt=0.1 rl=1 bs=1', 52 'm=POST uri=/withdraw s=999 uct=- urt=- rt=0.1 rl=1 bs=1', 53 'm=POST uri=/withdraw s=200 uct=- urt=- rt=-1 rl=1 bs=1', 54 'm=POST uri=/withdraw s=200 uct=- urt=- rt=NaN rl=1 bs=1', 55 'm=POST uri=/withdraw s=200 uct=- urt=- rt=' + '9' * 400 + ' rl=1 bs=1']: 56 source += '\ndo\nlocal _, _, r = exchange_latency("test", 42, {log=' + json.dumps(line) + '})\nassert(r.valid == "false")\nend\n' 57 result = subprocess.run(['lua', '-'], input=source, text=True, capture_output=True) 58 self.assertEqual(result.returncode, 0, result.stderr) 59 60 61 if __name__ == '__main__': 62 unittest.main()