summaryrefslogtreecommitdiffstats
path: root/tests/topotests/munet/__main__.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /tests/topotests/munet/__main__.py
parentInitial commit. (diff)
downloadfrr-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__.py236
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)