diff --git a/ceph_devstack/resources/ceph/containers.py b/ceph_devstack/resources/ceph/containers.py index 9e3ea1c7..934afa37 100644 --- a/ceph_devstack/resources/ceph/containers.py +++ b/ceph_devstack/resources/ceph/containers.py @@ -218,19 +218,7 @@ def create_cmd(self): "/dev/fuse:/dev/fuse", "-v", "/dev/disk:/dev/disk", - # cephadm tries to access these DMI-related files, and by default they - # have 600 permissions on the host. It appears to be ok if they are - # empty, though. - # The below was bizarrely causing this error message: - # No such file or directory: OCI runtime attempted to invoke a command that was - # not found - # That was causing the container to fail to start up. - "-v", - "/dev/null:/sys/class/dmi/id/board_serial", - "-v", - "/dev/null:/sys/class/dmi/id/chassis_serial", - "-v", - "/dev/null:/sys/class/dmi/id/product_serial", + *self._get_available_dmi_mounts(), *self.additional_volumes, "--device", "/dev/net/tun", @@ -329,6 +317,26 @@ def device_name(self, index: int): def device_image(self, device: str): return f"{self.name}-{device.removeprefix('/dev/loop')}" + def _get_available_dmi_mounts(self) -> List[str]: + """Return volume mount args for DMI files that exist on the host. + + cephadm tries to access DMI-related files, and by default they have 600 + permissions on the host. We mount /dev/null to make them accessible but empty. + However, in nested virtualization these files may not exist at all, so we + only mount them if they're present on the host. + """ + dmi_files = [ + "board_serial", + "chassis_serial", + "product_serial", + ] + mounts = [] + for dmi_file in dmi_files: + dmi_path = f"/sys/class/dmi/id/{dmi_file}" + if host.path_exists(dmi_path): + mounts.extend(["-v", f"/dev/null:{dmi_path}"]) + return mounts + class Teuthology(Container): cmd_vars: List[str] = ["name", "image", "image_tag", "archive_dir"] diff --git a/tests/resources/ceph/test_testnode.py b/tests/resources/ceph/test_testnode.py index 9425edab..69271fb1 100644 --- a/tests/resources/ceph/test_testnode.py +++ b/tests/resources/ceph/test_testnode.py @@ -1,4 +1,5 @@ from pathlib import Path +from unittest.mock import patch import pytest @@ -35,3 +36,66 @@ def test_testnode_devices_is_based_on_loop_device_count_config(self, cls): "/dev/loop6", "/dev/loop7", ] + + def test_get_available_dmi_mounts_with_all_files_present(self, cls): + testnode = cls("testnode_1") + with patch( + "ceph_devstack.resources.ceph.containers.host.path_exists" + ) as mock_exists: + mock_exists.return_value = True + mounts = testnode._get_available_dmi_mounts() + assert mounts == [ + "-v", + "/dev/null:/sys/class/dmi/id/board_serial", + "-v", + "/dev/null:/sys/class/dmi/id/chassis_serial", + "-v", + "/dev/null:/sys/class/dmi/id/product_serial", + ] + + def test_get_available_dmi_mounts_with_no_files_present(self, cls): + testnode = cls("testnode_1") + with patch( + "ceph_devstack.resources.ceph.containers.host.path_exists" + ) as mock_exists: + mock_exists.return_value = False + mounts = testnode._get_available_dmi_mounts() + assert mounts == [] + + def test_get_available_dmi_mounts_with_partial_files_present(self, cls): + testnode = cls("testnode_1") + with patch( + "ceph_devstack.resources.ceph.containers.host.path_exists" + ) as mock_exists: + # Only board_serial exists + mock_exists.side_effect = lambda path: "board_serial" in path + mounts = testnode._get_available_dmi_mounts() + assert mounts == [ + "-v", + "/dev/null:/sys/class/dmi/id/board_serial", + ] + + def test_create_cmd_includes_dmi_mounts_when_available(self, cls): + config.load(Path(__file__).parent.joinpath("fixtures", "testnode-config.toml")) + testnode = cls("testnode_1") + with patch( + "ceph_devstack.resources.ceph.containers.host.path_exists" + ) as mock_exists: + mock_exists.return_value = True + create_cmd = testnode.create_cmd + assert "/dev/null:/sys/class/dmi/id/board_serial" in " ".join(create_cmd) + assert "/dev/null:/sys/class/dmi/id/chassis_serial" in " ".join(create_cmd) + assert "/dev/null:/sys/class/dmi/id/product_serial" in " ".join(create_cmd) + + def test_create_cmd_excludes_dmi_mounts_when_unavailable(self, cls): + config.load(Path(__file__).parent.joinpath("fixtures", "testnode-config.toml")) + testnode = cls("testnode_1") + with patch( + "ceph_devstack.resources.ceph.containers.host.path_exists" + ) as mock_exists: + mock_exists.return_value = False + create_cmd = testnode.create_cmd + cmd_str = " ".join(create_cmd) + assert "/sys/class/dmi/id/board_serial" not in cmd_str + assert "/sys/class/dmi/id/chassis_serial" not in cmd_str + assert "/sys/class/dmi/id/product_serial" not in cmd_str