Files
Viano-Core/tools/testbots/client.py
T

85 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Submit Lua TestBot jobs through existing authenticated Trinity SOAP commands."""
import argparse
import base64
import json
import os
from pathlib import Path
import time
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET
def command(endpoint, username, password, text, timeout=20):
envelope = ET.Element('{http://schemas.xmlsoap.org/soap/envelope/}Envelope')
body = ET.SubElement(envelope, '{http://schemas.xmlsoap.org/soap/envelope/}Body')
execute = ET.SubElement(body, '{urn:TC}executeCommand')
ET.SubElement(execute, 'command').text = text
auth = base64.b64encode((username + ':' + password).encode()).decode()
request = urllib.request.Request(endpoint, ET.tostring(envelope), headers={
'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': 'urn:TC#executeCommand',
'Authorization': 'Basic ' + auth,
})
with urllib.request.urlopen(request, timeout=timeout) as response:
root = ET.fromstring(response.read())
for node in root.iter():
if node.tag.rsplit('}', 1)[-1] == 'Fault':
raise RuntimeError('SOAP fault: ' + ''.join(node.itertext()))
for node in root.iter():
if node.tag.rsplit('}', 1)[-1] in ('result', 'return'):
return ''.join(node.itertext()).strip()
raise RuntimeError('SOAP response contains no command result')
def run(endpoint, username, password, run_id, script, deadline=330):
if not run_id or len(run_id) > 80 or any(c not in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-' for c in run_id):
raise ValueError('Invalid run ID')
content = Path(script).read_bytes()
if not content or len(content) > 65536:
raise ValueError('Script must contain 1..65536 bytes')
reply = command(endpoint, username, password,
f'clientless test submit {run_id} ' + base64.b64encode(content).decode())
if f'Testbot run {run_id}: queued' not in reply:
raise RuntimeError(reply)
end = time.monotonic() + deadline
while time.monotonic() < end:
reply = command(endpoint, username, password, f'clientless test result {run_id}')
try:
result = json.loads(reply)
except json.JSONDecodeError:
if 'not available yet' not in reply:
raise RuntimeError(reply)
else:
if result['run_id'] != run_id:
raise RuntimeError('Result belongs to another run')
if result['state'] != 'running':
return result
time.sleep(1)
raise TimeoutError(f'Client deadline exceeded; retrieve/cancel run {run_id} on server')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('run_id')
parser.add_argument('script', type=Path)
parser.add_argument('--endpoint', default='http://127.0.0.1:7878/')
parser.add_argument('--user', required=True)
parser.add_argument('--output', type=Path)
parser.add_argument('--timeout', type=float, default=330, help='client wait deadline in seconds')
args = parser.parse_args()
try:
result = run(args.endpoint, args.user, os.environ['TESTBOT_PASSWORD'], args.run_id, args.script, deadline=args.timeout)
text = json.dumps(result, ensure_ascii=False, indent=2)
if args.output:
args.output.write_text(text + '\n')
print(text)
return 0 if result['state'] == 'passed' else 1
except (OSError, ValueError, RuntimeError, TimeoutError, KeyError, ET.ParseError) as error:
print(json.dumps({'client_error': str(error)}, ensure_ascii=False))
return 2
if __name__ == '__main__':
raise SystemExit(main())