From 28af6100f700db46fe1bfcaddad1a50a064669d5 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Fri, 21 Jun 2024 12:01:30 -0600 Subject: [PATCH 1/4] CephDevstack.check_requirements: Use new objects Signed-off-by: Zack Cerza --- ceph_devstack/resources/ceph/__init__.py | 58 +++++------------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/ceph_devstack/resources/ceph/__init__.py b/ceph_devstack/resources/ceph/__init__.py index bde9b983..dbc59b70 100644 --- a/ceph_devstack/resources/ceph/__init__.py +++ b/ceph_devstack/resources/ceph/__init__.py @@ -6,7 +6,7 @@ from collections import OrderedDict from subprocess import CalledProcessError -from ceph_devstack import config, logger, PROJECT_ROOT +from ceph_devstack import config, logger from ceph_devstack.host import host from ceph_devstack.resources.misc import Secret, Network from ceph_devstack.resources.ceph.containers import ( @@ -18,6 +18,12 @@ Teuthology, Archive, ) +from ceph_devstack.resources.ceph.requirements import ( + HasSudo, + LoopControlDeviceExists, + LoopControlDeviceWriteable, + SELinuxModule, +) class SSHKeyPair(Secret): @@ -105,57 +111,15 @@ async def get_testnode_count(self) -> int: async def check_requirements(self): result = True - if has_sudo := host.run(["sudo", "true"]).returncode == 0: - has_sudo = True - else: - has_sudo = False - result = False - logger.error("sudo access is required") - - loop_control = "/dev/loop-control" - if not host.path_exists(loop_control): - result = False - logger.error(f"{loop_control} does not exist!") - elif host.run(["test", "-w", loop_control]).wait() != 0: - result = False - group = ( - host.run(["stat", "--printf", "%G", loop_control]) - .communicate()[0] - .decode() - ) - user = host.run(["whoami"]).communicate()[0].strip().decode() - if host.type == "local": - logger.error( - f"Cannot write to {loop_control}. " - f"Try: sudo usermod -a -G {group} {user}" - ) - logger.warning( - "Note that group modifications require a logout to take effect." - ) - else: - logger.error( - f"Cannot write to {loop_control}. " - f"Try: sudo chgrp {user} {loop_control}" - ) + result = has_sudo = await HasSudo().evaluate() + result = result and await LoopControlDeviceExists().evaluate() + result = result and await LoopControlDeviceWriteable().evaluate() # Check for SELinux being enabled and Enforcing; then check for the # presence of our module. If necessary, inform the user and instruct # them how to build and install. if has_sudo and await host.selinux_enforcing(): - proc = await host.arun(["sudo", "semodule", "-l"]) - assert proc.stdout is not None - await proc.wait() - out = (await proc.stdout.read()).decode() - if "ceph_devstack" not in out.split("\n"): - result = False - logger.error( - "SELinux is in Enforcing mode. To run nested rootless podman " - "containers, it is necessary to install ceph-devstack's SELinux " - "module. Try: (sudo dnf install policycoreutils-devel " - f"selinux-policy-devel && cd {PROJECT_ROOT} && make -f " - "/usr/share/selinux/devel/Makefile ceph_devstack.pp && sudo " - "semodule -i ceph_devstack.pp)" - ) + result = result and await SELinuxModule().evaluate() for name, obj in config["containers"].items(): if (repo := obj.get("repo")) and not host.path_exists(repo): From cef6aa51627c0260eac600d0f372f64b8f9f5b6c Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Fri, 21 Jun 2024 11:41:03 -0600 Subject: [PATCH 2/4] PodmanResource.inspect: Remove dead code Signed-off-by: Zack Cerza --- ceph_devstack/resources/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ceph_devstack/resources/__init__.py b/ceph_devstack/resources/__init__.py index 1c9e39d6..e97881fc 100644 --- a/ceph_devstack/resources/__init__.py +++ b/ceph_devstack/resources/__init__.py @@ -89,12 +89,8 @@ async def apply(self, action: str): async def inspect(self): proc = await self.cmd(self.format_cmd(self.exists_cmd)) - out, err = await proc.communicate() + out, _ = await proc.communicate() return json.loads(out) - return json.loads(proc.stdout.read()) - if proc.stdout is None: - return {} - return json.loads((await proc.stdout.read()).decode()) async def exists(self): if not self.exists_cmd: From b3ade234949535840330799561eb48edd48b316b Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Thu, 20 Jun 2024 16:22:52 -0600 Subject: [PATCH 3/4] Add wait command This is much like `podman wait`, and uses it under the hood, but will also exit with the same code as the container did. Signed-off-by: Zack Cerza --- ceph_devstack/__init__.py | 8 ++++++++ ceph_devstack/cli.py | 8 ++++++-- ceph_devstack/resources/ceph/__init__.py | 9 +++++++++ ceph_devstack/resources/container.py | 9 +++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ceph_devstack/__init__.py b/ceph_devstack/__init__.py index e5d822b8..c2c88d66 100644 --- a/ceph_devstack/__init__.py +++ b/ceph_devstack/__init__.py @@ -96,6 +96,14 @@ def parse_args(args: List[str]) -> argparse.Namespace: subparsers.add_parser( "watch", help="Monitor the cluster, recreating containers as necessary" ) + parser_wait = subparsers.add_parser( + "wait", + help="Wait for the specified container to exit. Exit with its exit code.", + ) + parser_wait.add_argument( + "container", + help="The container to wait for", + ) subparsers.add_parser("show-conf", help="show the configuration") return parser.parse_args(args) diff --git a/ceph_devstack/cli.py b/ceph_devstack/cli.py index d73b7617..b3116f94 100644 --- a/ceph_devstack/cli.py +++ b/ceph_devstack/cli.py @@ -34,9 +34,13 @@ async def run(): sys.exit(1) if args.command == "doctor": return - await obj.apply(args.command) + elif args.command == "wait": + return await obj.wait(container_name=args.container) + else: + await obj.apply(args.command) + return 0 try: - asyncio.run(run()) + sys.exit(asyncio.run(run())) except KeyboardInterrupt: logger.debug("Exiting!") diff --git a/ceph_devstack/resources/ceph/__init__.py b/ceph_devstack/resources/ceph/__init__.py index dbc59b70..95a8754e 100644 --- a/ceph_devstack/resources/ceph/__init__.py +++ b/ceph_devstack/resources/ceph/__init__.py @@ -220,3 +220,12 @@ async def watch(self): await container.start() except KeyboardInterrupt: break + + async def wait(self, container_name: str): + for kind in (await self.get_containers()).keys(): + for name in await self.get_container_names(kind): + container = kind(name=name) + if container.name == container_name: + return await container.wait() + logger.error(f"Could not find container {container_name}") + return 1 diff --git a/ceph_devstack/resources/container.py b/ceph_devstack/resources/container.py index 1ce90222..c0c6d9ef 100644 --- a/ceph_devstack/resources/container.py +++ b/ceph_devstack/resources/container.py @@ -25,6 +25,7 @@ class Container(PodmanResource): stop_cmd: List[str] = ["podman", "container", "stop", "{name}"] exists_cmd: List[str] = ["podman", "container", "inspect", "{name}"] pull_cmd: List[str] = ["podman", "pull", "{image}"] + wait_cmd: List[str] = ["podman", "wait", "{name}"] env_vars: Dict[str, Optional[str]] = {} def __init__(self, name: str = ""): @@ -153,3 +154,11 @@ async def is_running(self): if not result: return False return result[0]["State"]["Status"].lower() == "running" + + async def wait(self) -> Optional[int]: + proc = await self.cmd(self.format_cmd(self.wait_cmd)) + out, err = await proc.communicate() + if proc.returncode: + logger.error(f"Could not wait for {self.name}: {err.decode().strip()}") + return proc.returncode + return int(out.decode().strip()) From d28c50e86977500d3932025c92af36c95a168b0d Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Fri, 21 Jun 2024 16:07:21 -0600 Subject: [PATCH 4/4] Jenkinsfile: Use new wait command Signed-off-by: Zack Cerza --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 456b31e9..728eb5e6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -90,8 +90,8 @@ pipeline { stage("Wait for teuthology container") { steps { sh """ - podman wait teuthology - exit \$(podman inspect -f "{{.State.ExitCode}}" teuthology) + source ./venv/bin/activate + ceph-devstack wait teuthology """ } }