]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
cephadm/box: add container engine class
authorPere Diaz Bou <pdiazbou@redhat.com>
Mon, 9 Jan 2023 17:02:12 +0000 (18:02 +0100)
committerPere Diaz Bou <pdiazbou@redhat.com>
Mon, 9 Jan 2023 17:02:12 +0000 (18:02 +0100)
ContainerEngine is the baseclass of PodmanEngine and DockerEngine.
Furthermore, a HostContainer class was added as struct of info related
to a container.

Signed-off-by: Pere Diaz Bou <pdiazbou@redhat.com>
src/cephadm/box/box.py
src/cephadm/box/host.py
src/cephadm/box/osd.py
src/cephadm/box/util.py

index ee6ad168d97e4c786a3581c333ae276210fe6d02..fca55403c6ecd9e24680ad508953d41b93862835 100755 (executable)
@@ -16,8 +16,12 @@ from util import (
     run_cephadm_shell_command,
     run_dc_shell_command,
     run_dc_shell_commands,
+    get_container_engine,
     run_shell_command,
     run_shell_commands,
+    ContainerEngine,
+    DockerEngine,
+    PodmanEngine,
     colored,
     engine,
     engine_compose,
@@ -55,7 +59,8 @@ def image_exists(image_name: str):
     # extract_tag
     assert image_name.find(':')
     image_name, tag = image_name.split(':')
-    images = run_shell_command(f'{engine()} image ls').split('\n')
+    engine = get_container_engine()
+    images = engine.run('image ls').split('\n')
     IMAGE_NAME = 0
     TAG = 1
     for image in images:
@@ -69,25 +74,24 @@ def image_exists(image_name: str):
 
 def get_ceph_image():
     print('Getting ceph image')
-    run_shell_command(f'{engine()} pull {CEPH_IMAGE}')
+    engine = get_container_engine()
+    engine.run('pull {CEPH_IMAGE}')
     # update
-    run_shell_command(f'{engine()} build -t {CEPH_IMAGE} docker/ceph')
+    engine.run('build -t {CEPH_IMAGE} docker/ceph')
     if not os.path.exists('docker/ceph/image'):
         os.mkdir('docker/ceph/image')
 
     remove_ceph_image_tar()
 
-    run_shell_command(f'{engine()} save {CEPH_IMAGE} -o {CEPH_IMAGE_TAR}')
+    engine.run('save {CEPH_IMAGE} -o {CEPH_IMAGE_TAR}')
     run_shell_command(f'chmod 777 {CEPH_IMAGE_TAR}')
     print('Ceph image added')
 
 
 def get_box_image():
     print('Getting box image')
-    if engine() == 'docker':
-        run_shell_command(f'{engine()} build -t cephadm-box -f DockerfileDocker .')
-    else:
-        run_shell_command(f'{engine()} build -t cephadm-box -f DockerfilePodman .')
+    engine = get_container_engine()
+    engine.run(f'build -t cephadm-box -f {engine.dockerfile} .')
     print('Box image added')
 
 def check_dashboard():
@@ -106,65 +110,9 @@ def check_selinux():
         print(colored('selinux should be disabled, please disable it if you '
                        'don\'t want unexpected behaviour.', Colors.WARNING))
 
-def setup_podman_env(hosts: int = 1, osd_devs={}):
-    network_name = 'box_network'
-    networks = run_shell_command('podman network ls')
-    if network_name not in networks:
-        run_shell_command(f'podman network create -d bridge {network_name}')
-
-    run_default_options = """--group-add keep-groups --device /dev/fuse -it -d \\
-        --cap-add SYS_ADMIN \\
-        --cap-add NET_ADMIN \\
-        --cap-add SYS_TIME \\
-        --cap-add SYS_RAWIO \\
-        --cap-add MKNOD \\
-        --cap-add NET_RAW \\
-        --cap-add SETUID \\
-        --cap-add SETGID \\
-        --cap-add CHOWN \\
-        --cap-add SYS_PTRACE \\
-        --cap-add SYS_TTY_CONFIG \\
-        --cap-add CAP_AUDIT_WRITE \\
-        --cap-add CAP_AUDIT_CONTROL \\
-        -e CEPH_BRANCH=main \\
-        -v ../../../:/ceph:z \\
-        -v ../:/cephadm:z \\
-        -v /run/udev:/run/udev \\
-        --tmpfs /run \\
-        --tmpfs /tmp \\
-        -v /sys/dev/block:/sys/dev/block \\
-        -v /sys/fs/cgroup:/sys/fs/cgroup:ro \\
-        -v /dev/fuse:/dev/fuse \\
-        -v /dev/disk:/dev/disk \\
-        -v /sys/devices/virtual/block:/sys/devices/virtual/block \\
-        -v /sys/block:/dev/block \\
-        -v /dev/mapper:/dev/mapper \\
-        -v /dev/mapper/control:/dev/mapper/control \\
-        --stop-signal RTMIN+3 -m 20g cephadm-box \\
-      """
-    def add_option(dest, src):
-        dest = f'{src} {dest}'
-        return dest
-    for osd_dev in osd_devs.values():
-        device = osd_dev["device"]
-        run_default_options = add_option(run_default_options, f'--device {device}:{device}')
-        
-
-    for host in range(hosts+1): # 0 will be the seed
-        options = run_default_options
-        options = add_option(options, f'--name box_hosts_{host}')
-        options = add_option(options, f'--network {network_name}')
-        if host == 0:
-            options = add_option(options, f'-p 8443:8443') # dashboard
-            options = add_option(options, f'-p 3000:3000') # grafana
-            options = add_option(options, f'-p 9093:9093') # alertmanager
-            options = add_option(options, f'-p 9095:9095') # prometheus
-        
-        run_shell_command(f'podman run {options}')
-
 class Cluster(Target):
     _help = 'Manage docker cephadm boxes'
-    actions = ['bootstrap', 'start', 'down', 'list', 'sh', 'setup', 'cleanup', 'doctor']
+    actions = ['bootstrap', 'start', 'down', 'list', 'sh', 'setup', 'cleanup']
 
     def set_args(self):
         self.parser.add_argument(
@@ -196,16 +144,13 @@ class Cluster(Target):
         print('Running bootstrap on seed')
         cephadm_path = str(os.environ.get('CEPHADM_PATH'))
 
-        if engine() == 'docker':
-            # restart to ensure docker is using daemon.json
-            run_shell_command(
-                'systemctl restart docker'
-            )
-
+        engine = get_container_engine()
+        if isinstance(engine, DockerEngine):
+            engine.restart()
         st = os.stat(cephadm_path)
         os.chmod(cephadm_path, st.st_mode | stat.S_IEXEC)
 
-        run_shell_command(f'{engine()} load < /cephadm/box/docker/ceph/image/quay.ceph.image.tar')
+        engine.run('load < /cephadm/box/docker/ceph/image/quay.ceph.image.tar')
         # cephadm guid error because it sometimes tries to use quay.ceph.io/ceph-ci/ceph:<none>
         # instead of main branch's tag
         run_shell_command('export CEPH_SOURCE_FOLDER=/ceph')
@@ -254,7 +199,7 @@ class Cluster(Target):
         )
 
         print('Running cephadm bootstrap...')
-        run_shell_command(cephadm_bootstrap_command)
+        run_shell_command(cephadm_bootstrap_command, expect_exit_code=120) 
         print('Cephadm bootstrap complete')
 
         run_shell_command('sudo vgchange --refresh')
@@ -271,12 +216,13 @@ class Cluster(Target):
         check_selinux()
         osds = int(Config.get('osds'))
         hosts = int(Config.get('hosts'))
+        engine = get_container_engine()
 
         # ensure boxes don't exist
         self.down()
 
         # podman is ran without sudo
-        if engine() == 'podman':
+        if isinstance(engine, PodmanEngine):
             I_am = run_shell_command('whoami')
             if 'root' in I_am:
                 print(root_error_msg)
@@ -296,18 +242,14 @@ class Cluster(Target):
 
         print('Starting containers')
 
-        if engine() == 'docker':
-            dcflags = f'-f {Config.get("docker_yaml")}'
-            if not os.path.exists('/sys/fs/cgroup/cgroup.controllers'):
-                dcflags += f' -f {Config.get("docker_v1_yaml")}'
-            run_shell_command(f'{engine_compose()} {dcflags} up --scale hosts={hosts} -d')
-        else:
-            setup_podman_env(hosts=hosts, osd_devs=osd.load_osd_devices())
+        engine.up(hosts)
 
+        containers = engine.get_containers()
+        seed = engine.get_seed()
         # Umounting somehow brings back the contents of the host /sys/dev/block. 
         # On startup /sys/dev/block is empty. After umount, we can see symlinks again
         # so that lsblk is able to run as expected
-        run_dc_shell_command('umount /sys/dev/block', 1, BoxType.SEED)
+        run_dc_shell_command('umount /sys/dev/block', seed)
 
         run_shell_command('sudo sysctl net.ipv4.conf.all.forwarding=1')
         run_shell_command('sudo iptables -P FORWARD ACCEPT')
@@ -319,15 +261,15 @@ class Cluster(Target):
         systemctl start chronyd
         systemctl status --no-pager chronyd
         """
-        for h in range(hosts):
-            run_dc_shell_commands(h + 1, BoxType.HOST, chronyd_setup)
-        run_dc_shell_commands(1, BoxType.SEED, chronyd_setup)
+        for container in containers:
+            print(colored('Got container:', Colors.OKCYAN), str(container))
+        for container in containers:
+            run_dc_shell_commands(chronyd_setup, container)
 
         print('Seting up host ssh servers')
-        for h in range(hosts):
-            host._setup_ssh(BoxType.HOST, h + 1)
-
-        host._setup_ssh(BoxType.SEED, 1)
+        for container in containers:
+            print(colored('Setting up ssh server for:', Colors.OKCYAN), str(container))
+            host._setup_ssh(container)
 
         verbose = '-v' if Config.get('verbose') else ''
         skip_deploy = '--skip-deploy-osds' if Config.get('skip-deploy-osds') else ''
@@ -336,15 +278,15 @@ class Cluster(Target):
         )
         skip_dashboard = '--skip-dashboard' if Config.get('skip-dashboard') else ''
         box_bootstrap_command = (
-            f'/cephadm/box/box.py {verbose} --engine {engine()} cluster bootstrap '
+            f'/cephadm/box/box.py {verbose} --engine {engine.command} cluster bootstrap '
             f'--osds {osds} '
             f'--hosts {hosts} '
             f'{skip_deploy} '
             f'{skip_dashboard} '
             f'{skip_monitoring_stack} '
         )
-        run_dc_shell_command(box_bootstrap_command, 1, BoxType.SEED)
-
+        print(box_bootstrap_command)
+        run_dc_shell_command(box_bootstrap_command, seed)
 
         expanded = Config.get('expanded')
         if expanded:
@@ -363,7 +305,7 @@ class Cluster(Target):
 
         dashboard_ip = 'localhost'
         info = get_boxes_container_info(with_seed=True)
-        if engine() == 'docker':
+        if isinstance(engine, DockerEngine):
             for i in range(info['size']):
                 if get_seed_name() in info['container_names'][i]:
                     dashboard_ip = info["ips"][i]
@@ -371,25 +313,22 @@ class Cluster(Target):
 
         print('Bootstrap finished successfully')
 
-    @ensure_outside_container
-    def doctor(self):
-        pass
-
     @ensure_outside_container
     def down(self):
-        if engine() == 'podman':
-            containers = json.loads(run_shell_command('podman container ls --format json'))
+        engine = get_container_engine()
+        if isinstance(engine, PodmanEngine):
+            containers = json.loads(engine.run('container ls --format json'))
             for container in containers:
                 for name in container['Names']:
                     if name.startswith('box_hosts_'):
-                        run_shell_command(f'podman container kill {name}')
-                        run_shell_command(f'podman container rm {name}')
-            pods = json.loads(run_shell_command('podman pod ls --format json'))
+                        engine.run(f'container kill {name}')
+                        engine.run(f'container rm {name}')
+            pods = json.loads(engine.run('pod ls --format json'))
             for pod in pods:
                 if 'Name' in pod and pod['Name'].startswith('box_pod_host'):
                     name = pod['Name']
-                    run_shell_command(f'podman pod kill {name}')
-                    run_shell_command(f'podman pod rm {name}')
+                    engine.run(f'pod kill {name}')
+                    engine.run(f'pod rm {name}')
         else:
             run_shell_command(f'{engine_compose()} -f {Config.get("docker_yaml")} down')
         print('Successfully killed all boxes')
index cb663a61d9517455ae05ab8878827c3d3141232c..aae16d07f4531f0a702da8f67391f59358323707 100644 (file)
@@ -3,8 +3,10 @@ from typing import List, Union
 
 from util import (
     Config,
+    HostContainer,
     Target,
     get_boxes_container_info,
+    get_container_engine,
     inside_container,
     run_cephadm_shell_command,
     run_dc_shell_command,
@@ -14,10 +16,11 @@ from util import (
 )
 
 
-def _setup_ssh(container_type: BoxType, container_index):
+def _setup_ssh(container: HostContainer):
     if inside_container():
         if not os.path.exists('/root/.ssh/known_hosts'):
-            run_shell_command('ssh-keygen -b 2048 -t rsa -f /root/.ssh/id_rsa -q -N ""')
+            run_shell_command('echo "y" | ssh-keygen -b 2048 -t rsa -f /root/.ssh/id_rsa -q -N ""', 
+                              expect_error=True)
 
         run_shell_command('echo "root:root" | chpasswd')
         with open('/etc/ssh/sshd_config', 'a+') as f:
@@ -29,9 +32,8 @@ def _setup_ssh(container_type: BoxType, container_index):
         print('Redirecting to _setup_ssh to container')
         verbose = '-v' if Config.get('verbose') else ''
         run_dc_shell_command(
-            f'/cephadm/box/box.py {verbose} --engine {engine()} host setup_ssh {BoxType.to_string(container_type)} {container_index}',
-            container_index,
-            container_type,
+            f'/cephadm/box/box.py {verbose} --engine {engine()} host setup_ssh {container.name}',
+            container
         )
 
 
@@ -48,11 +50,11 @@ def _add_hosts(ips: Union[List[str], str], hostnames: Union[List[str], str]):
         ips = f'{ips}'
         hostnames = ' '.join(hostnames)
         hostnames = f'{hostnames}'
+        seed = get_container_engine().get_seed()
         run_dc_shell_command(
-            f'/cephadm/box/box.py {verbose} --engine {engine()} host add_hosts seed {BoxType.to_string(BoxType.SEED)} --ips {ips} --hostnames {hostnames}',
-            1,
-            BoxType.SEED,
-        )
+                f'/cephadm/box/box.py {verbose} --engine {engine()} host add_hosts {seed.name} --ips {ips} --hostnames {hostnames}',
+                seed
+                )
 
 
 def _copy_cluster_ssh_key(ips: Union[List[str], str]):
@@ -74,10 +76,10 @@ def _copy_cluster_ssh_key(ips: Union[List[str], str]):
         ips = ' '.join(ips)
         ips = f'{ips}'
         # assume we only have one seed
+        seed = get_container_engine().get_seed()
         run_dc_shell_command(
-            f'/cephadm/box/box.py {verbose} --engine {engine()} host copy_cluster_ssh_key {BoxType.to_string(BoxType.SEED)} 1 --ips {ips}',
-            1,
-            BoxType.SEED,
+            f'/cephadm/box/box.py {verbose} --engine {engine()} host copy_cluster_ssh_key {seed.name} --ips {ips}',
+            seed
         )
 
 
@@ -88,10 +90,9 @@ class Host(Target):
     def set_args(self):
         self.parser.add_argument('action', choices=Host.actions)
         self.parser.add_argument(
-            'container_type', type=str, help='box_{type}_{index}'
-        )
-        self.parser.add_argument(
-            'container_index', type=str, help='box_{type}_{index}'
+            'container_name', 
+            type=str, 
+            help='box_{type}_{index}. In docker, type can be seed or hosts. In podman only hosts.'
         )
         self.parser.add_argument('--ips', nargs='*', help='List of host ips')
         self.parser.add_argument(
@@ -99,9 +100,9 @@ class Host(Target):
         )
 
     def setup_ssh(self):
-        type_ = Config.get('container_type')
-        index = Config.get('container_index')
-        _setup_ssh(type_, index)
+        container_name = Config.get('container_name')
+        engine = get_container_engine()
+        _setup_ssh(engine.get_container(container_name))
 
     def add_hosts(self):
         ips = Config.get('ips')
index 6ce3a1d6fea57c1f28c1bcb0b97944f52c0aa4c3..b57af42434ac38167a7214296fe358c28665b371 100644 (file)
@@ -12,6 +12,7 @@ from util import (
     get_orch_hosts,
     run_cephadm_shell_command,
     run_dc_shell_command,
+    get_container_engine,
     run_shell_command,
 )
 
@@ -100,6 +101,7 @@ def deploy_osds(count: int):
     osd_devs = load_osd_devices()
     hosts = get_orch_hosts()
     host_index = 0
+    seed = get_container_engine().get_seed()
     v = '-v' if Config.get('verbose') else ''
     for osd in osd_devs.values():
         deployed = False
@@ -108,8 +110,7 @@ def deploy_osds(count: int):
             hostname = hosts[host_index]['hostname']
             deployed = run_dc_shell_command(
                 f'/cephadm/box/box.py {v} osd deploy --data {osd["device"]} --hostname {hostname}',
-                1,
-                BoxType.SEED
+                seed
             )
             deployed = 'created osd' in deployed.lower() or 'already created?' in deployed.lower()
             print('Waiting 5 seconds to re-run deploy osd...')
index e2d802b21ed3e5b0df9be1298d0572cd0a351d6f..7dcf883f8a377c55ea375c34e2fcc0098f6bd0e9 100644 (file)
@@ -2,8 +2,10 @@ import json
 import os
 import subprocess
 import sys
-import enum
-from typing import Any, Callable, Dict
+import copy
+from abc import ABCMeta, abstractmethod
+from enum import Enum
+from typing import Any, Callable, Dict, List
 
 class Colors:
     HEADER = '\033[95m'
@@ -92,7 +94,26 @@ def ensure_inside_container(func) -> bool:
 def colored(msg, color: Colors):
     return color + msg + Colors.ENDC
 
-def run_shell_command(command: str, expect_error=False, verbose=True) -> str:
+class BoxType(str, Enum):
+  SEED = 'seed'
+  HOST = 'host'
+
+class HostContainer:
+    def __init__(self, _name, _type) -> None:
+        self._name: str = _name
+        self._type: BoxType = _type
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def type(self) -> BoxType:
+        return self._type
+    def __str__(self) -> str:
+        return f'{self.name} {self.type}'
+
+def run_shell_command(command: str, expect_error=False, verbose=True, expect_exit_code=0) -> str:
     if Config.get('verbose'):
         print(f'{colored("Running command", Colors.HEADER)}: {colored(command, Colors.OKBLUE)}')
 
@@ -119,43 +140,20 @@ def run_shell_command(command: str, expect_error=False, verbose=True) -> str:
     err += process.stderr.read().decode('latin1').strip()
     out = out.strip()
 
-    if process.returncode != 0 and not expect_error:
+    if process.returncode != 0 and not expect_error and process.returncode != expect_exit_code:
         err = colored(err, Colors.FAIL);
-        raise RuntimeError(f'Failed command: {command}\n{err}')
+        
+        raise RuntimeError(f'Failed command: {command}\n{err}\nexit code: {process.returncode}')
         sys.exit(1)
     return out
 
 
-class BoxType(enum.IntEnum):
-    SEED = 0 # where we bootstrap cephadm
-    HOST = 1
-    @staticmethod
-    def to_enum(value: str):
-        if value == 'seed':
-            return BoxType.SEED
-        elif value == 'host':
-            return BoxType.HOST
-        else:
-            print(f'Wrong container type {value}')
-            sys.exit(1)
-
-    @staticmethod
-    def to_string(box_type):
-        if box_type == BoxType.SEED:
-            return 'seed'
-        elif box_type == BoxType.HOST:
-            return 'host'
-        else:
-            print(f'Wrong container type {type_}')
-            sys.exit(1)
-
-
-def run_dc_shell_commands(index, box_type: BoxType, commands: str, expect_error=False) -> str:
+def run_dc_shell_commands(commands: str, container: HostContainer, expect_error=False) -> str:
     for command in commands.split('\n'):
         command = command.strip()
         if not command:
             continue
-        run_dc_shell_command(command.strip(), index, box_type, expect_error=expect_error)
+        run_dc_shell_command(command.strip(), container, expect_error=expect_error)
 
 def run_shell_commands(commands: str, expect_error=False) -> str:
     for command in commands.split('\n'):
@@ -179,20 +177,9 @@ def run_cephadm_shell_command(command: str, expect_error=False) -> str:
 
 
 def run_dc_shell_command(
-    command: str, index: int, box_type: BoxType, expect_error=False
+        command: str, container: HostContainer, expect_error=False
 ) -> str:
-    box_type_str = 'box_hosts'
-    if box_type == BoxType.SEED:
-        index = 0
-        if engine() == 'docker':
-            box_type_str = 'seed'
-            index = 1
-
-    container_id = get_container_id(f'{box_type_str}_{index}')
-    print(container_id)
-    out = run_shell_command(
-        f'{engine()} exec -it {container_id} {command}', expect_error
-    )
+    out = get_container_engine().run_exec(container, command, expect_error=expect_error)
     return out
 
 def inside_container() -> bool:
@@ -246,8 +233,189 @@ def get_orch_hosts():
     if inside_container():
         orch_host_ls_out = run_cephadm_shell_command('ceph orch host ls --format json')
     else:
-        orch_host_ls_out = run_dc_shell_command(f'cephadm shell --keyring /etc/ceph/ceph.keyring --config /etc/ceph/ceph.conf -- ceph orch host ls --format json', 1, BoxType.SEED)
+        orch_host_ls_out = run_dc_shell_command(f'cephadm shell --keyring /etc/ceph/ceph.keyring --config /etc/ceph/ceph.conf -- ceph orch host ls --format json', 
+                                                get_container_engine().get_seed())
         sp = orch_host_ls_out.split('\n')
         orch_host_ls_out  = sp[len(sp) - 1]
     hosts = json.loads(orch_host_ls_out)
     return hosts
+
+
+class ContainerEngine(metaclass=ABCMeta):
+    @property
+    @abstractmethod
+    def command(self) -> str: pass
+
+    @property
+    @abstractmethod
+    def seed_name(self) -> str: pass
+
+    @property
+    @abstractmethod
+    def dockerfile(self) -> str: pass
+
+    @property
+    def host_name_prefix(self) -> str: 
+        return 'box_hosts_'
+
+    @abstractmethod
+    def up(self, hosts: int): pass
+
+    def run_exec(self, container: HostContainer, command: str, expect_error: bool = False):
+        return run_shell_command(' '.join([self.command, 'exec', container.name, command]), 
+                                 expect_error=expect_error) 
+
+    def run(self, engine_command: str, expect_error: bool = False):
+        return run_shell_command(' '.join([self.command, engine_command]), expect_error=expect_error) 
+
+    def get_containers(self) -> List[HostContainer]:
+        ps_out = json.loads(run_shell_command('podman ps --format json'))
+        containers = [] 
+        for container in ps_out:
+            if not container['Names']:
+                raise RuntimeError(f'Container {container} missing name')
+            name = container['Names'][0]
+            if name == self.seed_name:
+                containers.append(HostContainer(name, BoxType.SEED))
+            elif name.startswith(self.host_name_prefix):
+                containers.append(HostContainer(name, BoxType.HOST))
+        return containers
+
+    def get_seed(self) -> HostContainer:
+        for container in self.get_containers():
+            if container.type == BoxType.SEED:
+                return container
+        raise RuntimeError('Missing seed container')
+
+    def get_container(self, container_name: str):
+        containers = self.get_containers()
+        for container in containers:
+            if container.name == container_name:
+                return container
+        return None
+
+
+    def restart(self):
+        pass
+
+
+class DockerEngine(ContainerEngine):
+    command = 'docker'
+    seed_name = 'seed'
+    dockerfile = 'DockerfileDocker'
+
+    def restart(self):
+        run_shell_command('systemctl restart docker')
+
+    def up(self, hosts: int):
+        dcflags = f'-f {Config.get("docker_yaml")}'
+        if not os.path.exists('/sys/fs/cgroup/cgroup.controllers'):
+            dcflags += f' -f {Config.get("docker_v1_yaml")}'
+        run_shell_command(f'{engine_compose()} {dcflags} up --scale hosts={hosts} -d')
+
+class PodmanEngine(ContainerEngine):
+    command = 'podman'
+    seed_name = 'box_hosts_0'
+    dockerfile = 'DockerfilePodman'
+
+    CAPS = [
+            "SYS_ADMIN",
+            "NET_ADMIN",
+            "SYS_TIME",
+            "SYS_RAWIO",
+            "MKNOD",
+            "NET_RAW",
+            "SETUID",
+            "SETGID",
+            "CHOWN",
+            "SYS_PTRACE",
+            "SYS_TTY_CONFIG",
+            "CAP_AUDIT_WRITE",
+            "CAP_AUDIT_CONTROL",
+            ]
+
+    VOLUMES = [
+                '../../../:/ceph:z',
+                '../:/cephadm:z',
+                '/run/udev:/run/udev',
+                '/sys/dev/block:/sys/dev/block',
+                '/sys/fs/cgroup:/sys/fs/cgroup:ro',
+                '/dev/fuse:/dev/fuse',
+                '/dev/disk:/dev/disk',
+                '/sys/devices/virtual/block:/sys/devices/virtual/block',
+                '/sys/block:/dev/block',
+                '/dev/mapper:/dev/mapper',
+                '/dev/mapper/control:/dev/mapper/control',
+            ]
+
+    TMPFS = ['/run', '/tmp']
+
+    # FIXME: right now we are assuming every service will be exposed through the seed, but this is far
+    # from the truth. Services can be deployed on different hosts so we need a system to manage this.
+    SEED_PORTS = [
+            8443, # dashboard
+            3000, # grafana
+            9093, # alertmanager
+            9095  # prometheus
+            ]
+
+
+    def setup_podman_env(self, hosts: int = 1, osd_devs={}):
+        network_name = 'box_network'
+        networks = run_shell_command('podman network ls')
+        if network_name not in networks:
+            run_shell_command(f'podman network create -d bridge {network_name}')
+
+        args = [
+                '--group-add', 'keep-groups', 
+                '--device', '/dev/fuse' ,
+                '-it' ,
+                '-d',
+                '-e', 'CEPH_BRANCH=main',
+                '--stop-signal', 'RTMIN+3'
+                ]
+
+        for cap in self.CAPS:
+            args.append('--cap-add')
+            args.append(cap)
+
+        for volume in self.VOLUMES:
+            args.append('-v')
+            args.append(volume)
+
+        for tmp in self.TMPFS:
+            args.append('--tmpfs')
+            args.append(tmp)
+
+
+        for osd_dev in osd_devs.values():
+            device = osd_dev["device"]
+            args.append('--device')
+            args.append(f'{device}:{device}')
+
+
+        for host in range(hosts+1): # 0 will be the seed
+            options = copy.copy(args)
+            options.append('--name')
+            options.append(f'box_hosts_{host}')
+            options.append('--network')
+            options.append(f'{network_name}')
+            if host == 0:
+                for port in self.SEED_PORTS:
+                    options.append('-p')
+                    options.append(f'{port}:{port}')
+
+            options.append('cephadm-box')
+            options = ' '.join(options)
+
+            run_shell_command(f'podman run {options}')
+
+    def up(self, hosts: int):
+        import osd
+        self.setup_podman_env(hosts=hosts, osd_devs=osd.load_osd_devices())
+
+def get_container_engine() -> ContainerEngine:
+    if engine() == 'docker':
+        return DockerEngine()
+    else:
+        return PodmanEngine()