return ''
+def should_retry_lvs_without_devices_file(stdout: List[str],
+ stderr: List[str],
+ returncode: int) -> bool:
+ """
+ Detect an LVM 'devices file' mismatch scenario where `lvs` can return no data
+ and only emits warnings on stderr.
+ """
+ if returncode != 0 or stdout:
+ return False
+ return any('devices file is missing' in line.lower() for line in stderr)
+
+
def _output_parser(output: List[str], fields: str) -> List[Dict[str, Any]]:
"""
Newer versions of LVM allow ``--reportformat=json``, but older versions,
args = ['lvs'] + LV_CMD_OPTIONS + ['-S', filters_str, '-o', fields]
stdout, stderr, returncode = process.call(args, run_on_host=True, verbose_on_failure=False)
+ if should_retry_lvs_without_devices_file(stdout, stderr, returncode):
+ logger.warning(
+ 'lvs returned no output with devices-file warnings; retrying with use_devicesfile=0'
+ )
+ retry_args = ['lvs'] + LV_CMD_OPTIONS + [
+ '--config',
+ 'devices { use_devicesfile=0 }',
+ '-S',
+ filters_str,
+ '-o',
+ fields,
+ ]
+ stdout, stderr, returncode = process.call(
+ retry_args, run_on_host=True, verbose_on_failure=False
+ )
lvs_report = _output_parser(stdout, fields)
return [Volume(**lv_report) for lv_report in lvs_report]
monkeypatch.setattr(api.process, 'call', lambda x,**kw: ('', '', 0))
assert api.get_lvs() == []
+ def test_get_lvs_retries_without_devices_file_on_warning(self, monkeypatch):
+ retry_stdout = ['ceph.type=data;/dev/vg/lv;lv;vg;lv-uuid;1024']
+ calls = []
+
+ def fake_call(cmd, **kw):
+ calls.append(cmd)
+ if len(calls) == 1:
+ return (
+ [],
+ ['WARNING: devices file is missing /dev/mapper/eui.foo using multipath component /dev/nvme0n1.'],
+ 0,
+ )
+ return (retry_stdout, [], 0)
+
+ monkeypatch.setattr(api.process, 'call', fake_call)
+
+ lvs_ = api.get_lvs()
+
+ assert len(calls) == 2
+ assert '--config' in calls[1]
+ assert 'devices { use_devicesfile=0 }' in calls[1]
+ assert len(lvs_) == 1
+ assert lvs_[0].lv_name == 'lv'
+ assert lvs_[0].vg_name == 'vg'
+
class TestGetSinglePV(object):