From: Guillaume Abrioux Date: Thu, 14 Oct 2021 07:41:42 +0000 (+0200) Subject: ceph-volume: implement lvm wrapper X-Git-Tag: v17.1.0~472^2~1 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=4d33630deeaee51578868fb29337da802e9cb231;p=ceph.git ceph-volume: implement lvm wrapper ceph-volume should run pv/vg/lv commands in the host namespace rather than running them inside the container in order to avoid lvm metadata corruption. Fixes: https://tracker.ceph.com/issues/52926 Signed-off-by: Guillaume Abrioux --- diff --git a/src/ceph-volume/ceph_volume/api/lvm.py b/src/ceph-volume/ceph_volume/api/lvm.py index ca0d0e98a2142..174bdb8d52eaa 100644 --- a/src/ceph-volume/ceph_volume/api/lvm.py +++ b/src/ceph-volume/ceph_volume/api/lvm.py @@ -399,13 +399,14 @@ class PVolume(object): if self.tags.get(key): current_value = self.tags[key] tag = "%s=%s" % (key, current_value) - process.call(['pvchange', '--deltag', tag, self.pv_name]) + process.call(['pvchange', '--deltag', tag, self.pv_name], run_on_host=True) process.call( [ 'pvchange', '--addtag', '%s=%s' % (key, value), self.pv_name - ] + ], + run_on_host=True ) @@ -420,7 +421,7 @@ def create_pv(device): '-f', # force it '--yes', # answer yes to any prompts device - ]) + ], run_on_host=True) def remove_pv(pv_name): @@ -446,6 +447,7 @@ def remove_pv(pv_name): '-f', # force it pv_name ], + run_on_host=True, fail_msg=fail_msg, ) @@ -470,7 +472,7 @@ def get_pvs(fields=PV_FIELDS, filters='', tags=None): args = ['pvs', '--noheadings', '--readonly', '--separator=";"', '-S', filters, '-o', fields] - stdout, stderr, returncode = process.call(args, verbose_on_failure=False) + stdout, stderr, returncode = process.call(args, run_on_host=True, verbose_on_failure=False) pvs_report = _output_parser(stdout, fields) return [PVolume(**pv_report) for pv_report in pvs_report] @@ -657,7 +659,8 @@ def create_vg(devices, name=None, name_prefix=None): 'vgcreate', '--force', '--yes', - name] + devices + name] + devices, + run_on_host=True ) return get_single_vg(filters={'vg_name': name}) @@ -681,7 +684,8 @@ def extend_vg(vg, devices): 'vgextend', '--force', '--yes', - vg.name] + devices + vg.name] + devices, + run_on_host=True ) return get_single_vg(filters={'vg_name': vg.name}) @@ -703,7 +707,8 @@ def reduce_vg(vg, devices): 'vgreduce', '--force', '--yes', - vg.name] + devices + vg.name] + devices, + run_on_host=True ) return get_single_vg(filter={'vg_name': vg.name}) @@ -724,6 +729,7 @@ def remove_vg(vg_name): '-f', # force it vg_name ], + run_on_host=True, fail_msg=fail_msg, ) @@ -747,7 +753,7 @@ def get_vgs(fields=VG_FIELDS, filters='', tags=None): filters = make_filters_lvmcmd_ready(filters, tags) args = ['vgs'] + VG_CMD_OPTIONS + ['-S', filters, '-o', fields] - stdout, stderr, returncode = process.call(args, verbose_on_failure=False) + stdout, stderr, returncode = process.call(args, run_on_host=True, verbose_on_failure=False) vgs_report =_output_parser(stdout, fields) return [VolumeGroup(**vg_report) for vg_report in vgs_report] @@ -772,6 +778,7 @@ def get_single_vg(fields=VG_FIELDS, filters=None, tags=None): def get_device_vgs(device, name_prefix=''): stdout, stderr, returncode = process.call( ['pvs'] + VG_CMD_OPTIONS + ['-o', VG_FIELDS, device], + run_on_host=True, verbose_on_failure=False ) vgs = _output_parser(stdout, VG_FIELDS) @@ -860,7 +867,7 @@ class Volume(object): return del_tag_args = self._format_tag_args('--deltag', del_tags) # --deltag returns successful even if the to be deleted tag is not set - process.call(['lvchange'] + del_tag_args + [self.lv_path]) + process.call(['lvchange'] + del_tag_args + [self.lv_path], run_on_host=True) for k in del_tags.keys(): del self.tags[k] @@ -879,7 +886,7 @@ class Volume(object): """ self.clear_tags(tags.keys()) add_tag_args = self._format_tag_args('--addtag', tags) - process.call(['lvchange'] + add_tag_args + [self.lv_path]) + process.call(['lvchange'] + add_tag_args + [self.lv_path], run_on_host=True) for k, v in tags.items(): self.tags[k] = v @@ -888,7 +895,7 @@ class Volume(object): if self.tags.get(key): current_value = self.tags[key] tag = "%s=%s" % (key, current_value) - process.call(['lvchange', '--deltag', tag, self.lv_path]) + process.call(['lvchange', '--deltag', tag, self.lv_path], run_on_host=True) del self.tags[key] @@ -903,7 +910,8 @@ class Volume(object): [ 'lvchange', '--addtag', '%s=%s' % (key, value), self.lv_path - ] + ], + run_on_host=True ) self.tags[key] = value @@ -911,7 +919,7 @@ class Volume(object): """ Deactivate the LV by calling lvchange -an """ - process.call(['lvchange', '-an', self.lv_path]) + process.call(['lvchange', '-an', self.lv_path], run_on_host=True) def create_lv(name_prefix, @@ -984,7 +992,7 @@ def create_lv(name_prefix, '100%FREE', '-n', name, vg.vg_name ] - process.run(command) + process.run(command, run_on_host=True) lv = get_single_lv(filters={'lv_name': name, 'vg_name': vg.vg_name}) @@ -1079,6 +1087,7 @@ def remove_lv(lv): '-f', # force it path ], + run_on_host=True, show_command=True, terminal_verbose=True, ) @@ -1106,7 +1115,7 @@ def get_lvs(fields=LV_FIELDS, filters='', tags=None): filters = make_filters_lvmcmd_ready(filters, tags) args = ['lvs'] + LV_CMD_OPTIONS + ['-S', filters, '-o', fields] - stdout, stderr, returncode = process.call(args, verbose_on_failure=False) + stdout, stderr, returncode = process.call(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] @@ -1140,6 +1149,7 @@ def get_lv_by_name(name): stdout, stderr, returncode = process.call( ['lvs', '--noheadings', '-o', LV_FIELDS, '-S', 'lv_name={}'.format(name)], + run_on_host=True, verbose_on_failure=False ) lvs = _output_parser(stdout, LV_FIELDS) @@ -1150,6 +1160,7 @@ def get_lvs_by_tag(lv_tag): stdout, stderr, returncode = process.call( ['lvs', '--noheadings', '--separator=";"', '-a', '-o', LV_FIELDS, '-S', 'lv_tags={{{}}}'.format(lv_tag)], + run_on_host=True, verbose_on_failure=False ) lvs = _output_parser(stdout, LV_FIELDS) @@ -1159,6 +1170,7 @@ def get_lvs_by_tag(lv_tag): def get_device_lvs(device, name_prefix=''): stdout, stderr, returncode = process.call( ['pvs'] + LV_CMD_OPTIONS + ['-o', LV_FIELDS, device], + run_on_host=True, verbose_on_failure=False ) lvs = _output_parser(stdout, LV_FIELDS) diff --git a/src/ceph-volume/ceph_volume/process.py b/src/ceph-volume/ceph_volume/process.py index e70986892b7fd..dd8e0dae8cd6d 100644 --- a/src/ceph-volume/ceph_volume/process.py +++ b/src/ceph-volume/ceph_volume/process.py @@ -1,5 +1,5 @@ from fcntl import fcntl, F_GETFL, F_SETFL -from os import O_NONBLOCK, read +from os import O_NONBLOCK, read, path import subprocess from select import select from ceph_volume import terminal @@ -8,7 +8,15 @@ from ceph_volume.util import as_bytes import logging logger = logging.getLogger(__name__) - +host_rootfs = '/rootfs' +run_host_cmd = [ + 'nsenter', + '--root={}'.format(host_rootfs), + '--mount={}/proc/1/ns/mnt'.format(host_rootfs), + '--ipc={}/proc/1/ns/ipc'.format(host_rootfs), + '--net={}/proc/1/ns/net'.format(host_rootfs), + '--uts={}/proc/1/ns/uts'.format(host_rootfs) +] def which(executable): """ @@ -103,7 +111,7 @@ def obfuscate(command_, on=None): return "Running command: %s" % ' '.join(command) -def run(command, **kw): +def run(command, run_on_host=False, **kw): """ A real-time-logging implementation of a remote subprocess.Popen call where a command is just executed on the remote end and no other handling is done. @@ -114,6 +122,8 @@ def run(command, **kw): """ executable = which(command.pop(0)) command.insert(0, executable) + if run_on_host and path.isdir(host_rootfs): + command = run_host_cmd + command stop_on_error = kw.pop('stop_on_error', True) command_msg = obfuscate(command, kw.pop('obfuscate', None)) fail_msg = kw.pop('fail_msg', None) @@ -157,7 +167,7 @@ def run(command, **kw): logger.warning(msg) -def call(command, **kw): +def call(command, run_on_host=False, **kw): """ Similar to ``subprocess.Popen`` with the following changes: @@ -182,6 +192,8 @@ def call(command, **kw): """ executable = which(command.pop(0)) command.insert(0, executable) + if run_on_host and path.isdir(host_rootfs): + command = run_host_cmd + command terminal_verbose = kw.pop('terminal_verbose', False) logfile_verbose = kw.pop('logfile_verbose', True) verbose_on_failure = kw.pop('verbose_on_failure', True) diff --git a/src/ceph-volume/ceph_volume/tests/api/test_lvm.py b/src/ceph-volume/ceph_volume/tests/api/test_lvm.py index 16e586e02b22a..0bfc34075c31c 100644 --- a/src/ceph-volume/ceph_volume/tests/api/test_lvm.py +++ b/src/ceph-volume/ceph_volume/tests/api/test_lvm.py @@ -195,8 +195,8 @@ class TestCreateLV(object): def test_uses_size(self, m_get_single_lv, m_call, m_run, monkeypatch): m_get_single_lv.return_value = self.foo_volume api.create_lv('foo', 0, vg=self.foo_group, size=419430400, tags={'ceph.type': 'data'}) - expected = ['lvcreate', '--yes', '-l', '100', '-n', 'foo-0', 'foo_group'] - m_run.assert_called_with(expected) + expected = (['lvcreate', '--yes', '-l', '100', '-n', 'foo-0', 'foo_group']) + m_run.assert_called_with(expected, run_on_host=True) @patch('ceph_volume.api.lvm.process.run') @patch('ceph_volume.api.lvm.process.call') @@ -211,7 +211,7 @@ class TestCreateLV(object): # 423624704 should be just under 1% off of the available size 419430400 api.create_lv('foo', 0, vg=foo_group, size=4232052736, tags={'ceph.type': 'data'}) expected = ['lvcreate', '--yes', '-l', '1000', '-n', 'foo-0', 'foo_group'] - m_run.assert_called_with(expected) + m_run.assert_called_with(expected, run_on_host=True) @patch('ceph_volume.api.lvm.process.run') @patch('ceph_volume.api.lvm.process.call') @@ -228,7 +228,7 @@ class TestCreateLV(object): m_get_single_lv.return_value = self.foo_volume api.create_lv('foo', 0, vg=self.foo_group, extents='50', tags={'ceph.type': 'data'}) expected = ['lvcreate', '--yes', '-l', '50', '-n', 'foo-0', 'foo_group'] - m_run.assert_called_with(expected) + m_run.assert_called_with(expected, run_on_host=True) @pytest.mark.parametrize("test_input,expected", [(2, 50), @@ -240,7 +240,7 @@ class TestCreateLV(object): m_get_single_lv.return_value = self.foo_volume api.create_lv('foo', 0, vg=self.foo_group, slots=test_input, tags={'ceph.type': 'data'}) expected = ['lvcreate', '--yes', '-l', str(expected), '-n', 'foo-0', 'foo_group'] - m_run.assert_called_with(expected) + m_run.assert_called_with(expected, run_on_host=True) @patch('ceph_volume.api.lvm.process.run') @patch('ceph_volume.api.lvm.process.call') @@ -249,7 +249,7 @@ class TestCreateLV(object): m_get_single_lv.return_value = self.foo_volume api.create_lv('foo', 0, vg=self.foo_group, tags={'ceph.type': 'data'}) expected = ['lvcreate', '--yes', '-l', '100%FREE', '-n', 'foo-0', 'foo_group'] - m_run.assert_called_with(expected) + m_run.assert_called_with(expected, run_on_host=True) @patch('ceph_volume.api.lvm.process.run') @patch('ceph_volume.api.lvm.process.call')