diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /tests/topotests/munet/__main__.py | |
parent | Initial commit. (diff) | |
download | frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip |
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/topotests/munet/__main__.py')
-rw-r--r-- | tests/topotests/munet/__main__.py | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/tests/topotests/munet/__main__.py b/tests/topotests/munet/__main__.py new file mode 100644 index 0000000..4419ab9 --- /dev/null +++ b/tests/topotests/munet/__main__.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# September 2 2021, Christian Hopps <chopps@labn.net> +# +# Copyright 2021, LabN Consulting, L.L.C. +# +"""The main function for standalone operation.""" +import argparse +import asyncio +import logging +import logging.config +import os +import subprocess +import sys + +from . import cli +from . import parser +from .base import get_event_loop +from .cleanup import cleanup_previous +from .compat import PytestConfig + + +logger = None + + +async def forever(): + while True: + await asyncio.sleep(3600) + + +async def run_and_wait(args, unet): + tasks = [] + + if not args.topology_only: + # add the cmd.wait()s returned from unet.run() + tasks += await unet.run() + + if sys.stdin.isatty() and not args.no_cli: + # Run an interactive CLI + task = cli.async_cli(unet) + else: + if args.no_wait: + logger.info("Waiting for all node cmd to complete") + task = asyncio.gather(*tasks, return_exceptions=True) + else: + logger.info("Waiting on signal to exit") + task = asyncio.create_task(forever()) + task = asyncio.gather(task, *tasks, return_exceptions=True) + + try: + await task + finally: + # Basically we are canceling tasks from unet.run() which are just async calls to + # node.cmd_p.wait() so we've stopped waiting for them to complete, but not + # actually canceld/killed the cmd_p process. + for task in tasks: + task.cancel() + + +async def async_main(args, config): + status = 3 + + # Setup the namespaces and network addressing. + + unet = await parser.async_build_topology( + config, rundir=args.rundir, args=args, pytestconfig=PytestConfig(args) + ) + logger.info("Topology up: rundir: %s", unet.rundir) + + try: + status = await run_and_wait(args, unet) + except KeyboardInterrupt: + logger.info("Exiting, received KeyboardInterrupt in async_main") + except asyncio.CancelledError as ex: + logger.info("task canceled error: %s cleaning up", ex) + except Exception as error: + logger.info("Exiting, unexpected exception %s", error, exc_info=True) + else: + logger.info("Exiting normally") + + logger.debug("main: async deleting") + try: + await unet.async_delete() + except KeyboardInterrupt: + status = 2 + logger.warning("Received KeyboardInterrupt while cleaning up.") + except Exception as error: + status = 2 + logger.info("Deleting, unexpected exception %s", error, exc_info=True) + return status + + +def main(*args): + ap = argparse.ArgumentParser(args) + cap = ap.add_argument_group(title="Config", description="config related options") + + cap.add_argument("-c", "--config", help="config file (yaml, toml, json, ...)") + cap.add_argument( + "-d", "--rundir", help="runtime directory for tempfiles, logs, etc" + ) + cap.add_argument( + "--kinds-config", + help="kinds config file, overrides default search (yaml, toml, json, ...)", + ) + cap.add_argument( + "--project-root", help="directory to stop searching for kinds config at" + ) + rap = ap.add_argument_group(title="Runtime", description="runtime related options") + rap.add_argument( + "-C", + "--cleanup", + action="store_true", + help="Remove the entire rundir (not just node subdirs) prior to running.", + ) + rap.add_argument( + "--gdb", metavar="NODE-LIST", help="comma-sep list of hosts to run gdb on" + ) + rap.add_argument( + "--gdb-breakpoints", + metavar="BREAKPOINT-LIST", + help="comma-sep list of breakpoints to set", + ) + rap.add_argument( + "--host", + action="store_true", + help="no isolation for top namespace, bridges exposed to default namespace", + ) + rap.add_argument( + "--pcap", + metavar="TARGET-LIST", + help="comma-sep list of capture targets (NETWORK or NODE:IFNAME)", + ) + rap.add_argument( + "--shell", metavar="NODE-LIST", help="comma-sep list of nodes to open shells on" + ) + rap.add_argument( + "--stderr", + metavar="NODE-LIST", + help="comma-sep list of nodes to open windows viewing stderr", + ) + rap.add_argument( + "--stdout", + metavar="NODE-LIST", + help="comma-sep list of nodes to open windows viewing stdout", + ) + rap.add_argument( + "--topology-only", + action="store_true", + help="Do not run any node commands", + ) + rap.add_argument("--unshare-inline", action="store_true", help=argparse.SUPPRESS) + rap.add_argument( + "--validate-only", + action="store_true", + help="Validate the config against the schema definition", + ) + rap.add_argument("-v", "--verbose", action="store_true", help="be verbose") + rap.add_argument( + "-V", "--version", action="store_true", help="print the verison number and exit" + ) + eap = ap.add_argument_group(title="Uncommon", description="uncommonly used options") + eap.add_argument("--log-config", help="logging config file (yaml, toml, json, ...)") + eap.add_argument( + "--no-kill", + action="store_true", + help="Do not kill previous running processes", + ) + eap.add_argument( + "--no-cli", action="store_true", help="Do not run the interactive CLI" + ) + eap.add_argument("--no-wait", action="store_true", help="Exit after commands") + + args = ap.parse_args() + + if args.version: + from importlib import metadata # pylint: disable=C0415 + + print(metadata.version("munet")) + sys.exit(0) + + rundir = args.rundir if args.rundir else "/tmp/munet" + args.rundir = rundir + + if args.cleanup: + if os.path.exists(rundir): + if not os.path.exists(f"{rundir}/config.json"): + logging.critical( + 'unsafe: won\'t clean up rundir "%s" as ' + "previous config.json not present", + rundir, + ) + sys.exit(1) + else: + subprocess.run(["/usr/bin/rm", "-rf", rundir], check=True) + + subprocess.run(f"mkdir -p {rundir} && chmod 755 {rundir}", check=True, shell=True) + os.environ["MUNET_RUNDIR"] = rundir + + parser.setup_logging(args) + + global logger # pylint: disable=W0603 + logger = logging.getLogger("munet") + + config = parser.get_config(args.config) + logger.info("Loaded config from %s", config["config_pathname"]) + if not config["topology"]["nodes"]: + logger.critical("No nodes defined in config file") + return 1 + + if not args.no_kill: + cleanup_previous() + + loop = None + status = 4 + try: + parser.validate_config(config, logger, args) + if args.validate_only: + return 0 + # Executes the cmd for each node. + loop = get_event_loop() + status = loop.run_until_complete(async_main(args, config)) + except KeyboardInterrupt: + logger.info("Exiting, received KeyboardInterrupt in main") + except Exception as error: + logger.info("Exiting, unexpected exception %s", error, exc_info=True) + finally: + if loop: + loop.close() + + return status + + +if __name__ == "__main__": + exit_status = main() + sys.exit(exit_status) |