ceph-devstack 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. ceph_devstack/Dockerfile.selinux +20 -0
  2. ceph_devstack/__init__.py +187 -0
  3. ceph_devstack/ceph_devstack.pp +0 -0
  4. ceph_devstack/ceph_devstack.te +127 -0
  5. ceph_devstack/cli.py +64 -0
  6. ceph_devstack/config.toml +24 -0
  7. ceph_devstack/exec.py +93 -0
  8. ceph_devstack/host.py +154 -0
  9. ceph_devstack/logging.conf +30 -0
  10. ceph_devstack/py.typed +0 -0
  11. ceph_devstack/requirements.py +277 -0
  12. ceph_devstack/resources/__init__.py +115 -0
  13. ceph_devstack/resources/ceph/__init__.py +266 -0
  14. ceph_devstack/resources/ceph/containers.py +419 -0
  15. ceph_devstack/resources/ceph/exceptions.py +3 -0
  16. ceph_devstack/resources/ceph/requirements.py +90 -0
  17. ceph_devstack/resources/ceph/utils.py +45 -0
  18. ceph_devstack/resources/container.py +171 -0
  19. ceph_devstack/resources/misc.py +15 -0
  20. ceph_devstack-0.1.0.dist-info/METADATA +222 -0
  21. ceph_devstack-0.1.0.dist-info/RECORD +44 -0
  22. ceph_devstack-0.1.0.dist-info/WHEEL +5 -0
  23. ceph_devstack-0.1.0.dist-info/entry_points.txt +2 -0
  24. ceph_devstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  25. ceph_devstack-0.1.0.dist-info/top_level.txt +2 -0
  26. tests/__init__.py +0 -0
  27. tests/conftest.py +9 -0
  28. tests/resources/__init__.py +0 -0
  29. tests/resources/ceph/__init__.py +0 -0
  30. tests/resources/ceph/fixtures/__init__.py +0 -0
  31. tests/resources/ceph/fixtures/testnode-config.toml +2 -0
  32. tests/resources/ceph/test_cephdevstack_core.py +459 -0
  33. tests/resources/ceph/test_devstack.py +182 -0
  34. tests/resources/ceph/test_env_vars.py +110 -0
  35. tests/resources/ceph/test_requirements_ceph.py +262 -0
  36. tests/resources/ceph/test_ssh_keypair.py +109 -0
  37. tests/resources/ceph/test_testnode.py +36 -0
  38. tests/resources/test_container.py +247 -0
  39. tests/resources/test_misc.py +46 -0
  40. tests/resources/test_podmanresource.py +59 -0
  41. tests/test_config.py +120 -0
  42. tests/test_deep_merge.py +71 -0
  43. tests/test_parse_args.py +228 -0
  44. tests/test_requirements_core.py +495 -0
@@ -0,0 +1,20 @@
1
+ FROM quay.io/centos/centos:stream9 as build
2
+ RUN \
3
+ dnf install -y \
4
+ policycoreutils-devel \
5
+ selinux-policy-devel \
6
+ && dnf clean all
7
+ COPY ceph_devstack/ceph_devstack.te /ceph_devstack/
8
+ RUN \
9
+ mkdir -p /ceph_devstack && \
10
+ cd /ceph_devstack && \
11
+ make \
12
+ -f \
13
+ /usr/share/selinux/devel/Makefile \
14
+ ceph_devstack.pp
15
+
16
+ FROM scratch
17
+ WORKDIR /ceph_devstack
18
+ COPY --from=build /ceph_devstack/ceph_devstack.pp /ceph_devstack.pp
19
+ COPY --from=build /bin/cat /bin/cat
20
+ ENTRYPOINT /bin/cat
@@ -0,0 +1,187 @@
1
+ import argparse
2
+ import logging.config
3
+ import tomlkit
4
+ import tomlkit.items
5
+ import tomlkit.exceptions
6
+
7
+ from pathlib import Path
8
+ from typing import List
9
+
10
+
11
+ VERBOSE = 15
12
+ logging.addLevelName(15, "VERBOSE")
13
+ logging.config.fileConfig(Path(__file__).parent / "logging.conf")
14
+ logger = logging.getLogger("ceph-devstack")
15
+
16
+ PROJECT_ROOT = Path(__file__).parent
17
+ DEFAULT_CONFIG_PATH = Path("~/.config/ceph-devstack/config.toml")
18
+
19
+
20
+ def parse_args(args: List[str]) -> argparse.Namespace:
21
+ parser = argparse.ArgumentParser(
22
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
23
+ )
24
+ parser.add_argument(
25
+ "--dry-run",
26
+ action="store_true",
27
+ default=False,
28
+ help="Instead of running commands, print them",
29
+ )
30
+ parser.add_argument(
31
+ "-v",
32
+ "--verbose",
33
+ action="store_true",
34
+ default=False,
35
+ help="Be more verbose",
36
+ )
37
+ parser.add_argument(
38
+ "-c",
39
+ "--config-file",
40
+ type=Path,
41
+ default=DEFAULT_CONFIG_PATH,
42
+ help="Path to the ceph-devstack config file",
43
+ )
44
+ subparsers = parser.add_subparsers(dest="command")
45
+ parser_config = subparsers.add_parser("config", help="Get or set config items")
46
+ subparsers_config = parser_config.add_subparsers(dest="config_op")
47
+ subparsers_config.add_parser("dump", help="show the configuration")
48
+ parser_config_get = subparsers_config.add_parser("get")
49
+ parser_config_get.add_argument("name")
50
+ parser_config_set = subparsers_config.add_parser("set")
51
+ parser_config_set.add_argument("name")
52
+ parser_config_set.add_argument("value")
53
+ parser_doc = subparsers.add_parser(
54
+ "doctor", help="Check that the system meets requirements"
55
+ )
56
+ parser_doc.add_argument(
57
+ "--fix",
58
+ action="store_true",
59
+ default=False,
60
+ help="Apply suggested fixes for issues found",
61
+ )
62
+ parser_pull = subparsers.add_parser("pull", help="Pull container images")
63
+ parser_pull.add_argument(
64
+ "image",
65
+ nargs="*",
66
+ help="Specific image(s) to pull",
67
+ )
68
+ parser_build = subparsers.add_parser("build", help="Build container images")
69
+ parser_build.add_argument(
70
+ "image",
71
+ nargs="*",
72
+ help="Specific image(s) to build",
73
+ )
74
+ parser_create = subparsers.add_parser(
75
+ "create",
76
+ help="Create the cluster",
77
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
78
+ )
79
+ parser_create.add_argument(
80
+ "-b",
81
+ "--build",
82
+ action="store_true",
83
+ default=False,
84
+ help="Build images before creating",
85
+ )
86
+ parser_create.add_argument(
87
+ "-w",
88
+ "--wait",
89
+ action="store_true",
90
+ default=False,
91
+ help="Leave the cluster running - and don't auto-schedule anything",
92
+ )
93
+ subparsers.add_parser("remove", help="Destroy the cluster")
94
+ subparsers.add_parser("start", help="Start the cluster")
95
+ subparsers.add_parser("stop", help="Stop the cluster")
96
+ subparsers.add_parser(
97
+ "watch", help="Monitor the cluster, recreating containers as necessary"
98
+ )
99
+ parser_wait = subparsers.add_parser(
100
+ "wait",
101
+ help="Wait for the specified container to exit. Exit with its exit code.",
102
+ )
103
+ parser_wait.add_argument(
104
+ "container",
105
+ help="The container to wait for",
106
+ )
107
+ parser_log = subparsers.add_parser("logs", help="Dump teuthology logs")
108
+ parser_log.add_argument("-r", "--run-name", type=str, default=None)
109
+ parser_log.add_argument("-j", "--job-id", type=str, default=None)
110
+ parser_log.add_argument(
111
+ "--locate",
112
+ action=argparse.BooleanOptionalAction,
113
+ help="Display log file path instead of contents",
114
+ )
115
+ return parser.parse_args(args)
116
+
117
+
118
+ def deep_merge(*maps):
119
+ result = {}
120
+ for mapping in maps:
121
+ for k, v in mapping.items():
122
+ if isinstance(v, dict):
123
+ v = deep_merge(result.get(k, {}), v)
124
+ result[k] = v
125
+ return result
126
+
127
+
128
+ class Config(dict):
129
+ __slots__ = ["user_obj", "user_path"]
130
+
131
+ def load(self, config_path: Path | None = None):
132
+ parsed = tomlkit.parse((Path(__file__).parent / "config.toml").read_text())
133
+ self.update(parsed)
134
+ if config_path:
135
+ self.user_path = config_path.expanduser()
136
+ if self.user_path.exists():
137
+ self.user_obj: dict = tomlkit.parse(self.user_path.read_text()) or {}
138
+ self.update(deep_merge(config, self.user_obj))
139
+ elif self.user_path != DEFAULT_CONFIG_PATH.expanduser():
140
+ raise OSError(f"Config file at {self.user_path} not found!")
141
+ else:
142
+ self.user_obj = {}
143
+
144
+ def dump(self):
145
+ return tomlkit.dumps(self)
146
+
147
+ def get_value(self, name: str) -> str:
148
+ path = name.split(".")
149
+ obj = config
150
+ i = 0
151
+ while i < len(path):
152
+ sub_path = path[i]
153
+ try:
154
+ obj = obj[sub_path]
155
+ except KeyError:
156
+ logger.error(f"{name} not found in config")
157
+ raise
158
+ i += 1
159
+ if isinstance(obj, (str, int, bool)):
160
+ return str(obj)
161
+ return tomlkit.dumps(obj).strip()
162
+
163
+ def set_value(self, name: str, value: str) -> None:
164
+ path = name.split(".")
165
+ obj = self.user_obj
166
+ i = 0
167
+ last_index = len(path) - 1
168
+ try:
169
+ item = tomlkit.value(value)
170
+ except (
171
+ tomlkit.exceptions.UnexpectedCharError,
172
+ tomlkit.exceptions.InternalParserError,
173
+ ):
174
+ item = tomlkit.item(value)
175
+ while i <= last_index:
176
+ if i < last_index:
177
+ obj = obj.setdefault(path[i], {})
178
+ elif i == last_index:
179
+ obj[path[i]] = item
180
+ self.update(self.user_obj)
181
+ self.user_path.parent.mkdir(exist_ok=True)
182
+ self.user_path.write_text(tomlkit.dumps(self.user_obj).strip())
183
+ i += 1
184
+
185
+
186
+ config = Config()
187
+ config.load()
Binary file
@@ -0,0 +1,127 @@
1
+ module ceph_devstack 1.0;
2
+
3
+ require {
4
+ type unconfined_t;
5
+ type container_t;
6
+ type container_init_t;
7
+ type proc_t;
8
+ type sysfs_t;
9
+ type tmpfs_t;
10
+ type user_tmp_t;
11
+ type devpts_t;
12
+ class filesystem mount;
13
+ class filesystem unmount;
14
+
15
+ type null_device_t;
16
+ type zero_device_t;
17
+ type devtty_t;
18
+ type random_device_t;
19
+ type urandom_device_t;
20
+ class chr_file mounton;
21
+
22
+ class dir mounton;
23
+
24
+ type proc_kcore_t;
25
+ class file mounton;
26
+
27
+ type device_t;
28
+ class filesystem remount;
29
+
30
+ type sysctl_irq_t;
31
+
32
+ type fixed_disk_device_t;
33
+ class blk_file setattr;
34
+ class blk_file mounton;
35
+
36
+ type fs_t;
37
+
38
+ type cgroup_t;
39
+
40
+ type sysctl_t;
41
+
42
+ type unlabeled_t;
43
+ class chr_file getattr;
44
+ class chr_file setattr;
45
+ class chr_file unlink;
46
+
47
+ type kernel_t;
48
+ class system syslog_read;
49
+
50
+ type iptables_t;
51
+ type container_file_t;
52
+ class dir ioctl;
53
+
54
+ type init_t;
55
+ type unconfined_service_t;
56
+ class process siginh;
57
+
58
+ type chkpwd_t;
59
+ type user_devpts_t;
60
+ class chr_file { read write };
61
+
62
+ type proc_kmsg_t;
63
+
64
+ type ramfs_t;
65
+
66
+ type sysctl_kernel_t;
67
+ class dir read;
68
+
69
+ type system_map_t;
70
+
71
+ type mtrr_device_t;
72
+
73
+ class bpf prog_load;
74
+ class bpf map_create;
75
+
76
+ type lvm_control_t;
77
+ type fuse_device_t;
78
+
79
+ type tun_tap_device_t;
80
+
81
+ class sock_file write;
82
+ class unix_stream_socket connectto;
83
+ }
84
+
85
+ #============= container_init_t ==============
86
+ allow container_init_t proc_t:filesystem { mount unmount remount };
87
+ allow container_init_t sysfs_t:filesystem { mount unmount remount };
88
+ allow container_init_t tmpfs_t:filesystem { mount unmount remount };
89
+ allow container_init_t devpts_t:filesystem mount;
90
+ allow container_init_t null_device_t:chr_file { mounton setattr };
91
+ allow container_init_t zero_device_t:chr_file mounton;
92
+ allow container_init_t devtty_t:chr_file mounton;
93
+ allow container_init_t random_device_t:chr_file mounton;
94
+ allow container_init_t urandom_device_t:chr_file mounton;
95
+ allow container_init_t proc_t:dir mounton;
96
+ allow container_init_t proc_kcore_t:file mounton;
97
+ allow container_init_t device_t:filesystem { unmount remount};
98
+ allow container_init_t proc_t:file mounton;
99
+ allow container_init_t sysctl_irq_t:dir mounton;
100
+ allow container_init_t fixed_disk_device_t:blk_file setattr;
101
+ allow container_init_t fs_t:filesystem remount;
102
+ allow container_init_t cgroup_t:filesystem remount;
103
+ allow container_init_t sysctl_t:dir mounton;
104
+ allow container_init_t sysctl_t:file mounton;
105
+ allow container_init_t unlabeled_t:chr_file { getattr unlink };
106
+ allow container_init_t kernel_t:system syslog_read;
107
+ allow iptables_t container_file_t:dir ioctl;
108
+ allow init_t unconfined_service_t:process siginh;
109
+ allow chkpwd_t user_devpts_t:chr_file { read write };
110
+ allow container_init_t proc_kmsg_t:file mounton;
111
+ allow container_init_t ramfs_t:filesystem mount;
112
+ allow init_t chkpwd_t:process siginh;
113
+ allow container_init_t ramfs_t:dir read;
114
+ allow container_init_t sysctl_kernel_t:file mounton;
115
+ allow container_init_t devpts_t:filesystem remount;
116
+ allow container_init_t system_map_t:file mounton;
117
+ allow container_init_t system_map_t:file mounton;
118
+ allow container_init_t mtrr_device_t:file mounton;
119
+ allow container_init_t self:bpf prog_load;
120
+ allow container_init_t self:bpf map_create;
121
+ allow container_init_t lvm_control_t:chr_file mounton;
122
+ allow container_init_t fuse_device_t:chr_file mounton;
123
+ allow container_init_t fixed_disk_device_t:blk_file mounton;
124
+ allow container_init_t tun_tap_device_t:chr_file mounton;
125
+
126
+ allow container_t user_tmp_t:sock_file write;
127
+ allow container_t unconfined_t:unix_stream_socket connectto;
ceph_devstack/cli.py ADDED
@@ -0,0 +1,64 @@
1
+ import asyncio
2
+ import logging
3
+ import sys
4
+
5
+ from pathlib import Path
6
+
7
+ from ceph_devstack import config, logger, parse_args, VERBOSE
8
+ from ceph_devstack.requirements import check_requirements
9
+ from ceph_devstack.resources.ceph import CephDevStack
10
+
11
+ CONFIG_HANDLERS = {
12
+ "dump": lambda config, args: print(config.dump()),
13
+ "get": lambda config, args: print(config.get_value(args.name)),
14
+ "set": lambda config, args: print(config.set_value(args.name, args.value)),
15
+ }
16
+
17
+ COMMAND_HANDLERS = {
18
+ "doctor": None,
19
+ "apply": lambda args, obj: obj.apply(args.command),
20
+ "pull": lambda _, obj: obj.pull(),
21
+ "build": lambda _, obj: obj.build(),
22
+ "create": lambda _, obj: obj.create(),
23
+ "remove": lambda _, obj: obj.remove(),
24
+ "start": lambda _, obj: obj.start(),
25
+ "stop": lambda _, obj: obj.stop(),
26
+ "watch": lambda _, obj: obj.watch(),
27
+ "wait": lambda args, obj: obj.wait(container_name=args.container),
28
+ "logs": lambda args, obj: obj.logs(
29
+ run_name=args.run_name, job_id=args.job_id, locate=args.locate
30
+ ),
31
+ }
32
+
33
+
34
+ def main() -> int:
35
+ args = parse_args(sys.argv[1:])
36
+ config.load(args.config_file)
37
+ if args.verbose:
38
+ for handler in logging.getLogger("root").handlers:
39
+ if not isinstance(handler, logging.FileHandler):
40
+ handler.setLevel(VERBOSE)
41
+ if args.command == "config":
42
+ CONFIG_HANDLERS[args.config_op](config, args)
43
+ return 0
44
+ config["args"] = vars(args)
45
+ data_path = Path(config["data_dir"]).expanduser()
46
+ data_path.mkdir(parents=True, exist_ok=True)
47
+ obj = CephDevStack()
48
+
49
+ async def run():
50
+ if not await asyncio.gather(
51
+ check_requirements(),
52
+ obj.check_requirements(),
53
+ ):
54
+ logger.error("Requirements not met!")
55
+ return 1
56
+ handler = COMMAND_HANDLERS.get(args.command)
57
+ if handler:
58
+ return await handler(args, obj)
59
+
60
+ try:
61
+ sys.exit(asyncio.run(run()))
62
+ except KeyboardInterrupt:
63
+ logger.debug("Exiting!")
64
+ return 130 # 128 + SIGINT
@@ -0,0 +1,24 @@
1
+ data_dir = "~/.local/share/ceph-devstack"
2
+
3
+ [containers.archive]
4
+ image = "python:alpine"
5
+
6
+ [containers.beanstalk]
7
+ image = "quay.io/ceph-infra/teuthology-beanstalkd:main"
8
+
9
+ [containers.paddles]
10
+ image = "quay.io/ceph-infra/paddles:main"
11
+
12
+ [containers.postgres]
13
+ image = "quay.io/ceph-infra/teuthology-postgresql:latest"
14
+
15
+ [containers.pulpito]
16
+ image = "quay.io/ceph-infra/pulpito:main"
17
+
18
+ [containers.testnode]
19
+ count = 3
20
+ loop_device_size = "5G"
21
+ image = "quay.io/ceph-infra/teuthology-testnode:main"
22
+
23
+ [containers.teuthology]
24
+ image = "quay.io/ceph-infra/teuthology-dev:main"
ceph_devstack/exec.py ADDED
@@ -0,0 +1,93 @@
1
+ import asyncio
2
+ from asyncio.subprocess import SubprocessStreamProtocol
3
+ import functools
4
+ import os
5
+ import pathlib
6
+ import subprocess
7
+
8
+ from typing import Dict, List, Optional
9
+
10
+ from ceph_devstack import logger, VERBOSE
11
+
12
+
13
+ class LoggingStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
14
+ def __init__(self, limit, loop, log_level):
15
+ self.log_level = log_level
16
+ super().__init__(limit=limit, loop=loop)
17
+
18
+ def pipe_data_received(self, fd, data):
19
+ logger.log(
20
+ self.log_level,
21
+ (data.decode() if isinstance(data, bytes) else str(data)).rstrip("\n"),
22
+ )
23
+ super().pipe_data_received(fd, data)
24
+
25
+
26
+ class Command:
27
+ def __init__(
28
+ self,
29
+ args: List[str],
30
+ cwd: Optional[pathlib.Path] = None,
31
+ env: Optional[Dict] = None,
32
+ stream_output: bool = False,
33
+ ):
34
+ self.args = args
35
+ self.env = os.environ | (env or {})
36
+ self.kwargs: Dict = {
37
+ "stdout": asyncio.subprocess.PIPE,
38
+ "stderr": asyncio.subprocess.PIPE,
39
+ }
40
+ if cwd:
41
+ self.kwargs.update(cwd=cwd)
42
+ self.stream_output = stream_output
43
+
44
+ def _make_log_msg(self) -> str:
45
+ msg = "> " + " ".join(self.args)
46
+ if (cwd := str(self.kwargs.get("cwd", "."))) != ".":
47
+ msg = f"{msg} cwd='{cwd}'"
48
+ return msg
49
+
50
+ def run(self) -> subprocess.Popen:
51
+ logger.log(VERBOSE, self._make_log_msg())
52
+ proc = subprocess.Popen(
53
+ args=self.args,
54
+ env=self.env,
55
+ **self.kwargs,
56
+ )
57
+ proc.wait()
58
+ return proc
59
+
60
+ async def arun(self) -> asyncio.subprocess.Process:
61
+ logger.log(VERBOSE, self._make_log_msg())
62
+ loop = asyncio.get_running_loop()
63
+ protocol_factory: (
64
+ functools.partial[SubprocessStreamProtocol]
65
+ | functools.partial[LoggingStreamProtocol]
66
+ )
67
+ if self.stream_output:
68
+ protocol_factory = functools.partial(
69
+ LoggingStreamProtocol,
70
+ limit=2**16,
71
+ loop=loop,
72
+ log_level=VERBOSE,
73
+ )
74
+ else:
75
+ protocol_factory = functools.partial(
76
+ asyncio.subprocess.SubprocessStreamProtocol,
77
+ limit=2**16,
78
+ loop=loop,
79
+ )
80
+ transport, protocol = await loop.subprocess_exec(
81
+ protocol_factory,
82
+ *self.args,
83
+ env=self.env,
84
+ **self.kwargs,
85
+ )
86
+ return asyncio.subprocess.Process(
87
+ transport,
88
+ protocol,
89
+ loop,
90
+ )
91
+
92
+ def __str__(self):
93
+ return " ".join(self.args)
ceph_devstack/host.py ADDED
@@ -0,0 +1,154 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import pathlib
5
+ import socket
6
+ import sys
7
+ import yaml
8
+
9
+ from packaging.version import parse as parse_version, Version
10
+ from typing import Dict, List, Optional, Union
11
+
12
+ from .exec import Command
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class Host:
18
+ type = "local"
19
+
20
+ def cmd(
21
+ self,
22
+ args: List[str],
23
+ cwd: Optional[pathlib.Path] = None,
24
+ env: Optional[Dict] = None,
25
+ stream_output: bool = False,
26
+ ) -> Command:
27
+ return Command(
28
+ args,
29
+ cwd=cwd,
30
+ env=env,
31
+ stream_output=stream_output,
32
+ )
33
+
34
+ def run(
35
+ self,
36
+ args: List[str],
37
+ cwd: Optional[pathlib.Path] = None,
38
+ env: Optional[Dict] = None,
39
+ ):
40
+ return self.cmd(args, cwd=cwd, env=env).run()
41
+
42
+ async def arun(
43
+ self,
44
+ args: List[str],
45
+ cwd: Optional[pathlib.Path] = None,
46
+ env: Optional[Dict] = None,
47
+ stream_output: bool = False,
48
+ ) -> asyncio.subprocess.Process:
49
+ return await self.cmd(
50
+ args, cwd=cwd, env=env, stream_output=stream_output
51
+ ).arun()
52
+
53
+ def path_exists(self, path: Union[str, pathlib.Path]):
54
+ if isinstance(path, pathlib.Path):
55
+ return path.exists()
56
+ return os.path.exists(path)
57
+
58
+ def hostname(self) -> str:
59
+ name = socket.getfqdn()
60
+ try:
61
+ socket.gethostbyname(name)
62
+ return name
63
+ except socket.gaierror:
64
+ return "localhost"
65
+
66
+ def kernel_version(self) -> Version:
67
+ if not hasattr(self, "_kernel_version"):
68
+ proc = self.run(["uname", "-r"])
69
+ assert proc.stdout is not None
70
+ assert proc.wait() == 0, "`uname -r` failed?!"
71
+ raw_version = proc.stdout.read().decode().strip()
72
+ self._kernel_version = parse_version(raw_version.split("-")[0])
73
+ return self._kernel_version
74
+
75
+ def os_type(self) -> str:
76
+ if not hasattr(self, "_os_type"):
77
+ proc = self.run(["bash", "-c", ". /etc/os-release && echo $ID"])
78
+ assert proc.stdout is not None
79
+ assert proc.wait() == 0, "is /etc/os-release missing?"
80
+ self._os_type = proc.stdout.read().decode().strip().lower()
81
+ return self._os_type
82
+
83
+ async def podman_info(self, force: bool = False) -> Dict:
84
+ if force or not hasattr(self, "_podman_info"):
85
+ proc = await self.arun(["podman", "info"])
86
+ assert proc.stdout is not None
87
+ await proc.wait()
88
+ stdout = await proc.stdout.read()
89
+ self._podman_info = yaml.safe_load(stdout.decode().strip())
90
+ return self._podman_info
91
+
92
+ async def selinux_enforcing(self) -> bool:
93
+ proc = await host.arun(["cat", "/sys/fs/selinux/enforce"])
94
+ assert proc.stdout is not None
95
+ await proc.wait()
96
+ out = (await proc.stdout.read()).decode()
97
+ return proc.returncode == 0 and out == "1"
98
+
99
+ async def check_selinux_bool(self, name: str):
100
+ proc = await host.arun(["getsebool", name])
101
+ assert proc.stdout is not None
102
+ out = await proc.stdout.read()
103
+ return out.decode().strip() == f"{name} --> on"
104
+
105
+ async def get_sysctl_value(self, name: str) -> int:
106
+ proc = await host.arun(["sysctl", "-b", name])
107
+ assert proc.stdout is not None
108
+ out = await proc.stdout.read()
109
+ return int(out.decode().strip())
110
+
111
+ async def apparmor_enabled(self) -> bool:
112
+ try:
113
+ proc = await host.arun(["aa-enabled", "-q"])
114
+ except FileNotFoundError:
115
+ return False
116
+ return await proc.wait() == 0
117
+
118
+
119
+ class LocalHost(Host):
120
+ pass
121
+
122
+
123
+ class RemoteHost(Host):
124
+ type = "remote"
125
+ base_args = ["podman", "machine", "ssh", "--"]
126
+
127
+ def cmd(
128
+ self,
129
+ args: List[str],
130
+ cwd: Optional[pathlib.Path] = None,
131
+ env: Optional[Dict] = None,
132
+ stream_output: bool = False,
133
+ ):
134
+ if args[0] != "podman":
135
+ args = self.base_args + args
136
+ return super().cmd(args, cwd=cwd, env=env, stream_output=stream_output)
137
+
138
+ def path_exists(self, path: Union[str, pathlib.Path]):
139
+ path = os.path.expanduser(path)
140
+ proc = host.run(["ls", path])
141
+ return proc.returncode == 0
142
+
143
+ def hostname(self) -> str:
144
+ proc = self.run(["hostname"])
145
+ assert proc.stdout is not None
146
+ return proc.stdout.read().decode().strip()
147
+
148
+
149
+ local_host = LocalHost()
150
+
151
+ if sys.platform == "darwin":
152
+ host = RemoteHost()
153
+ else:
154
+ host = local_host
@@ -0,0 +1,30 @@
1
+ [loggers]
2
+ keys=root
3
+
4
+ [handlers]
5
+ keys=consoleHandler,fileHandler
6
+
7
+ [formatters]
8
+ keys=bareFormatter,simpleFormatter
9
+
10
+ [logger_root]
11
+ level=DEBUG
12
+ handlers=consoleHandler,fileHandler
13
+
14
+ [handler_consoleHandler]
15
+ class=StreamHandler
16
+ level=INFO
17
+ formatter=bareFormatter
18
+ args=(sys.stdout,)
19
+
20
+ [handler_fileHandler]
21
+ class=FileHandler
22
+ level=DEBUG
23
+ formatter=simpleFormatter
24
+ args=('/tmp/ceph-devstack.log', 'w')
25
+
26
+ [formatter_bareFormatter]
27
+ format=%(message)s
28
+
29
+ [formatter_simpleFormatter]
30
+ format=%(asctime)s %(levelname)s:%(name)s:%(message)s
ceph_devstack/py.typed ADDED
File without changes