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,419 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from ceph_devstack import config, DEFAULT_CONFIG_PATH
|
|
8
|
+
from ceph_devstack.host import host
|
|
9
|
+
from ceph_devstack.resources.container import Container
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ARCHIVE_MOUNT_SUFFIX = "" if sys.platform == "darwin" else ":z"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Postgres(Container):
|
|
16
|
+
create_cmd = [
|
|
17
|
+
"podman",
|
|
18
|
+
"container",
|
|
19
|
+
"create",
|
|
20
|
+
"-i",
|
|
21
|
+
"--network",
|
|
22
|
+
"ceph-devstack",
|
|
23
|
+
"-p",
|
|
24
|
+
"5432:5432",
|
|
25
|
+
"--health-cmd",
|
|
26
|
+
"CMD pg_isready -q -d paddles -U admin",
|
|
27
|
+
"--health-interval",
|
|
28
|
+
"10s",
|
|
29
|
+
"--health-retries",
|
|
30
|
+
"2",
|
|
31
|
+
"--health-timeout",
|
|
32
|
+
"5s",
|
|
33
|
+
"--name",
|
|
34
|
+
"{name}",
|
|
35
|
+
"{image}",
|
|
36
|
+
]
|
|
37
|
+
env_vars = {
|
|
38
|
+
"POSTGRES_USER": "root",
|
|
39
|
+
"POSTGRES_PASSWORD": "password",
|
|
40
|
+
"APP_DB_USER": "admin",
|
|
41
|
+
"APP_DB_PASS": "password",
|
|
42
|
+
"APP_DB_NAME": "paddles",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def __init__(self, name: str = ""):
|
|
46
|
+
super().__init__(name)
|
|
47
|
+
username = self.env_vars["APP_DB_USER"]
|
|
48
|
+
password = self.env_vars["APP_DB_PASS"]
|
|
49
|
+
db_name = self.env_vars["APP_DB_NAME"]
|
|
50
|
+
self.paddles_sqla_url = (
|
|
51
|
+
f"postgresql+psycopg2://{username}:{password}@postgres:5432/{db_name}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Beanstalk(Container):
|
|
56
|
+
_name = "beanstalk"
|
|
57
|
+
create_cmd = [
|
|
58
|
+
"podman",
|
|
59
|
+
"container",
|
|
60
|
+
"create",
|
|
61
|
+
"-i",
|
|
62
|
+
"--network",
|
|
63
|
+
"ceph-devstack",
|
|
64
|
+
"-p",
|
|
65
|
+
"11300:11300",
|
|
66
|
+
"--name",
|
|
67
|
+
"{name}",
|
|
68
|
+
"{image}",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Paddles(Container):
|
|
73
|
+
create_cmd = [
|
|
74
|
+
"podman",
|
|
75
|
+
"container",
|
|
76
|
+
"create",
|
|
77
|
+
"-i",
|
|
78
|
+
"--network",
|
|
79
|
+
"ceph-devstack",
|
|
80
|
+
"-p",
|
|
81
|
+
"8080:8080",
|
|
82
|
+
"--health-cmd",
|
|
83
|
+
"CMD curl -f http://0.0.0.0:8080",
|
|
84
|
+
"--health-interval",
|
|
85
|
+
"10s",
|
|
86
|
+
"--health-retries",
|
|
87
|
+
"30",
|
|
88
|
+
"--health-timeout",
|
|
89
|
+
"5s",
|
|
90
|
+
"--name",
|
|
91
|
+
"{name}",
|
|
92
|
+
"{image}",
|
|
93
|
+
]
|
|
94
|
+
env_vars = {
|
|
95
|
+
"PADDLES_SERVER_HOST": "0.0.0.0",
|
|
96
|
+
"PADDLES_JOB_LOG_HREF_TEMPL": f"http://{host.hostname()}:8000"
|
|
97
|
+
"/{run_name}/{job_id}/teuthology.log",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Archive(Container):
|
|
102
|
+
cmd_vars = Container.cmd_vars + ["archive_dir"]
|
|
103
|
+
create_cmd = [
|
|
104
|
+
"podman",
|
|
105
|
+
"container",
|
|
106
|
+
"create",
|
|
107
|
+
"-i",
|
|
108
|
+
"--network",
|
|
109
|
+
"ceph-devstack",
|
|
110
|
+
"-p",
|
|
111
|
+
"8000:8000",
|
|
112
|
+
"-v",
|
|
113
|
+
"{archive_dir}:/archive" + ARCHIVE_MOUNT_SUFFIX,
|
|
114
|
+
"--name",
|
|
115
|
+
"{name}",
|
|
116
|
+
"{image}",
|
|
117
|
+
"python3",
|
|
118
|
+
"-m",
|
|
119
|
+
"http.server",
|
|
120
|
+
"-d",
|
|
121
|
+
"/archive",
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def archive_dir(self):
|
|
126
|
+
return Path(config["data_dir"]) / "archive"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Pulpito(Container):
|
|
130
|
+
create_cmd = [
|
|
131
|
+
"podman",
|
|
132
|
+
"container",
|
|
133
|
+
"create",
|
|
134
|
+
"-i",
|
|
135
|
+
"--network",
|
|
136
|
+
"ceph-devstack",
|
|
137
|
+
"-p",
|
|
138
|
+
"8081:8081",
|
|
139
|
+
"--health-cmd",
|
|
140
|
+
"CMD curl -f http://0.0.0.0:8081",
|
|
141
|
+
"--health-interval",
|
|
142
|
+
"10s",
|
|
143
|
+
"--health-retries",
|
|
144
|
+
"10",
|
|
145
|
+
"--health-timeout",
|
|
146
|
+
"5s",
|
|
147
|
+
"--name",
|
|
148
|
+
"{name}",
|
|
149
|
+
"{image}",
|
|
150
|
+
]
|
|
151
|
+
env_vars = {
|
|
152
|
+
"PULPITO_PADDLES_ADDRESS": "http://paddles:8080",
|
|
153
|
+
"VITE_MACHINE_TYPE": "testnode",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestNode(Container):
|
|
158
|
+
_image_name = "teuthology-testnode"
|
|
159
|
+
capabilities = [
|
|
160
|
+
"SYS_ADMIN",
|
|
161
|
+
"NET_ADMIN",
|
|
162
|
+
"SYS_TIME",
|
|
163
|
+
"SYS_RAWIO",
|
|
164
|
+
"MKNOD",
|
|
165
|
+
"NET_RAW",
|
|
166
|
+
"SETUID",
|
|
167
|
+
"SETGID",
|
|
168
|
+
"CHOWN",
|
|
169
|
+
"SYS_PTRACE",
|
|
170
|
+
"SYS_TTY_CONFIG",
|
|
171
|
+
"AUDIT_WRITE",
|
|
172
|
+
"AUDIT_CONTROL",
|
|
173
|
+
]
|
|
174
|
+
env_vars = {
|
|
175
|
+
"SSH_PUBKEY": "",
|
|
176
|
+
"CEPH_VOLUME_ALLOW_LOOP_DEVICES": "true",
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
def __init__(self, name: str = ""):
|
|
180
|
+
super().__init__(name=name)
|
|
181
|
+
self.index = 0
|
|
182
|
+
if "_" in self.name:
|
|
183
|
+
self.index = int(self.name.split("_")[-1])
|
|
184
|
+
self.loop_device_count = config["containers"]["testnode"].get(
|
|
185
|
+
"loop_device_count", 1
|
|
186
|
+
)
|
|
187
|
+
self.devices = [self.device_name(i) for i in range(self.loop_device_count)]
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def loop_img_dir(self):
|
|
191
|
+
return (Path(config["data_dir"]) / "disk_images").expanduser()
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def create_cmd(self):
|
|
195
|
+
return [
|
|
196
|
+
"podman",
|
|
197
|
+
"container",
|
|
198
|
+
"create",
|
|
199
|
+
"--rm",
|
|
200
|
+
"-i",
|
|
201
|
+
"--network",
|
|
202
|
+
"ceph-devstack",
|
|
203
|
+
"--systemd=always",
|
|
204
|
+
"--cgroupns=host",
|
|
205
|
+
"--secret",
|
|
206
|
+
"id_rsa.pub",
|
|
207
|
+
"-p",
|
|
208
|
+
"22",
|
|
209
|
+
"--cap-add",
|
|
210
|
+
",".join(self.capabilities),
|
|
211
|
+
"--security-opt",
|
|
212
|
+
"unmask=/sys/dev/block",
|
|
213
|
+
"-v",
|
|
214
|
+
"/sys/dev/block:/sys/dev/block",
|
|
215
|
+
"-v",
|
|
216
|
+
"/sys/fs/cgroup:/sys/fs/cgroup",
|
|
217
|
+
"-v",
|
|
218
|
+
"/dev/fuse:/dev/fuse",
|
|
219
|
+
"-v",
|
|
220
|
+
"/dev/disk:/dev/disk",
|
|
221
|
+
# cephadm tries to access these DMI-related files, and by default they
|
|
222
|
+
# have 600 permissions on the host. It appears to be ok if they are
|
|
223
|
+
# empty, though.
|
|
224
|
+
# The below was bizarrely causing this error message:
|
|
225
|
+
# No such file or directory: OCI runtime attempted to invoke a command that was
|
|
226
|
+
# not found
|
|
227
|
+
# That was causing the container to fail to start up.
|
|
228
|
+
"-v",
|
|
229
|
+
"/dev/null:/sys/class/dmi/id/board_serial",
|
|
230
|
+
"-v",
|
|
231
|
+
"/dev/null:/sys/class/dmi/id/chassis_serial",
|
|
232
|
+
"-v",
|
|
233
|
+
"/dev/null:/sys/class/dmi/id/product_serial",
|
|
234
|
+
*self.additional_volumes,
|
|
235
|
+
"--device",
|
|
236
|
+
"/dev/net/tun",
|
|
237
|
+
*[f"--device={device}" for device in self.devices],
|
|
238
|
+
"--name",
|
|
239
|
+
"{name}",
|
|
240
|
+
"{image}",
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def additional_volumes(self):
|
|
245
|
+
volumes = []
|
|
246
|
+
if (
|
|
247
|
+
sshd_config := DEFAULT_CONFIG_PATH.parent.joinpath(
|
|
248
|
+
"sshd_config"
|
|
249
|
+
).expanduser()
|
|
250
|
+
) and sshd_config.exists():
|
|
251
|
+
volumes.extend(
|
|
252
|
+
[
|
|
253
|
+
"-v",
|
|
254
|
+
f"{sshd_config}:/etc/ssh/sshd_config.d/teuthology.conf:z",
|
|
255
|
+
]
|
|
256
|
+
)
|
|
257
|
+
return volumes
|
|
258
|
+
|
|
259
|
+
async def create(self):
|
|
260
|
+
if not await self.exists():
|
|
261
|
+
await self.create_loop_devices()
|
|
262
|
+
await super().create()
|
|
263
|
+
|
|
264
|
+
async def remove(self):
|
|
265
|
+
await super().remove()
|
|
266
|
+
await self.remove_loop_devices()
|
|
267
|
+
|
|
268
|
+
async def create_loop_devices(self):
|
|
269
|
+
for device in self.devices:
|
|
270
|
+
await self.create_loop_device(device)
|
|
271
|
+
|
|
272
|
+
async def remove_loop_devices(self):
|
|
273
|
+
for device in self.devices:
|
|
274
|
+
await self.remove_loop_device(device)
|
|
275
|
+
|
|
276
|
+
async def create_loop_device(self, device: str):
|
|
277
|
+
size = config["containers"]["testnode"]["loop_device_size"]
|
|
278
|
+
os.makedirs(self.loop_img_dir, exist_ok=True)
|
|
279
|
+
proc = await self.cmd(["lsmod", "|", "grep", "loop"])
|
|
280
|
+
if proc and await proc.wait() != 0:
|
|
281
|
+
await self.cmd(["sudo", "modprobe", "loop"])
|
|
282
|
+
loop_img_name = os.path.join(self.loop_img_dir, self.device_image(device))
|
|
283
|
+
await self.remove_loop_device(device)
|
|
284
|
+
device_pos = device.removeprefix("/dev/loop")
|
|
285
|
+
await self.cmd(
|
|
286
|
+
[
|
|
287
|
+
"sudo",
|
|
288
|
+
"mknod",
|
|
289
|
+
"-m700",
|
|
290
|
+
device,
|
|
291
|
+
"b",
|
|
292
|
+
"7",
|
|
293
|
+
device_pos,
|
|
294
|
+
],
|
|
295
|
+
check=True,
|
|
296
|
+
)
|
|
297
|
+
await self.cmd(
|
|
298
|
+
["sudo", "chown", f"{os.getuid()}:{os.getgid()}", device],
|
|
299
|
+
check=True,
|
|
300
|
+
)
|
|
301
|
+
await self.cmd(
|
|
302
|
+
[
|
|
303
|
+
"sudo",
|
|
304
|
+
"dd",
|
|
305
|
+
"if=/dev/null",
|
|
306
|
+
f"of={loop_img_name}",
|
|
307
|
+
"bs=1",
|
|
308
|
+
"count=0",
|
|
309
|
+
f"seek={size}",
|
|
310
|
+
],
|
|
311
|
+
check=True,
|
|
312
|
+
)
|
|
313
|
+
await self.cmd(["sudo", "losetup", device, loop_img_name], check=True)
|
|
314
|
+
await self.cmd(["chcon", "-t", "fixed_disk_device_t", device])
|
|
315
|
+
|
|
316
|
+
async def remove_loop_device(self, device: str):
|
|
317
|
+
loop_img_name = os.path.join(self.loop_img_dir, self.device_image(device))
|
|
318
|
+
if os.path.ismount(device):
|
|
319
|
+
await self.cmd(["umount", device], check=True)
|
|
320
|
+
if host.path_exists(device):
|
|
321
|
+
await self.cmd(["sudo", "losetup", "-d", device])
|
|
322
|
+
await self.cmd(["sudo", "rm", "-f", device], check=True)
|
|
323
|
+
if host.path_exists(loop_img_name):
|
|
324
|
+
os.remove(loop_img_name)
|
|
325
|
+
|
|
326
|
+
def device_name(self, index: int):
|
|
327
|
+
return f"/dev/loop{self.loop_device_count * self.index + index}"
|
|
328
|
+
|
|
329
|
+
def device_image(self, device: str):
|
|
330
|
+
return f"{self.name}-{device.removeprefix('/dev/loop')}"
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class Teuthology(Container):
|
|
334
|
+
cmd_vars: List[str] = ["name", "image", "image_tag", "archive_dir"]
|
|
335
|
+
|
|
336
|
+
build_cmd: List[str] = [
|
|
337
|
+
"podman",
|
|
338
|
+
"build",
|
|
339
|
+
"-t",
|
|
340
|
+
"{name}:{image_tag}",
|
|
341
|
+
"-f",
|
|
342
|
+
"./containers/teuthology-dev/Dockerfile",
|
|
343
|
+
".",
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def create_cmd(self):
|
|
348
|
+
cmd = [
|
|
349
|
+
"podman",
|
|
350
|
+
"container",
|
|
351
|
+
"create",
|
|
352
|
+
"-i",
|
|
353
|
+
"--label",
|
|
354
|
+
f"testnode_count={config['containers']['testnode']['count']}",
|
|
355
|
+
"--network",
|
|
356
|
+
"ceph-devstack",
|
|
357
|
+
"--secret",
|
|
358
|
+
"id_rsa",
|
|
359
|
+
"-v",
|
|
360
|
+
"{archive_dir}:/archive_dir" + ARCHIVE_MOUNT_SUFFIX,
|
|
361
|
+
]
|
|
362
|
+
ansible_inv = os.environ.get("ANSIBLE_INVENTORY_PATH")
|
|
363
|
+
if ansible_inv:
|
|
364
|
+
cmd += [
|
|
365
|
+
"-v",
|
|
366
|
+
f"{ansible_inv}/inventory:/etc/ansible/hosts",
|
|
367
|
+
"-v",
|
|
368
|
+
f"{ansible_inv}/secrets:/etc/ansible/secrets",
|
|
369
|
+
]
|
|
370
|
+
ssh_auth_socket = os.environ.get("SSH_AUTH_SOCK")
|
|
371
|
+
if ssh_auth_socket and Path(ssh_auth_socket).exists():
|
|
372
|
+
cmd += [
|
|
373
|
+
"-v",
|
|
374
|
+
f"{ssh_auth_socket}:{ssh_auth_socket}",
|
|
375
|
+
"-e",
|
|
376
|
+
f"SSH_AUTH_SOCK={ssh_auth_socket}",
|
|
377
|
+
]
|
|
378
|
+
custom_conf = os.environ.get("TEUTHOLOGY_CONF")
|
|
379
|
+
if custom_conf:
|
|
380
|
+
cmd += [
|
|
381
|
+
"-v",
|
|
382
|
+
f"{custom_conf}:/tmp/conf.yaml",
|
|
383
|
+
"-e",
|
|
384
|
+
"TEUTHOLOGY_CONF=/tmp/conf.yaml",
|
|
385
|
+
]
|
|
386
|
+
teuthology_yaml = os.environ.get("TEUTHOLOGY_YAML")
|
|
387
|
+
if teuthology_yaml:
|
|
388
|
+
cmd += [
|
|
389
|
+
"-v",
|
|
390
|
+
f"{teuthology_yaml}:/root/.teuthology.yaml",
|
|
391
|
+
]
|
|
392
|
+
cmd += [
|
|
393
|
+
"--name",
|
|
394
|
+
"{name}",
|
|
395
|
+
"{image}",
|
|
396
|
+
]
|
|
397
|
+
return cmd
|
|
398
|
+
|
|
399
|
+
env_vars = {
|
|
400
|
+
"SSH_PRIVKEY": "",
|
|
401
|
+
"SSH_PRIVKEY_FILE": "",
|
|
402
|
+
"TEUTHOLOGY_MACHINE_TYPE": "",
|
|
403
|
+
"TEUTHOLOGY_TESTNODES": "",
|
|
404
|
+
"TEUTHOLOGY_BRANCH": "",
|
|
405
|
+
"TEUTHOLOGY_CEPH_BRANCH": "",
|
|
406
|
+
"TEUTHOLOGY_CEPH_REPO": "",
|
|
407
|
+
"TEUTHOLOGY_SUITE": "",
|
|
408
|
+
"TEUTHOLOGY_SUITE_BRANCH": "",
|
|
409
|
+
"TEUTHOLOGY_SUITE_REPO": "",
|
|
410
|
+
"TEUTHOLOGY_SUITE_EXTRA_ARGS": "",
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def archive_dir(self):
|
|
415
|
+
return Path(config["data_dir"]) / "archive"
|
|
416
|
+
|
|
417
|
+
async def create(self):
|
|
418
|
+
self.archive_dir.expanduser().resolve().mkdir(parents=True, exist_ok=True)
|
|
419
|
+
await super().create()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from ceph_devstack import logger, PROJECT_ROOT
|
|
2
|
+
from ceph_devstack.requirements import Requirement, FixableRequirement
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HasSudo(Requirement):
|
|
6
|
+
check_cmd = ["sudo", "true"]
|
|
7
|
+
suggest_msg = "sudo access is required"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoopControlDeviceExists(FixableRequirement):
|
|
11
|
+
device = "/dev/loop-control"
|
|
12
|
+
check_cmd = ["test", "-e", device]
|
|
13
|
+
suggest_msg = f"{device} does not exist"
|
|
14
|
+
fix_cmd = ["sudo", "modprobe", "loop"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LoopControlDeviceWriteable(FixableRequirement):
|
|
18
|
+
device = "/dev/loop-control"
|
|
19
|
+
check_cmd = ["test", "-w", device]
|
|
20
|
+
suggest_msg = f"Cannot write to {device}"
|
|
21
|
+
|
|
22
|
+
async def check(self):
|
|
23
|
+
if not (result := await super().check()):
|
|
24
|
+
group = (
|
|
25
|
+
self.host.run(["stat", "--printf", "%G", self.device])
|
|
26
|
+
.communicate()[0]
|
|
27
|
+
.decode()
|
|
28
|
+
)
|
|
29
|
+
user = self.host.run(["whoami"]).communicate()[0].strip().decode()
|
|
30
|
+
if self.host.type == "local":
|
|
31
|
+
self.fix_cmd = ["sudo", "usermod", "-a", "-G", group, user]
|
|
32
|
+
else:
|
|
33
|
+
self.fix_cmd = ["sudo", "chgrp", user, self.device]
|
|
34
|
+
self.suggest_msg = f"Cannot write to {self.device}"
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
async def suggest(self):
|
|
38
|
+
await super().suggest()
|
|
39
|
+
if self.host.type == "local":
|
|
40
|
+
logger.warning(
|
|
41
|
+
"Note that group modifications require a logout to take effect."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SELinuxModule(FixableRequirement):
|
|
46
|
+
def __init__(self):
|
|
47
|
+
fix_cmd = self.fix_cmd_prebuilt
|
|
48
|
+
if self.host.type == "remote":
|
|
49
|
+
fix_cmd = ["podman", "machine", "ssh", "--"] + fix_cmd
|
|
50
|
+
self.fix_cmd = fix_cmd
|
|
51
|
+
|
|
52
|
+
fix_cmd_build = [
|
|
53
|
+
"(sudo",
|
|
54
|
+
"dnf",
|
|
55
|
+
"install",
|
|
56
|
+
"-y",
|
|
57
|
+
"policycoreutils-devel",
|
|
58
|
+
"selinux-policy-devel",
|
|
59
|
+
"&&",
|
|
60
|
+
"cd",
|
|
61
|
+
str(PROJECT_ROOT),
|
|
62
|
+
"&&",
|
|
63
|
+
"make",
|
|
64
|
+
"-f",
|
|
65
|
+
"/usr/share/selinux/devel/Makefile",
|
|
66
|
+
"ceph_devstack.pp",
|
|
67
|
+
"&&",
|
|
68
|
+
"sudo",
|
|
69
|
+
"semodule",
|
|
70
|
+
"-i",
|
|
71
|
+
"ceph_devstack.pp)",
|
|
72
|
+
]
|
|
73
|
+
fix_cmd_prebuilt = [
|
|
74
|
+
"sudo",
|
|
75
|
+
"semodule",
|
|
76
|
+
"-i",
|
|
77
|
+
str(PROJECT_ROOT / "ceph_devstack.pp"),
|
|
78
|
+
]
|
|
79
|
+
suggest_msg = (
|
|
80
|
+
"SELinux is in Enforcing mode. To run nested rootless podman "
|
|
81
|
+
"containers, it is necessary to install ceph-devstack's SELinux "
|
|
82
|
+
"module"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async def check(self):
|
|
86
|
+
proc = await self.host.arun(["sudo", "semodule", "-l"])
|
|
87
|
+
assert proc.stdout is not None
|
|
88
|
+
await proc.wait()
|
|
89
|
+
out = (await proc.stdout.read()).decode()
|
|
90
|
+
return "ceph_devstack" in out.split("\n")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound
|
|
5
|
+
|
|
6
|
+
RUN_DIRNAME_PATTERN = re.compile(
|
|
7
|
+
r"^(?P<username>^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}))-(?P<timestamp>\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_logtimestamp(dirname: str) -> datetime:
|
|
12
|
+
match_ = RUN_DIRNAME_PATTERN.search(dirname)
|
|
13
|
+
assert match_
|
|
14
|
+
return datetime.strptime(match_.group("timestamp"), "%Y-%m-%d_%H:%M:%S")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_most_recent_run(runs: list[str]) -> str:
|
|
18
|
+
try:
|
|
19
|
+
run_name = next(
|
|
20
|
+
iter(
|
|
21
|
+
sorted(
|
|
22
|
+
(
|
|
23
|
+
dirname
|
|
24
|
+
for dirname in runs
|
|
25
|
+
if RUN_DIRNAME_PATTERN.search(dirname)
|
|
26
|
+
),
|
|
27
|
+
key=lambda dirname: get_logtimestamp(dirname),
|
|
28
|
+
reverse=True,
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
return run_name
|
|
33
|
+
except StopIteration as e:
|
|
34
|
+
raise FileNotFoundError from e
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_job_id(jobs: list[str]):
|
|
38
|
+
job_dir_pattern = re.compile(r"^\d+$")
|
|
39
|
+
dirs = [d for d in jobs if job_dir_pattern.match(d)]
|
|
40
|
+
|
|
41
|
+
if len(dirs) == 0:
|
|
42
|
+
raise FileNotFoundError
|
|
43
|
+
elif len(dirs) > 1:
|
|
44
|
+
raise TooManyJobsFound(dirs)
|
|
45
|
+
return dirs[0]
|