Skip to content

Commit 2f6b140

Browse files
authored
Merge pull request #31 from exkson/allow-loop-devices-config
testnode: add config entry for osd count per testnode Reviewed-by: Zack Cerza <zcerza@redhat.com>
2 parents 6531f1b + 0cb5fa1 commit 2f6b140

3 files changed

Lines changed: 143 additions & 69 deletions

File tree

ceph_devstack/resources/ceph/containers.py

Lines changed: 106 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import List
66

7-
from ceph_devstack import config
7+
from ceph_devstack import config, DEFAULT_CONFIG_PATH
88
from ceph_devstack.host import host
99
from ceph_devstack.resources.container import Container
1010

@@ -154,7 +154,7 @@ class Pulpito(Container):
154154

155155

156156
class TestNode(Container):
157-
cmd_vars: List[str] = ["name", "image", "loop_dev_name"]
157+
cmd_vars: List[str] = ["name", "image"]
158158
capabilities = [
159159
"SYS_ADMIN",
160160
"NET_ADMIN",
@@ -170,99 +170,131 @@ class TestNode(Container):
170170
"AUDIT_WRITE",
171171
"AUDIT_CONTROL",
172172
]
173-
create_cmd = [
174-
"podman",
175-
"container",
176-
"create",
177-
"--rm",
178-
"-i",
179-
"--network",
180-
"ceph-devstack",
181-
"--systemd=always",
182-
"--cgroupns=host",
183-
"--secret",
184-
"id_rsa.pub",
185-
"-p",
186-
"22",
187-
"--cap-add",
188-
",".join(capabilities),
189-
"--security-opt",
190-
"unmask=/sys/dev/block",
191-
"-v",
192-
"/sys/dev/block:/sys/dev/block",
193-
"-v",
194-
"/sys/fs/cgroup:/sys/fs/cgroup",
195-
"-v",
196-
"/dev/fuse:/dev/fuse",
197-
"-v",
198-
"/dev/disk:/dev/disk",
199-
# cephadm tries to access these DMI-related files, and by default they
200-
# have 600 permissions on the host. It appears to be ok if they are
201-
# empty, though.
202-
"-v",
203-
"/dev/null:/sys/class/dmi/id/board_serial",
204-
"-v",
205-
"/dev/null:/sys/class/dmi/id/chassis_serial",
206-
"-v",
207-
"/dev/null:/sys/class/dmi/id/product_serial",
208-
"--device",
209-
"/dev/net/tun",
210-
"--device",
211-
"{loop_dev_name}",
212-
"--name",
213-
"{name}",
214-
"{image}",
215-
]
216173
env_vars = {
217174
"SSH_PUBKEY": "",
218175
"CEPH_VOLUME_ALLOW_LOOP_DEVICES": "true",
219176
}
220177

221178
def __init__(self, name: str = ""):
222179
super().__init__(name=name)
223-
self.loop_index = 0
224-
self.loop_img_name = self.name
180+
self.index = 0
225181
if "_" in self.name:
226-
self.loop_index = int(self.name.split("_")[-1])
227-
else:
228-
self.loop_img_name += str(self.loop_index)
229-
self.loop_dev_name = f"/dev/loop{self.loop_index}"
182+
self.index = int(self.name.split("_")[-1])
183+
self.loop_device_count = config["containers"]["testnode"].get(
184+
"loop_device_count", 1
185+
)
186+
self.devices = [self.device_name(i) for i in range(self.loop_device_count)]
230187

231188
@property
232189
def loop_img_dir(self):
233190
return (Path(config["data_dir"]) / "disk_images").expanduser()
234191

192+
@property
193+
def create_cmd(self):
194+
return [
195+
"podman",
196+
"container",
197+
"create",
198+
"--rm",
199+
"-i",
200+
"--network",
201+
"ceph-devstack",
202+
"--systemd=always",
203+
"--cgroupns=host",
204+
"--secret",
205+
"id_rsa.pub",
206+
"-p",
207+
"22",
208+
"--cap-add",
209+
",".join(self.capabilities),
210+
"--security-opt",
211+
"unmask=/sys/dev/block",
212+
"-v",
213+
"/sys/dev/block:/sys/dev/block",
214+
"-v",
215+
"/sys/fs/cgroup:/sys/fs/cgroup",
216+
"-v",
217+
"/dev/fuse:/dev/fuse",
218+
"-v",
219+
"/dev/disk:/dev/disk",
220+
# cephadm tries to access these DMI-related files, and by default they
221+
# have 600 permissions on the host. It appears to be ok if they are
222+
# empty, though.
223+
# The below was bizarrely causing this error message:
224+
# No such file or directory: OCI runtime attempted to invoke a command that was
225+
# not found
226+
# That was causing the container to fail to start up.
227+
"-v",
228+
"/dev/null:/sys/class/dmi/id/board_serial",
229+
"-v",
230+
"/dev/null:/sys/class/dmi/id/chassis_serial",
231+
"-v",
232+
"/dev/null:/sys/class/dmi/id/product_serial",
233+
*self.additional_volumes,
234+
"--device",
235+
"/dev/net/tun",
236+
*[f"--device={device}" for device in self.devices],
237+
"--name",
238+
"{name}",
239+
"{image}",
240+
]
241+
242+
@property
243+
def additional_volumes(self):
244+
volumes = []
245+
if (
246+
sshd_config := DEFAULT_CONFIG_PATH.parent.joinpath(
247+
"sshd_config"
248+
).expanduser()
249+
) and sshd_config.exists():
250+
volumes.extend(
251+
[
252+
"-v",
253+
f"{sshd_config}:/etc/ssh/sshd_config.d/teuthology.conf:z",
254+
]
255+
)
256+
return volumes
257+
235258
async def create(self):
236259
if not await self.exists():
237-
await self.create_loop_device()
260+
await self.create_loop_devices()
238261
await super().create()
239262

240263
async def remove(self):
241264
await super().remove()
242-
await self.remove_loop_device()
265+
await self.remove_loop_devices()
243266

244-
async def create_loop_device(self):
267+
async def create_loop_devices(self):
268+
for device in self.devices:
269+
await self.create_loop_device(device)
270+
271+
async def remove_loop_devices(self):
272+
for device in self.devices:
273+
await self.remove_loop_device(device)
274+
275+
async def create_loop_device(self, device: str):
245276
size = config["containers"]["testnode"]["loop_device_size"]
246277
os.makedirs(self.loop_img_dir, exist_ok=True)
247278
proc = await self.cmd(["lsmod", "|", "grep", "loop"])
248279
if proc and await proc.wait() != 0:
249280
await self.cmd(["sudo", "modprobe", "loop"])
250-
loop_img_name = os.path.join(self.loop_img_dir, self.loop_img_name)
251-
await self.remove_loop_device()
281+
loop_img_name = os.path.join(self.loop_img_dir, self.device_image(device))
282+
await self.remove_loop_device(device)
283+
device_pos = device.removeprefix("/dev/loop")
252284
await self.cmd(
253285
[
254286
"sudo",
255287
"mknod",
256288
"-m700",
257-
self.loop_dev_name,
289+
device,
258290
"b",
259291
"7",
260-
str(self.loop_index),
292+
device_pos,
261293
],
262294
check=True,
263295
)
264296
await self.cmd(
265-
["sudo", "chown", f"{os.getuid()}:{os.getgid()}", self.loop_dev_name],
297+
["sudo", "chown", f"{os.getuid()}:{os.getgid()}", device],
266298
check=True,
267299
)
268300
await self.cmd(
@@ -277,20 +309,25 @@ async def create_loop_device(self):
277309
],
278310
check=True,
279311
)
280-
await self.cmd(
281-
["sudo", "losetup", self.loop_dev_name, loop_img_name], check=True
282-
)
312+
await self.cmd(["sudo", "losetup", device, loop_img_name], check=True)
313+
await self.cmd(["chcon", "-t", "fixed_disk_device_t", device])
283314

284-
async def remove_loop_device(self):
285-
loop_img_name = os.path.join(self.loop_img_dir, self.loop_img_name)
286-
if os.path.ismount(self.loop_dev_name):
287-
await self.cmd(["umount", self.loop_dev_name], check=True)
288-
if host.path_exists(self.loop_dev_name):
289-
await self.cmd(["sudo", "losetup", "-d", self.loop_dev_name])
290-
await self.cmd(["sudo", "rm", "-f", self.loop_dev_name], check=True)
315+
async def remove_loop_device(self, device: str):
316+
loop_img_name = os.path.join(self.loop_img_dir, self.device_image(device))
317+
if os.path.ismount(device):
318+
await self.cmd(["umount", device], check=True)
319+
if host.path_exists(device):
320+
await self.cmd(["sudo", "losetup", "-d", device])
321+
await self.cmd(["sudo", "rm", "-f", device], check=True)
291322
if host.path_exists(loop_img_name):
292323
os.remove(loop_img_name)
293324

325+
def device_name(self, index: int):
326+
return f"/dev/loop{self.loop_device_count * self.index + index}"
327+
328+
def device_image(self, device: str):
329+
return f"{self.name}-{device.removeprefix('/dev/loop')}"
330+
294331

295332
class Teuthology(Container):
296333
cmd_vars: List[str] = ["name", "image", "image_tag", "archive_dir"]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[containers.testnode]
2+
loop_device_count = 4
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
from ceph_devstack.resources.ceph import TestNode
6+
from ceph_devstack import config
7+
8+
9+
class TestTestnode:
10+
@pytest.fixture(scope="class")
11+
def cls(self) -> type[TestNode]:
12+
return TestNode
13+
14+
def test_testnode_loop_device_count_default_to_one(self, cls):
15+
testnode = cls("testnode_1")
16+
assert testnode.loop_device_count == 1
17+
18+
def test_testnode_create_cmd_includes_related_devices(self, cls):
19+
config.load(Path(__file__).parent.joinpath("fixtures", "testnode-config.toml"))
20+
testnode = cls("testnode_1")
21+
create_cmd = testnode.create_cmd
22+
assert "--device=/dev/loop4" in create_cmd
23+
assert "--device=/dev/loop5" in create_cmd
24+
assert "--device=/dev/loop6" in create_cmd
25+
assert "--device=/dev/loop7" in create_cmd
26+
27+
def test_testnode_devices_is_based_on_loop_device_count_config(self, cls):
28+
testnode = cls("testnode_1")
29+
assert testnode.loop_device_count == 4
30+
assert testnode.devices == [
31+
"/dev/loop4",
32+
"/dev/loop5",
33+
"/dev/loop6",
34+
"/dev/loop7",
35+
]

0 commit comments

Comments
 (0)