Skip to content

Commit d874d99

Browse files
authored
Merge pull request ceph#64833 from exkson/wip-exkson-ignore-invalid-loop-devices
ceph-volume: avoid RuntimeError on ceph-volume raw list with non-existent loop devices
2 parents 103bc93 + 9d6a224 commit d874d99

File tree

3 files changed

+70
-4
lines changed

3 files changed

+70
-4
lines changed

src/ceph-volume/ceph_volume/devices/raw/list.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from __future__ import print_function
2+
from typing import Any, Dict, Optional, List as _List
3+
import os
24
import argparse
35
import json
46
import logging
57
from textwrap import dedent
8+
from concurrent.futures import ThreadPoolExecutor
9+
610
from ceph_volume import decorators, process
711
from ceph_volume.util import disk
812
from ceph_volume.util.device import Device
9-
from typing import Any, Dict, Optional, List as _List
10-
from concurrent.futures import ThreadPoolExecutor
1113

1214
logger = logging.getLogger(__name__)
1315

@@ -57,6 +59,13 @@ def __init__(self, argv: _List[str]) -> None:
5759
self.info_devices: _List[Dict[str, str]] = []
5860
self.devices_to_scan: _List[str] = []
5961

62+
def exclude_invalid_devices(self, devices: _List[Dict[str, str]]) -> _List[Dict[str, str]]:
63+
return [
64+
dev
65+
for dev in devices
66+
if (dev_name := dev["NAME"]) and os.path.exists(dev_name)
67+
]
68+
6069
def exclude_atari_partitions(self) -> None:
6170
result: _List[str] = []
6271
for info_device in self.info_devices:
@@ -92,6 +101,8 @@ def generate(self, devices: Optional[_List[str]] = None) -> Dict[str, Any]:
92101
# the parent disk has a bluestore header, but children may be the most appropriate
93102
# devices to return if the parent disk does not have a bluestore header.
94103
self.info_devices = disk.lsblk_all(abspath=True)
104+
self.info_devices = self.exclude_invalid_devices(self.info_devices)
105+
95106
# Linux kernels built with CONFIG_ATARI_PARTITION enabled can falsely interpret
96107
# bluestore's on-disk format as an Atari partition table. These false Atari partitions
97108
# can be interpreted as real OSDs if a bluestore OSD was previously created on the false

src/ceph-volume/ceph_volume/tests/devices/lvm/test_zap.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ def process_call(command, **kw):
2626
result = [], [], 0
2727
return result
2828

29+
@pytest.fixture()
30+
def prevent_exclude_invalid_devices():
31+
with patch('ceph_volume.devices.raw.list.List.exclude_invalid_devices') as mock:
32+
mock.side_effect = lambda x: x
33+
yield
2934

3035
class TestZap:
3136
def test_invalid_osd_id_passed(self) -> None:
@@ -128,6 +133,7 @@ def test_lv_is_matched_id(self, mock_zap, monkeypatch, is_root):
128133
mock_zap.assert_called_once()
129134

130135
# @patch('ceph_volume.devices.lvm.zap.disk.has_bluestore_label', Mock(return_value=True))
136+
@pytest.mark.usefixtures("prevent_exclude_invalid_devices")
131137
@patch('ceph_volume.devices.lvm.zap.Zap.zap')
132138
@patch('ceph_volume.devices.raw.list.List.filter_lvm_osd_devices', Mock(return_value='/dev/sdb'))
133139
@patch('ceph_volume.process.call', Mock(side_effect=process_call))
@@ -156,6 +162,7 @@ def test_lv_is_matched_fsid(self, mock_zap, monkeypatch, is_root):
156162
assert z.args.devices[0].path == '/dev/VolGroup/lv'
157163
mock_zap.assert_called_once
158164

165+
@pytest.mark.usefixtures("prevent_exclude_invalid_devices")
159166
@patch('ceph_volume.devices.lvm.zap.Zap.zap')
160167
@patch('ceph_volume.devices.raw.list.List.filter_lvm_osd_devices', Mock(return_value='/dev/sdb'))
161168
@patch('ceph_volume.process.call', Mock(side_effect=process_call))
@@ -186,6 +193,7 @@ def test_lv_is_matched_id_fsid(self, mock_zap, monkeypatch, is_root):
186193
assert z.args.devices[0].path == '/dev/VolGroup/lv'
187194
mock_zap.assert_called_once
188195

196+
@pytest.mark.usefixtures("prevent_exclude_invalid_devices")
189197
@patch('ceph_volume.devices.lvm.zap.Zap.zap')
190198
@patch('ceph_volume.devices.raw.list.List.filter_lvm_osd_devices', Mock(return_value='/dev/sdb'))
191199
@patch('ceph_volume.process.call', Mock(side_effect=process_call))
@@ -199,6 +207,7 @@ def test_raw_is_matched_id_fsid(self, mock_zap, monkeypatch, is_root):
199207
assert z.args.devices[0].path == '/dev/sdb'
200208
mock_zap.assert_called_once
201209

210+
@pytest.mark.usefixtures("prevent_exclude_invalid_devices")
202211
@patch('ceph_volume.devices.lvm.zap.Zap.zap')
203212
@patch('ceph_volume.devices.raw.list.List.filter_lvm_osd_devices', Mock(side_effect=['/dev/vdx', '/dev/vdy', '/dev/vdz', None]))
204213
@patch('ceph_volume.process.call', Mock(side_effect=process_call))

src/ceph-volume/ceph_volume/tests/devices/raw/test_list.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .data_list import ceph_bluestore_tool_show_label_output
44
from unittest.mock import patch, Mock
55
from ceph_volume.devices import raw
6+
from ceph_volume.devices.raw import list as list_command
67

78
# Sample lsblk output is below that overviews the test scenario. (--json output for reader clarity)
89
# - sda and all its children are used for the OS
@@ -127,12 +128,14 @@ class TestList(object):
127128
@patch('ceph_volume.util.disk.has_bluestore_label')
128129
@patch('ceph_volume.process.call')
129130
@patch('ceph_volume.util.disk.lsblk_all')
130-
def test_raw_list(self, patched_disk_lsblk, patched_call, patched_bluestore_label, patched_get_devices):
131+
@patch('ceph_volume.devices.raw.list.os.path.exists')
132+
def test_raw_list(self, patched_path_exists, patched_disk_lsblk, patched_call, patched_bluestore_label, patched_get_devices):
131133
raw.list.logger.setLevel("DEBUG")
132134
patched_call.side_effect = _process_call_side_effect
133135
patched_disk_lsblk.side_effect = _lsblk_all_devices
134136
patched_bluestore_label.side_effect = _has_bluestore_label_side_effect
135137
patched_get_devices.side_effect = _devices_side_effect
138+
patched_path_exists.return_value = True
136139

137140
result = raw.list.List([]).generate()
138141
assert len(result) == 3
@@ -161,13 +164,15 @@ def test_raw_list(self, patched_disk_lsblk, patched_call, patched_bluestore_labe
161164
@patch('ceph_volume.util.disk.has_bluestore_label')
162165
@patch('ceph_volume.process.call')
163166
@patch('ceph_volume.util.disk.lsblk_all')
164-
def test_raw_list_with_OSError(self, patched_disk_lsblk, patched_call, patched_bluestore_label, patched_get_devices):
167+
@patch('ceph_volume.devices.raw.list.os.path.exists')
168+
def test_raw_list_with_OSError(self, patched_path_exists, patched_disk_lsblk, patched_call, patched_bluestore_label, patched_get_devices):
165169
def _has_bluestore_label_side_effect_with_OSError(device_path):
166170
if device_path == "/dev/sdd":
167171
raise OSError('fake OSError')
168172
return _has_bluestore_label_side_effect(device_path)
169173

170174
raw.list.logger.setLevel("DEBUG")
175+
patched_path_exists.return_value = True
171176
patched_disk_lsblk.side_effect = _lsblk_all_devices
172177
patched_call.side_effect = _process_call_side_effect
173178
patched_bluestore_label.side_effect = _has_bluestore_label_side_effect_with_OSError
@@ -176,3 +181,44 @@ def _has_bluestore_label_side_effect_with_OSError(device_path):
176181
result = raw.list.List([]).generate()
177182
assert len(result) == 2
178183
assert {'sdb-uuid', 'sde1-uuid'} == set(result.keys())
184+
185+
@patch('ceph_volume.devices.raw.list.os.path.exists')
186+
def test_raw_list_exclude_non_existent_loop_devices(self, path_exists_patch):
187+
def path_exists_side_effect(path):
188+
return path in ["/dev/sda"]
189+
path_exists_patch.side_effect = path_exists_side_effect
190+
191+
devices = [
192+
{"NAME": "/dev/loop0", "KNAME": "/dev/loop0", "PKNAME": "", "TYPE": "loop"},
193+
{"NAME": "/dev/nvme1n1", "KNAME": "/dev/nvme1n1", "PKNAME": "", "TYPE": "disk"},
194+
{"NAME": "/dev/sda", "KNAME": "/dev/sda", "PKNAME": "", "TYPE": "disk"},
195+
]
196+
cmd = list_command.List([])
197+
198+
assert cmd.exclude_invalid_devices(devices) == [
199+
{"NAME": "/dev/sda", "KNAME": "/dev/sda", "PKNAME": "", "TYPE": "disk"}
200+
]
201+
202+
@patch("ceph_volume.devices.raw.list.List.exclude_lvm_osd_devices", Mock())
203+
@patch("ceph_volume.util.device.disk.get_devices")
204+
@patch("ceph_volume.util.disk.has_bluestore_label")
205+
@patch("ceph_volume.process.call")
206+
@patch("ceph_volume.util.disk.lsblk_all")
207+
def test_exclude_invalid_devices_is_called(
208+
self,
209+
patched_disk_lsblk,
210+
patched_call,
211+
patched_bluestore_label,
212+
patched_get_devices,
213+
):
214+
patched_disk_lsblk.side_effect = _lsblk_all_devices
215+
patched_call.side_effect = _process_call_side_effect
216+
patched_bluestore_label.side_effect = _has_bluestore_label_side_effect
217+
patched_get_devices.side_effect = _devices_side_effect
218+
219+
with patch(
220+
"ceph_volume.devices.raw.list.List.exclude_invalid_devices"
221+
) as mock:
222+
list_command.List([]).generate()
223+
mock.assert_called_once()
224+

0 commit comments

Comments
 (0)