diff --git a/cloud/blockstore/tools/testing/fake-vhost-server/__main__.py b/cloud/blockstore/tools/testing/fake-vhost-server/__main__.py new file mode 100644 index 00000000000..4403a66751f --- /dev/null +++ b/cloud/blockstore/tools/testing/fake-vhost-server/__main__.py @@ -0,0 +1,242 @@ +import argparse +import json +import logging +import signal +import sys + +from datetime import datetime +from http.server import HTTPServer, BaseHTTPRequestHandler +from threading import Thread + + +def _prepare_logging(verbose): + log_level = { + "error": logging.ERROR, + "warn": logging.WARNING, + "info": logging.INFO, + "debug": logging.DEBUG, + }[verbose] + + logging.basicConfig( + stream=sys.stderr, + level=log_level, + format="[%(levelname)s] [%(asctime)s] %(message)s") + + +class _DeviceChunk: + + def __init__(self, s: str): + i = s.rfind(':') + if i == -1 or i == 0: + raise argparse.ArgumentTypeError(f"invalid format: {s}") + + j = s[0:i-1].rfind(':') + if j == -1: + raise argparse.ArgumentTypeError(f"invalid format: {s}") + + self.offset = int(s[i+1:]) + self.size = int(s[j+1:i]) + self.path = s[:j] + + def __str__(self): + return f"[{self.path} {self.size} {self.offset}]" + + +class _App: + + def __init__(self): + self.exit_code = 0 + + def terminate(self, exit_code): + self.exit_code = exit_code + exit(exit_code) + + +def _create_handler(args, app: _App): + + class Handler(BaseHTTPRequestHandler): + + def do_GET(self): + self.log_request() + + if self.path == '/ping': + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.end_headers() + self.wfile.write('pong'.encode('utf-8')) + return + + self.send_error(400) + + def do_POST(self): + self.log_request() + + length = int(self.headers['content-length']) + req = self.rfile.read(length).decode('utf-8') + + if self.path != '/crash': + self.send_error(400) + return + + data = json.loads(req) + + exit_code = data.get('exit_code', -1) + logging.info(f'exit with {exit_code}...') + app.terminate(exit_code) + + return Handler + + +def _run_server(args): + app = _App() + + server = HTTPServer(('localhost', args.port), _create_handler(args, app)) + + t0 = datetime.now() + + def signal_handler(signum, frame): + if signum == signal.SIGUSR1: + # TODO + print(json.dumps({ + "elapsed_ms": int((datetime.now() - t0).total_seconds() * 1000) + })) + return + + logging.info('shutdown...') + server.shutdown() + + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGUSR1, signal_handler) + + thread = Thread(target=server.serve_forever, name='server') + + logging.info('start...') + + thread.start() + thread.join() + + logging.info('done') + + return app.exit_code + + +def main(): + parser = argparse.ArgumentParser() + + # custom options + + parser.add_argument('--port', type=int, required=True) + + # vhost-server options + + parser.add_argument( + "-v", + '--verbose', + help="output level for diagnostics messages", + default="debug", + type=str) + + parser.add_argument("-s", '--socket-path', type=str, metavar="FILE", required=True) + + parser.add_argument( + "-i", + "--serial", + type=str, + metavar="STR", + help="disk serial", + required=True) + + parser.add_argument("--disk-id", type=str, metavar="STR", help="disk id") + + parser.add_argument( + "--client-id", + type=str, + metavar="STR", + help="client id", + default="vhost-server") + + parser.add_argument( + "--device", + required=True, + type=_DeviceChunk, + metavar="STR", + action="append", + help="specify device string path:size:offset " + "(e.g. /dev/vda:1000000:0, rdma://host:10020/abcdef:1000000:0)") + + parser.add_argument( + "--device-backend", + type=str, + metavar="STR", + help="specify device backend", + choices=["aio", "rdma", "null"], + default="aio") + + parser.add_argument( + "-r", + "--read-only", + action='store_true', + help="read only mode") + + parser.add_argument( + "--no-sync", + action='store_true', + help="do not use O_SYNC") + + parser.add_argument( + "--no-chmod", + action='store_true', + help="do not chmod socket") + + parser.add_argument( + "-B", + "--batch-size", + type=int, + metavar="INT", + default=1024) + + parser.add_argument( + "--block-size", + help="size of block device", + type=int, + metavar="INT", + default=512) + + parser.add_argument( + "-q", + "--queue-count", + type=int, + metavar="INT", + default=0) + + parser.add_argument( + "--log-type", + type=str, + metavar="STR", + choices=["json", "console"], + default="json") + + parser.add_argument( + "--rdma-queue-size", + help="Rdma client queue size", + type=int, + metavar="INT", + default=256) + + parser.add_argument( + "--rdma-max-buffer-size", + help="Rdma client queue size", + type=int, + metavar="INT", + default=4*1024**2 + 4096) + + args = parser.parse_args() + + _prepare_logging(args.verbose) + + return _run_server(args) + + +if __name__ == '__main__': + exit(main()) diff --git a/cloud/blockstore/tools/testing/fake-vhost-server/fake-vhost-server b/cloud/blockstore/tools/testing/fake-vhost-server/fake-vhost-server new file mode 120000 index 00000000000..768caac6338 --- /dev/null +++ b/cloud/blockstore/tools/testing/fake-vhost-server/fake-vhost-server @@ -0,0 +1 @@ +/home/sharpeye/.ya/build/symres/c30155ba13a47cf65811327d18e0c4e5/fake-vhost-server \ No newline at end of file diff --git a/cloud/blockstore/tools/testing/fake-vhost-server/ya.make b/cloud/blockstore/tools/testing/fake-vhost-server/ya.make new file mode 100644 index 00000000000..586eb64696a --- /dev/null +++ b/cloud/blockstore/tools/testing/fake-vhost-server/ya.make @@ -0,0 +1,11 @@ +PY3_PROGRAM() + +PEERDIR( + # contrib/python/requests/py3 +) + +PY_SRCS( + __main__.py +) + +END() diff --git a/cloud/blockstore/tools/testing/ya.make b/cloud/blockstore/tools/testing/ya.make index 30f57cde327..7ca46d879d1 100644 --- a/cloud/blockstore/tools/testing/ya.make +++ b/cloud/blockstore/tools/testing/ya.make @@ -4,6 +4,7 @@ RECURSE( eternal_tests fake-conductor fake-nbs + fake-vhost-server generate-agents infra-client loadtest