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.
- ceph_devstack/Dockerfile.selinux +20 -0
- ceph_devstack/__init__.py +187 -0
- ceph_devstack/ceph_devstack.pp +0 -0
- ceph_devstack/ceph_devstack.te +127 -0
- ceph_devstack/cli.py +64 -0
- ceph_devstack/config.toml +24 -0
- ceph_devstack/exec.py +93 -0
- ceph_devstack/host.py +154 -0
- ceph_devstack/logging.conf +30 -0
- ceph_devstack/py.typed +0 -0
- ceph_devstack/requirements.py +277 -0
- ceph_devstack/resources/__init__.py +115 -0
- ceph_devstack/resources/ceph/__init__.py +266 -0
- ceph_devstack/resources/ceph/containers.py +419 -0
- ceph_devstack/resources/ceph/exceptions.py +3 -0
- ceph_devstack/resources/ceph/requirements.py +90 -0
- ceph_devstack/resources/ceph/utils.py +45 -0
- ceph_devstack/resources/container.py +171 -0
- ceph_devstack/resources/misc.py +15 -0
- ceph_devstack-0.1.0.dist-info/METADATA +222 -0
- ceph_devstack-0.1.0.dist-info/RECORD +44 -0
- ceph_devstack-0.1.0.dist-info/WHEEL +5 -0
- ceph_devstack-0.1.0.dist-info/entry_points.txt +2 -0
- ceph_devstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- ceph_devstack-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +9 -0
- tests/resources/__init__.py +0 -0
- tests/resources/ceph/__init__.py +0 -0
- tests/resources/ceph/fixtures/__init__.py +0 -0
- tests/resources/ceph/fixtures/testnode-config.toml +2 -0
- tests/resources/ceph/test_cephdevstack_core.py +459 -0
- tests/resources/ceph/test_devstack.py +182 -0
- tests/resources/ceph/test_env_vars.py +110 -0
- tests/resources/ceph/test_requirements_ceph.py +262 -0
- tests/resources/ceph/test_ssh_keypair.py +109 -0
- tests/resources/ceph/test_testnode.py +36 -0
- tests/resources/test_container.py +247 -0
- tests/resources/test_misc.py +46 -0
- tests/resources/test_podmanresource.py +59 -0
- tests/test_config.py +120 -0
- tests/test_deep_merge.py +71 -0
- tests/test_parse_args.py +228 -0
- 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
|