From 49b05a999cd586bf4695b5e300aa499c731559c8 Mon Sep 17 00:00:00 2001 From: Nicolas Trangez Date: Wed, 25 Nov 2015 14:45:09 +0100 Subject: [PATCH] Revert "Add Scality SRB driver" The Scality SRB kernel-driver is being re-designed to optimize for specific workloads, which are (initially) not compatible with VM-style block device access. Not to confuse users, we believe it's advisable to remove the Cinder SRB driver in the meantime. This reverts commit a23f17f8cebe5e1e57f675aedf6272257257d1b7. Some references to the SRB driver and test modules added in later commits are removed as well. Conflicts: cinder/tests/unit/test_srb.py cinder/volume/drivers/srb.py etc/cinder/rootwrap.d/volume.filters See: 2f9e4163f42ae5246fd997b9f35e16d3d97be54f See: 3fda737f53824170bd59eb6e8ce2e991c89c3c1d Change-Id: Ic9d79b09363ef904932128f63371ba01a15c5d31 --- cinder/opts.py | 2 - cinder/tests/unit/test_srb.py | 963 --------------------------- cinder/volume/drivers/srb.py | 884 ------------------------ etc/cinder/rootwrap.d/volume.filters | 11 - tests-py3.txt | 1 - 5 files changed, 1861 deletions(-) delete mode 100644 cinder/tests/unit/test_srb.py delete mode 100644 cinder/volume/drivers/srb.py diff --git a/cinder/opts.py b/cinder/opts.py index 8d65f80314b..03685a4fc83 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -136,7 +136,6 @@ from cinder.volume.drivers import scality as cinder_volume_drivers_scality from cinder.volume.drivers import sheepdog as cinder_volume_drivers_sheepdog from cinder.volume.drivers import smbfs as cinder_volume_drivers_smbfs from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire -from cinder.volume.drivers import srb as cinder_volume_drivers_srb from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri from cinder.volume.drivers.violin import v6000_common as \ cinder_volume_drivers_violin_v6000common @@ -255,7 +254,6 @@ def list_opts(): cinder_volume_drivers_remotefs.nas_opts, cinder_volume_drivers_remotefs.volume_opts, cinder_volume_drivers_violin_v6000common.violin_opts, - cinder_volume_drivers_srb.srb_opts, cinder_volume_drivers_emc_xtremio.XTREMIO_OPTS, [cinder_api_middleware_auth.use_forwarded_for_opt], cinder_volume_drivers_hitachi_hbsdcommon.volume_opts, diff --git a/cinder/tests/unit/test_srb.py b/cinder/tests/unit/test_srb.py deleted file mode 100644 index ddfeb42cbac..00000000000 --- a/cinder/tests/unit/test_srb.py +++ /dev/null @@ -1,963 +0,0 @@ -# Copyright (c) 2014 Scality -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Unit tests for the Scality Rest Block Volume Driver.""" - -import mock -from oslo_concurrency import processutils -from oslo_utils import units -import six - -from cinder import context -from cinder import exception -from cinder import test -from cinder.tests.unit.brick import test_brick_lvm -from cinder.volume import configuration as conf -from cinder.volume.drivers import srb - - -class SRBLvmTestCase(test_brick_lvm.BrickLvmTestCase): - def setUp(self): - super(SRBLvmTestCase, self).setUp() - - self.vg = srb.LVM(self.configuration.volume_group_name, - 'sudo', - False, None, - 'default', - self.fake_execute) - - def fake_execute(self, *cmd, **kwargs): - try: - return super(SRBLvmTestCase, self).fake_execute(*cmd, **kwargs) - except AssertionError: - pass - - cmd_string = ', '.join(cmd) - - if 'vgremove, -f, ' in cmd_string: - pass - elif 'pvresize, ' in cmd_string: - pass - elif 'lvextend, ' in cmd_string: - pass - elif 'lvchange, ' in cmd_string: - pass - else: - raise AssertionError('unexpected command called: %s' % cmd_string) - - def test_activate_vg(self): - with mock.patch.object(self.vg, '_execute') as executor: - self.vg.activate_vg() - executor.assert_called_once_with( - 'vgchange', '-ay', - self.configuration.volume_group_name, - root_helper=self.vg._root_helper, - run_as_root=True) - - def test_deactivate_vg(self): - with mock.patch.object(self.vg, '_execute') as executor: - self.vg.deactivate_vg() - executor.assert_called_once_with( - 'vgchange', '-an', - self.configuration.volume_group_name, - root_helper=self.vg._root_helper, - run_as_root=True) - - def test_destroy_vg(self): - with mock.patch.object(self.vg, '_execute') as executor: - self.vg.destroy_vg() - executor.assert_called_once_with( - 'vgremove', '-f', - self.configuration.volume_group_name, - root_helper=self.vg._root_helper, - run_as_root=True) - - def test_pv_resize(self): - with mock.patch.object(self.vg, '_execute') as executor: - self.vg.pv_resize('fake-pv', '50G') - executor.assert_called_once_with( - 'env', - 'LC_ALL=C', - 'pvresize', - '--setphysicalvolumesize', - '50G', 'fake-pv', - root_helper=self.vg._root_helper, - run_as_root=True) - - def test_extend_thin_pool_nothin(self): - with mock.patch.object(self.vg, '_execute') as executor: - executor.side_effect = AssertionError - thin_calc =\ - mock.MagicMock( - side_effect= - Exception('Unexpected call to _calculate_thin_pool_size')) - self.vg._calculate_thin_pool_size = thin_calc - self.vg.extend_thin_pool() - - def test_extend_thin_pool_thin(self): - self.stubs.Set(processutils, 'execute', self.fake_execute) - self.thin_vg = srb.LVM(self.configuration.volume_group_name, - 'sudo', - False, None, - 'thin', - self.fake_execute) - self.assertTrue(self.thin_vg.supports_thin_provisioning('sudo')) - self.thin_vg.update_volume_group_info = mock.MagicMock() - with mock.patch('oslo_concurrency.processutils.execute'): - executor = mock.MagicMock() - self.thin_vg._execute = executor - self.thin_vg.extend_thin_pool() - executor.assert_called_once_with('env', 'LC_ALL=C', 'lvextend', - '-L', '9.5g', - 'fake-vg/fake-vg-pool', - root_helper=self.vg._root_helper, - run_as_root=True) - self.thin_vg.update_volume_group_info.assert_called_once_with() - - -class SRBRetryTestCase(test.TestCase): - - def __init__(self, *args, **kwargs): - super(SRBRetryTestCase, self).__init__(*args, **kwargs) - self.attempts = 0 - - def setUp(self): - super(SRBRetryTestCase, self).setUp() - self.attempts = 0 - - def test_retry_no_failure(self): - expected_attempts = 1 - - @srb.retry(exceptions=(), count=expected_attempts) - def _try_failing(self): - self.attempts = self.attempts + 1 - return True - - ret = _try_failing(self) - - self.assertTrue(ret) - self.assertEqual(expected_attempts, self.attempts) - - def test_retry_fail_by_exception(self): - expected_attempts = 2 - ret = None - - @srb.retry(count=expected_attempts, - exceptions=(processutils.ProcessExecutionError)) - def _try_failing(self): - self.attempts = self.attempts + 1 - raise processutils.ProcessExecutionError("Fail everytime") - - try: - ret = _try_failing(self) - except processutils.ProcessExecutionError: - pass - - self.assertIsNone(ret) - self.assertEqual(expected_attempts, self.attempts) - - def test_retry_fail_and_succeed_mixed(self): - - @srb.retry(count=4, exceptions=(Exception), - sleep_mechanism=srb.retry.SLEEP_NONE) - def _try_failing(self): - attempted = self.attempts - self.attempts = self.attempts + 1 - if attempted == 0: - raise IOError(0, 'Oops') - if attempted == 1: - raise Exception("Second try shall except") - if attempted == 2: - assert False - return 34 - - ret = _try_failing(self) - - self.assertEqual(34, ret) - self.assertEqual(4, self.attempts) - - -class TestHandleProcessExecutionError(test.TestCase): - def test_no_exception(self): - with srb.handle_process_execution_error( - message='', info_message='', reraise=True): - pass - - def test_other_exception(self): - def f(): - with srb.handle_process_execution_error( - message='', info_message='', reraise=True): - 1 / 0 - - self.assertRaises(ZeroDivisionError, f) - - def test_reraise_true(self): - def f(): - with srb.handle_process_execution_error( - message='', info_message='', reraise=True): - raise processutils.ProcessExecutionError(description='Oops') - - self.assertRaisesRegex(processutils.ProcessExecutionError, - r'^Oops', f) - - def test_reraise_false(self): - with srb.handle_process_execution_error( - message='', info_message='', reraise=False): - raise processutils.ProcessExecutionError(description='Oops') - - def test_reraise_exception(self): - def f(): - with srb.handle_process_execution_error( - message='', info_message='', reraise=RuntimeError('Oops')): - raise processutils.ProcessExecutionError - - self.assertRaisesRegex(RuntimeError, r'^Oops', f) - - -class SRBDriverTestCase(test.TestCase): - """Test case for the Scality Rest Block driver.""" - - def __init__(self, *args, **kwargs): - super(SRBDriverTestCase, self).__init__(*args, **kwargs) - self._urls = [] - self._volumes = { - "fake-old-volume": { - "name": "fake-old-volume", - "size": 4 * units.Gi, - "vgs": { - "fake-old-volume": { - "lvs": {"vol1": 4 * units.Gi}, - "snaps": ["snap1", "snap2", "snap3"], - }, - }, - }, - "volume-extend": { - "name": "volume-extend", - "size": 4 * units.Gi, - "vgs": { - "volume-extend": { - "lvs": {"volume-extend-pool": 0.95 * 4 * units.Gi, - "volume-extend": 4 * units.Gi}, - "snaps": [], - }, - }, - }, - "volume-SnapBase": { - "name": "volume-SnapBase", - "size": 4 * units.Gi, - "vgs": { - "volume-SnapBase": { - "lvs": {"volume-SnapBase-pool": 0.95 * 4 * units.Gi, - "volume-SnapBase": 4 * units.Gi}, - "snaps": ['snapshot-SnappedBase', 'snapshot-delSnap'], - }, - }, - }, - } - - @staticmethod - def _convert_size(s): - if isinstance(s, six.integer_types): - return s - - try: - return int(s) - except ValueError: - pass - - conv_map = { - 'g': units.Gi, - 'G': units.Gi, - 'm': units.Mi, - 'M': units.Mi, - 'k': units.Ki, - 'K': units.Ki, - } - - if s[-1] in conv_map: - return int(s[:-1]) * conv_map[s[-1]] - - raise ValueError('Unknown size: %r' % s) - - def _fake_add_urls(self): - def check(cmd_string): - return 'tee, /sys/class/srb/add_urls' in cmd_string - - def act(cmd): - self._urls.append(cmd[2]) - - return check, act - - def _fake_create(self): - def check(cmd_string): - return 'tee, /sys/class/srb/create' in cmd_string - - def act(cmd): - volname = cmd[2].split()[0] - volsize = cmd[2].split()[1] - self._volumes[volname] = { - "name": volname, - "size": self._convert_size(volsize), - "vgs": { - }, - } - - return check, act - - def _fake_destroy(self): - def check(cmd_string): - return 'tee, /sys/class/srb/destroy' in cmd_string - - def act(cmd): - volname = cmd[2] - del self._volumes[volname] - - return check, act - - def _fake_extend(self): - def check(cmd_string): - return 'tee, /sys/class/srb/extend' in cmd_string - - def act(cmd): - volname = cmd[2].split()[0] - volsize = cmd[2].split()[1] - self._volumes[volname]["size"] = self._convert_size(volsize) - - return check, act - - def _fake_attach(self): - def check(cmd_string): - return 'tee, /sys/class/srb/attach' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_detach(self): - def check(cmd_string): - return 'tee, /sys/class/srb/detach' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_vg_list(self): - def check(cmd_string): - return 'env, LC_ALL=C, vgs, --noheadings, -o, name' in cmd_string - - def act(cmd): - # vg exists - data = " fake-outer-vg\n" - for vname in self._volumes: - vol = self._volumes[vname] - for vgname in vol['vgs']: - data += " " + vgname + "\n" - - return data - - return check, act - - def _fake_thinpool_free_space(self): - def check(cmd_string): - return 'env, LC_ALL=C, lvs, --noheadings, --unit=g, '\ - '-o, size,data_percent, --separator, :, --nosuffix'\ - in cmd_string - - def act(cmd): - data = '' - - groupname, poolname = cmd[10].split('/')[2:4] - for vname in self._volumes: - vol = self._volumes[vname] - for vgname in vol['vgs']: - if vgname != groupname: - continue - vg = vol['vgs'][vgname] - for lvname in vg['lvs']: - if poolname != lvname: - continue - lv_size = vg['lvs'][lvname] - data += " %.2f:0.00\n" % (lv_size / units.Gi) - - return data - - return check, act - - def _fake_vgs_version(self): - def check(cmd_string): - return 'env, LC_ALL=C, vgs, --version' in cmd_string - - def act(cmd): - return " LVM version: 2.02.95(2) (2012-03-06)\n" - - return check, act - - def _fake_get_all_volumes(self): - def check(cmd_string): - return 'env, LC_ALL=C, lvs, --noheadings, --unit=g, ' \ - '-o, vg_name,name,size, --nosuffix' in cmd_string - - def act(cmd): - # get_all_volumes - data = " fake-outer-vg fake-1 1.00g\n" - for vname in self._volumes: - vol = self._volumes[vname] - for vgname in vol['vgs']: - vg = vol['vgs'][vgname] - for lvname in vg['lvs']: - lv_size = vg['lvs'][lvname] - data += " %s %s %.2fg\n" %\ - (vgname, lvname, lv_size) - - return data - - return check, act - - def _fake_get_all_physical_volumes(self): - def check(cmd_string): - return 'env, LC_ALL=C, pvs, --noheadings, --unit=g, ' \ - '-o, vg_name,name,size,free, --separator, |, ' \ - '--nosuffix' in cmd_string - - def act(cmd): - data = " fake-outer-vg|/dev/fake1|10.00|1.00\n" - for vname in self._volumes: - vol = self._volumes[vname] - for vgname in vol['vgs']: - vg = vol['vgs'][vgname] - for lvname in vg['lvs']: - lv_size = vg['lvs'][lvname] - data += " %s|/dev/srb/%s/device|%.2f|%.2f\n" %\ - (vgname, vol['name'], - lv_size / units.Gi, lv_size / units.Gi) - - return data - - return check, act - - def _fake_get_all_volume_groups(self): - def check(cmd_string): - return 'env, LC_ALL=C, vgs, --noheadings, --unit=g, ' \ - '-o, name,size,free,lv_count,uuid, --separator, :, ' \ - '--nosuffix' in cmd_string - - def act(cmd): - data = '' - - search_vgname = None - if len(cmd) == 11: - search_vgname = cmd[10] - # get_all_volume_groups - if search_vgname is None: - data = " fake-outer-vg:10.00:10.00:0:"\ - "kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n" - for vname in self._volumes: - vol = self._volumes[vname] - for vgname in vol['vgs']: - if search_vgname is None or search_vgname == vgname: - vg = vol['vgs'][vgname] - data += " %s:%.2f:%.2f:%i:%s\n" %\ - (vgname, - vol['size'] / units.Gi, vol['size'] / units.Gi, - len(vg['lvs']) + len(vg['snaps']), vgname) - - return data - - return check, act - - def _fake_udevadm_settle(self): - def check(cmd_string): - return 'udevadm, settle, ' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_vgcreate(self): - def check(cmd_string): - return 'vgcreate, ' in cmd_string - - def act(cmd): - volname = "volume-%s" % (cmd[2].split('/')[2].split('-')[1]) - vgname = cmd[1] - self._volumes[volname]['vgs'][vgname] = { - "lvs": {}, - "snaps": [] - } - - return check, act - - def _fake_vgremove(self): - def check(cmd_string): - return 'vgremove, -f, ' in cmd_string - - def act(cmd): - volname = cmd[2] - del self._volumes[volname]['vgs'][volname] - - return check, act - - def _fake_vgchange_ay(self): - def check(cmd_string): - return 'vgchange, -ay, ' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_vgchange_an(self): - def check(cmd_string): - return 'vgchange, -an, ' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_lvcreate_T_L(self): - def check(cmd_string): - return 'lvcreate, -T, -L, ' in cmd_string - - def act(cmd): - vgname = cmd[6].split('/')[0] - lvname = cmd[6].split('/')[1] - if cmd[5][-1] == 'g': - lv_size = int(float(cmd[5][0:-1]) * units.Gi) - elif cmd[5][-1] == 'B': - lv_size = int(cmd[5][0:-1]) - else: - lv_size = int(cmd[5]) - self._volumes[vgname]['vgs'][vgname]['lvs'][lvname] = lv_size - - return check, act - - def _fake_lvcreate_T_V(self): - def check(cmd_string): - return 'lvcreate, -T, -V, ' in cmd_string - - def act(cmd): - cmd_string = ', '.join(cmd) - - vgname = cmd[8].split('/')[0] - poolname = cmd[8].split('/')[1] - lvname = cmd[7] - if poolname not in self._volumes[vgname]['vgs'][vgname]['lvs']: - raise AssertionError('thin-lv creation attempted before ' - 'thin-pool creation: %s' - % cmd_string) - if cmd[5][-1] == 'g': - lv_size = int(float(cmd[5][0:-1]) * units.Gi) - elif cmd[5][-1] == 'B': - lv_size = int(cmd[5][0:-1]) - else: - lv_size = int(cmd[5]) - self._volumes[vgname]['vgs'][vgname]['lvs'][lvname] = lv_size - - return check, act - - def _fake_lvcreate_name(self): - def check(cmd_string): - return 'lvcreate, --name, ' in cmd_string - - def act(cmd): - cmd_string = ', '.join(cmd) - - vgname = cmd[6].split('/')[0] - lvname = cmd[6].split('/')[1] - snapname = cmd[4] - if lvname not in self._volumes[vgname]['vgs'][vgname]['lvs']: - raise AssertionError('snap creation attempted on non-existent ' - 'thin-lv: %s' % cmd_string) - if snapname[1:] in self._volumes[vgname]['vgs'][vgname]['snaps']: - raise AssertionError('snap creation attempted on existing ' - 'snapshot: %s' % cmd_string) - self._volumes[vgname]['vgs'][vgname]['snaps'].append(snapname[1:]) - - return check, act - - def _fake_lvchange(self): - def check(cmd_string): - return 'lvchange, -a, y, --yes' in cmd_string or \ - 'lvchange, -a, n' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_lvremove(self): - - def check(cmd_string): - return 'lvremove, --config, activation ' \ - '{ retry_deactivation = 1}, -f, ' in cmd_string - - def act(cmd): - cmd_string = ', '.join(cmd) - - vgname = cmd[4].split('/')[0] - lvname = cmd[4].split('/')[1] - if lvname in self._volumes[vgname]['vgs'][vgname]['lvs']: - del self._volumes[vgname]['vgs'][vgname]['lvs'][lvname] - elif lvname in self._volumes[vgname]['vgs'][vgname]['snaps']: - self._volumes[vgname]['vgs'][vgname]['snaps'].remove(lvname) - else: - raise AssertionError('Cannot delete inexistant lv or snap' - 'thin-lv: %s' % cmd_string) - - return check, act - - def _fake_lvdisplay(self): - def check(cmd_string): - return 'env, LC_ALL=C, lvdisplay, --noheading, -C, -o, Attr, ' \ - in cmd_string - - def act(cmd): - data = '' - cmd_string = ', '.join(cmd) - - vgname = cmd[7].split('/')[0] - lvname = cmd[7].split('/')[1] - if lvname not in self._volumes[vgname]['vgs'][vgname]['lvs']: - raise AssertionError('Cannot check snaps for inexistant lv' - ': %s' % cmd_string) - if len(self._volumes[vgname]['vgs'][vgname]['snaps']): - data = ' owi-a-\n' - else: - data = ' wi-a-\n' - - return data - - return check, act - - def _fake_lvextend(self): - def check(cmd_string): - return 'lvextend, -L, ' in cmd_string - - def act(cmd): - cmd_string = ', '.join(cmd) - vgname = cmd[5].split('/')[0] - lvname = cmd[5].split('/')[1] - if cmd[4][-1] == 'g': - size = int(float(cmd[4][0:-1]) * units.Gi) - elif cmd[4][-1] == 'B': - size = int(cmd[4][0:-1]) - else: - size = int(cmd[4]) - if vgname not in self._volumes: - raise AssertionError('Cannot extend inexistant volume' - ': %s' % cmd_string) - if lvname not in self._volumes[vgname]['vgs'][vgname]['lvs']: - raise AssertionError('Cannot extend inexistant lv' - ': %s' % cmd_string) - self._volumes[vgname]['vgs'][vgname]['lvs'][lvname] = size - - return check, act - - def _fake_pvresize(self): - def check(cmd_string): - return 'pvresize, ' in cmd_string - - def act(_): - pass - - return check, act - - def _fake_execute(self, *cmd, **kwargs): - # Initial version of this driver used to perform commands this way : - # sh echo $cmd > /sys/class/srb - # As noted in LP #1414531 this is wrong, it should be - # tee /sys/class/srb < $cmd - # To avoid having to rewrite every unit tests, we insert the STDIN - # as part of the original command - if 'process_input' in kwargs: - cmd = cmd + (kwargs['process_input'],) - cmd_string = ', '.join(cmd) - ## - # To test behavior, we need to stub part of the brick/local_dev/lvm - # functions too, because we want to check the state between calls, - # not only if the calls were done - ## - - handlers = [ - self._fake_add_urls(), - self._fake_attach(), - self._fake_create(), - self._fake_destroy(), - self._fake_detach(), - self._fake_extend(), - self._fake_get_all_physical_volumes(), - self._fake_get_all_volume_groups(), - self._fake_get_all_volumes(), - self._fake_lvchange(), - self._fake_lvcreate_T_L(), - self._fake_lvcreate_T_V(), - self._fake_lvcreate_name(), - self._fake_lvdisplay(), - self._fake_lvextend(), - self._fake_lvremove(), - self._fake_pvresize(), - self._fake_thinpool_free_space(), - self._fake_udevadm_settle(), - self._fake_vg_list(), - self._fake_vgchange_an(), - self._fake_vgchange_ay(), - self._fake_vgcreate(), - self._fake_vgremove(), - self._fake_vgs_version(), - ] - - for (check, act) in handlers: - if check(cmd_string): - out = act(cmd) - return (out, '') - - self.fail('Unexpected command: %s' % cmd_string) - - def _configure_driver(self): - srb.CONF.srb_base_urls = "http://127.0.0.1/volumes" - - def setUp(self): - super(SRBDriverTestCase, self).setUp() - - self.configuration = conf.Configuration(None) - self._driver = srb.SRBDriver(configuration=self.configuration) - # Stub processutils.execute for static methods - self.stubs.Set(processutils, 'execute', self._fake_execute) - exec_patcher = mock.patch.object(self._driver, - '_execute', - self._fake_execute) - exec_patcher.start() - self.addCleanup(exec_patcher.stop) - self._configure_driver() - - def test_setup(self): - """The url shall be added automatically""" - self._driver.do_setup(None) - self.assertEqual('http://127.0.0.1/volumes', - self._urls[0]) - self._driver.check_for_setup_error() - - @mock.patch.object(srb.CONF, 'srb_base_urls', "http://; evil") - def test_setup_malformated_url(self): - self.assertRaises(exception.VolumeBackendAPIException, - self._driver.do_setup, None) - - def test_setup_no_config(self): - """The driver shall not start without any url configured""" - srb.CONF.srb_base_urls = None - self.assertRaises(exception.VolumeBackendAPIException, - self._driver.do_setup, None) - - def test_volume_create(self): - """"Test volume create. - - The volume will be added in the internal state through fake_execute. - """ - volume = {'name': 'volume-test', 'id': 'test', 'size': 4 * units.Gi} - old_vols = self._volumes - updates = self._driver.create_volume(volume) - self.assertEqual({'provider_location': volume['name']}, updates) - new_vols = self._volumes - old_vols['volume-test'] = { - 'name': 'volume-test', - 'size': 4 * units.Gi, - 'vgs': { - 'volume-test': { - 'lvs': {'volume-test-pool': 0.95 * 4 * units.Gi, - 'volume-test': 4 * units.Gi}, - 'snaps': [], - }, - }, - } - self.assertDictMatch(old_vols, new_vols) - - def test_volume_delete(self): - vol = {'name': 'volume-delete', 'id': 'delete', 'size': units.Gi} - - old_vols = self._volumes - self._volumes['volume-delete'] = { - 'name': 'volume-delete', - 'size': units.Gi, - 'vgs': { - 'volume-delete': { - 'lvs': {'volume-delete-pool': 0.95 * units.Gi, - 'volume-delete': units.Gi}, - 'snaps': [], - }, - }, - } - self._driver.delete_volume(vol) - new_vols = self._volumes - self.assertDictMatch(old_vols, new_vols) - - def test_volume_create_and_delete(self): - volume = {'name': 'volume-autoDelete', 'id': 'autoDelete', - 'size': 4 * units.Gi} - old_vols = self._volumes - updates = self._driver.create_volume(volume) - self.assertEqual({'provider_location': volume['name']}, updates) - self._driver.delete_volume(volume) - new_vols = self._volumes - self.assertDictMatch(old_vols, new_vols) - - def test_volume_create_cloned(self): - with mock.patch('cinder.volume.utils.copy_volume'): - new = {'name': 'volume-cloned', 'size': 4 * units.Gi, - 'id': 'cloned'} - old = {'name': 'volume-old', 'size': 4 * units.Gi, 'id': 'old'} - old_vols = self._volumes - self._volumes['volume-old'] = { - 'name': 'volume-old', - 'size': 4 * units.Gi, - 'vgs': { - 'volume-old': { - 'name': 'volume-old', - 'lvs': {'volume-old-pool': 0.95 * 4 * units.Gi, - 'volume-old': 4 * units.Gi}, - 'snaps': [], - }, - }, - } - self._driver.create_cloned_volume(new, old) - new_vols = self._volumes - old_vols['volume-cloned'] = { - 'name': 'volume-cloned', - 'size': 4 * units.Gi, - 'vgs': { - 'volume-cloned': { - 'name': 'volume-cloned', - 'lvs': {'volume-cloned-pool': 0.95 * 4 * units.Gi, - 'volume-cloned': 4 * units.Gi}, - 'snaps': [], - }, - }, - } - self.assertDictMatch(old_vols, new_vols) - - def test_volume_create_from_snapshot(self): - cp_vol_patch = mock.patch('cinder.volume.utils.copy_volume') - lv_activ_patch = mock.patch( - 'cinder.brick.local_dev.lvm.LVM.activate_lv') - - with cp_vol_patch as cp_vol, lv_activ_patch as lv_activ: - old_vols = self._volumes - newvol = {"name": "volume-SnapClone", "id": "SnapClone", - "size": 4 * units.Gi} - srcsnap = {"name": "snapshot-SnappedBase", "id": "SnappedBase", - "volume_id": "SnapBase", "volume_size": 4, - "volume_name": "volume-SnapBase"} - - self._driver.create_volume_from_snapshot(newvol, srcsnap) - - expected_lv_activ_calls = [ - mock.call(mock.ANY, srcsnap['volume_name'] + "-pool"), - mock.call(mock.ANY, srcsnap['name'], True) - ] - lv_activ.assert_has_calls(expected_lv_activ_calls, any_order=True) - cp_vol.assert_called_with( - '/dev/mapper/volume--SnapBase-_snapshot--SnappedBase', - '/dev/mapper/volume--SnapClone-volume--SnapClone', - srcsnap['volume_size'] * units.Ki, - self._driver.configuration.volume_dd_blocksize, - execute=self._fake_execute) - - new_vols = self._volumes - old_vols['volume-SnapClone'] = { - "name": 'volume-SnapClone', - "id": 'SnapClone', - "size": 30, - "vgs": { - "name": 'volume-SnapClone', - "lvs": {'volume-SnapClone-pool': 0.95 * 30 * units.Gi, - 'volume-SnapClone': 30 * units.Gi}, - "snaps": [], - }, - } - self.assertDictMatch(old_vols, new_vols) - - def test_volume_snapshot_create(self): - old_vols = self._volumes - snap = {"name": "snapshot-SnapBase1", - "id": "SnapBase1", - "volume_name": "volume-SnapBase", - "volume_id": "SnapBase", - "volume_size": 4} - self._driver.create_snapshot(snap) - new_vols = self._volumes - old_vols['volume-SnapBase']["vgs"]['volume-SnapBase']["snaps"].\ - append('snapshot-SnapBase1') - self.assertDictMatch(old_vols, new_vols) - - def test_volume_snapshot_delete(self): - old_vols = self._volumes - snap = {"name": "snapshot-delSnap", - "id": "delSnap", - "volume_name": "volume-SnapBase", - "volume_id": "SnapBase", - "volume_size": 4} - self._driver.delete_snapshot(snap) - new_vols = self._volumes - old_vols['volume-SnapBase']["vgs"]['volume-SnapBase']["snaps"].\ - remove(snap['name']) - self.assertDictMatch(old_vols, new_vols) - self.assertEqual( - set(old_vols['volume-SnapBase']['vgs'] - ['volume-SnapBase']['snaps']), - set(new_vols['volume-SnapBase']['vgs'] - ['volume-SnapBase']['snaps'])) - - def test_volume_copy_from_image(self): - with (mock.patch('cinder.image.image_utils.fetch_to_volume_format'))\ - as fetch: - vol = {'name': 'volume-SnapBase', 'id': 'SnapBase', - 'size': 5 * units.Gi} - self._driver.copy_image_to_volume(context, - vol, - 'image_service', - 'image_id') - fetch.assert_called_once_with(context, - 'image_service', - 'image_id', - self._driver._mapper_path(vol), - 'qcow2', - self._driver. - configuration.volume_dd_blocksize, - size=vol['size']) - - def test_volume_copy_to_image(self): - with mock.patch('cinder.image.image_utils.upload_volume') as upload: - vol = {'name': 'volume-SnapBase', 'id': 'SnapBase', - 'size': 5 * units.Gi} - self._driver.copy_volume_to_image(context, - vol, - 'image_service', - 'image_meta') - upload.assert_called_once_with(context, - 'image_service', - 'image_meta', - self._driver._mapper_path(vol)) - - def test_volume_extend(self): - vol = {'name': 'volume-extend', 'id': 'extend', 'size': 4 * units.Gi} - new_size = 5 - - self._driver.extend_volume(vol, new_size) - - new_vols = self._volumes - self.assertEqual(srb.SRBDriver.OVER_ALLOC_RATIO * new_size * units.Gi, - new_vols['volume-extend']['size']) diff --git a/cinder/volume/drivers/srb.py b/cinder/volume/drivers/srb.py deleted file mode 100644 index 351b26471cf..00000000000 --- a/cinder/volume/drivers/srb.py +++ /dev/null @@ -1,884 +0,0 @@ -# Copyright (c) 2014 Scality -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Volume driver for the Scality REST Block storage system - -This driver provisions Linux SRB volumes leveraging RESTful storage platforms -(e.g. Scality CDMI). -""" - -import contextlib -import functools -import re -import sys -import time - -from oslo_concurrency import lockutils -from oslo_concurrency import processutils as putils -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import excutils -from oslo_utils import units -import six -from six.moves import range - -from cinder.brick.local_dev import lvm -from cinder import exception -from cinder.i18n import _, _LI, _LE, _LW -from cinder.image import image_utils -from cinder import utils -from cinder.volume import driver -from cinder.volume import utils as volutils - - -LOG = logging.getLogger(__name__) - -srb_opts = [ - cfg.StrOpt('srb_base_urls', - help='Comma-separated list of REST servers IP to connect to. ' - '(eg http://IP1/,http://IP2:81/path'), -] - -CONF = cfg.CONF -CONF.register_opts(srb_opts) - -ACCEPTED_REST_SERVER = re.compile(r'^http://' - '(\d{1,3}\.){3}\d{1,3}' - '(:\d+)?/[a-zA-Z0-9\-_\/]*$') - - -class retry(object): - SLEEP_NONE = 'none' - SLEEP_DOUBLE = 'double' - SLEEP_INCREMENT = 'increment' - - def __init__(self, exceptions, count, sleep_mechanism=SLEEP_INCREMENT, - sleep_factor=1): - if sleep_mechanism not in [self.SLEEP_NONE, - self.SLEEP_DOUBLE, - self.SLEEP_INCREMENT]: - raise ValueError('Invalid value for `sleep_mechanism` argument') - - self._exceptions = exceptions - self._count = count - self._sleep_mechanism = sleep_mechanism - self._sleep_factor = sleep_factor - - def __call__(self, fun): - func_name = fun.__name__ - - @functools.wraps(fun) - def wrapped(*args, **kwargs): - sleep_time = self._sleep_factor - exc_info = None - - for attempt in range(self._count): - if attempt != 0: - LOG.warning(_LW('Retrying failed call to %(func)s, ' - 'attempt %(attempt)i.'), - {'func': func_name, - 'attempt': attempt}) - try: - return fun(*args, **kwargs) - except self._exceptions: - exc_info = sys.exc_info() - - if attempt != self._count - 1: - if self._sleep_mechanism == self.SLEEP_NONE: - continue - elif self._sleep_mechanism == self.SLEEP_INCREMENT: - time.sleep(sleep_time) - sleep_time += self._sleep_factor - elif self._sleep_mechanism == self.SLEEP_DOUBLE: - time.sleep(sleep_time) - sleep_time *= 2 - else: - raise ValueError('Unknown sleep mechanism: %r' - % self._sleep_mechanism) - - six.reraise(exc_info[0], exc_info[1], exc_info[2]) - - return wrapped - - -class LVM(lvm.LVM): - def activate_vg(self): - """Activate the Volume Group associated with this instantiation. - - :raises: putils.ProcessExecutionError - """ - - cmd = ['vgchange', '-ay', self.vg_name] - try: - self._execute(*cmd, - root_helper=self._root_helper, - run_as_root=True) - except putils.ProcessExecutionError as err: - LOG.exception(_LE('Error activating Volume Group')) - LOG.error(_LE('Cmd :%s'), err.cmd) - LOG.error(_LE('StdOut :%s'), err.stdout) - LOG.error(_LE('StdErr :%s'), err.stderr) - raise - - def deactivate_vg(self): - """Deactivate the Volume Group associated with this instantiation. - - This forces LVM to release any reference to the device. - - :raises: putils.ProcessExecutionError - """ - - cmd = ['vgchange', '-an', self.vg_name] - try: - self._execute(*cmd, - root_helper=self._root_helper, - run_as_root=True) - except putils.ProcessExecutionError as err: - LOG.exception(_LE('Error deactivating Volume Group')) - LOG.error(_LE('Cmd :%s'), err.cmd) - LOG.error(_LE('StdOut :%s'), err.stdout) - LOG.error(_LE('StdErr :%s'), err.stderr) - raise - - def destroy_vg(self): - """Destroy the Volume Group associated with this instantiation. - - :raises: putils.ProcessExecutionError - """ - - cmd = ['vgremove', '-f', self.vg_name] - try: - self._execute(*cmd, - root_helper=self._root_helper, - run_as_root=True) - except putils.ProcessExecutionError as err: - LOG.exception(_LE('Error destroying Volume Group')) - LOG.error(_LE('Cmd :%s'), err.cmd) - LOG.error(_LE('StdOut :%s'), err.stdout) - LOG.error(_LE('StdErr :%s'), err.stderr) - raise - - def pv_resize(self, pv_name, new_size_str): - """Extend the size of an existing PV (for virtual PVs). - - :raises: putils.ProcessExecutionError - """ - try: - cmd = lvm.LVM.LVM_CMD_PREFIX + ['pvresize', - '--setphysicalvolumesize', - new_size_str, pv_name] - self._execute(*cmd, root_helper=self._root_helper, - run_as_root=True) - except putils.ProcessExecutionError as err: - LOG.exception(_LE('Error resizing Physical Volume')) - LOG.error(_LE('Cmd :%s'), err.cmd) - LOG.error(_LE('StdOut :%s'), err.stdout) - LOG.error(_LE('StdErr :%s'), err.stderr) - raise - - def extend_thin_pool(self): - """Extend the size of the thin provisioning pool. - - This method extends the size of a thin provisioning pool to 95% of the - size of the VG, if the VG is configured as thin and owns a thin - provisioning pool. - - :raises: putils.ProcessExecutionError - """ - if self.vg_thin_pool is None: - return - - new_size_str = self._calculate_thin_pool_size() - try: - cmd = lvm.LVM.LVM_CMD_PREFIX + ['lvextend', '-L', new_size_str, - "%s/%s-pool" % (self.vg_name, - self.vg_name)] - self._execute(*cmd, - root_helper=self._root_helper, - run_as_root=True) - except putils.ProcessExecutionError as err: - LOG.exception(_LE('Error extending thin provisioning pool')) - LOG.error(_LE('Cmd :%s'), err.cmd) - LOG.error(_LE('StdOut :%s'), err.stdout) - LOG.error(_LE('StdErr :%s'), err.stderr) - raise - - -@contextlib.contextmanager -def patched(obj, attr, fun): - """Context manager to locally patch a method. - - Within the managed context, the `attr` method of `obj` will be replaced by - a method which calls `fun` passing in the original `attr` attribute of - `obj` as well as any positional and keyword arguments. - - At the end of the context, the original method is restored. - """ - - orig = getattr(obj, attr) - - def patch(*args, **kwargs): - return fun(orig, *args, **kwargs) - - setattr(obj, attr, patch) - - try: - yield - finally: - setattr(obj, attr, orig) - - -@contextlib.contextmanager -def handle_process_execution_error(message, info_message, reraise=True): - """Consistently handle `putils.ProcessExecutionError` exceptions - - This context-manager will catch any `putils.ProcessExecutionError` - exceptions raised in the managed block, and generate logging output - accordingly. - - The value of the `message` argument will be logged at `logging.ERROR` - level, and the `info_message` argument at `logging.INFO` level. Finally - the command string, exit code, standard output and error output of the - process will be logged at `logging.DEBUG` level. - - The `reraise` argument specifies what should happen when a - `putils.ProcessExecutionError` is caught. If it's equal to `True`, the - exception will be re-raised. If it's some other non-`False` object, this - object will be raised instead (so you most likely want it to be some - `Exception`). Any `False` value will result in the exception to be - swallowed. - """ - - try: - yield - except putils.ProcessExecutionError as exc: - LOG.error(message) - - LOG.info(info_message) - LOG.debug('Command : %s', exc.cmd) - LOG.debug('Exit Code : %r', exc.exit_code) - LOG.debug('StdOut : %s', exc.stdout) - LOG.debug('StdErr : %s', exc.stderr) - - if reraise is True: - raise - elif reraise: - raise reraise # pylint: disable=E0702 - - -@contextlib.contextmanager -def temp_snapshot(driver, volume, src_vref): - snapshot = {'volume_name': src_vref['name'], - 'volume_id': src_vref['id'], - 'volume_size': src_vref['size'], - 'name': 'snapshot-clone-%s' % volume['id'], - 'id': 'tmp-snap-%s' % volume['id'], - 'size': src_vref['size']} - - driver.create_snapshot(snapshot) - - try: - yield snapshot - finally: - driver.delete_snapshot(snapshot) - - -@contextlib.contextmanager -def temp_raw_device(driver, volume): - driver._attach_file(volume) - - try: - yield - finally: - driver._detach_file(volume) - - -@contextlib.contextmanager -def temp_lvm_device(driver, volume): - with temp_raw_device(driver, volume): - vg = driver._get_lvm_vg(volume) - vg.activate_vg() - - yield vg - - -class SRBDriver(driver.VolumeDriver): - """Scality SRB volume driver - - This driver manages volumes provisioned by the Scality REST Block driver - Linux kernel module, backed by RESTful storage providers (e.g. Scality - CDMI). - """ - - VERSION = '1.1.0' - - # Over-allocation ratio (multiplied with requested size) for thin - # provisioning - OVER_ALLOC_RATIO = 2 - SNAPSHOT_PREFIX = 'snapshot' - - def __init__(self, *args, **kwargs): - super(SRBDriver, self).__init__(*args, **kwargs) - self.configuration.append_config_values(srb_opts) - self.urls_setup = False - self.backend_name = None - self.base_urls = None - self.root_helper = utils.get_root_helper() - self._attached_devices = {} - - def _setup_urls(self): - if not self.base_urls: - message = _("No url configured") - raise exception.VolumeBackendAPIException(data=message) - - with handle_process_execution_error( - message=_LE('Cound not setup urls on the Block Driver.'), - info_message=_LI('Error creating Volume'), - reraise=False): - cmd = self.base_urls - path = '/sys/class/srb/add_urls' - putils.execute('tee', path, process_input=cmd, - root_helper=self.root_helper, run_as_root=True) - self.urls_setup = True - - def do_setup(self, context): - """Any initialization the volume driver does while starting.""" - self.backend_name = self.configuration.safe_get('volume_backend_name') - - base_urls = self.configuration.safe_get('srb_base_urls') - sane_urls = [] - if base_urls: - for url in base_urls.split(','): - stripped_url = url.strip() - if ACCEPTED_REST_SERVER.match(stripped_url): - sane_urls.append(stripped_url) - else: - LOG.warning(_LW("%s is not an accepted REST server " - "IP address"), stripped_url) - - self.base_urls = ','.join(sane_urls) - self._setup_urls() - - def check_for_setup_error(self): - """Returns an error if prerequisites aren't met.""" - if not self.base_urls: - LOG.warning(_LW("Configuration variable srb_base_urls" - " not set or empty.")) - - if self.urls_setup is False: - message = _("Could not setup urls properly") - raise exception.VolumeBackendAPIException(data=message) - - @classmethod - def _is_snapshot(cls, volume): - return volume['name'].startswith(cls.SNAPSHOT_PREFIX) - - @classmethod - def _get_volname(cls, volume): - """Returns the name of the actual volume - - If the volume is a snapshot, it returns the name of the parent volume. - otherwise, returns the volume's name. - """ - name = volume['name'] - if cls._is_snapshot(volume): - name = "volume-%s" % (volume['volume_id']) - return name - - @classmethod - def _get_volid(cls, volume): - """Returns the ID of the actual volume - - If the volume is a snapshot, it returns the ID of the parent volume. - otherwise, returns the volume's id. - """ - volid = volume['id'] - if cls._is_snapshot(volume): - volid = volume['volume_id'] - return volid - - @classmethod - def _device_name(cls, volume): - volume_id = cls._get_volid(volume) - name = 'cinder-%s' % volume_id - - # Device names can't be longer than 32 bytes (incl. \0) - return name[:31] - - @classmethod - def _device_path(cls, volume): - return "/dev/" + cls._device_name(volume) - - @classmethod - def _escape_snapshot(cls, snapshot_name): - # Linux LVM reserves name that starts with snapshot, so that - # such volume name can't be created. Mangle it. - if not snapshot_name.startswith(cls.SNAPSHOT_PREFIX): - return snapshot_name - return '_' + snapshot_name - - @classmethod - def _mapper_path(cls, volume): - groupname = cls._get_volname(volume) - name = volume['name'] - if cls._is_snapshot(volume): - name = cls._escape_snapshot(name) - # NOTE(vish): stops deprecation warning - groupname = groupname.replace('-', '--') - name = name.replace('-', '--') - return "/dev/mapper/%s-%s" % (groupname, name) - - @staticmethod - def _size_int(size_in_g): - try: - return max(int(size_in_g), 1) - except ValueError: - message = (_("Invalid size parameter '%s': Cannot be interpreted" - " as an integer value.") - % size_in_g) - LOG.error(message) - raise exception.VolumeBackendAPIException(data=message) - - @classmethod - def _set_device_path(cls, volume): - volume['provider_location'] = cls._get_volname(volume) - return { - 'provider_location': volume['provider_location'], - } - - @staticmethod - def _activate_lv(orig, *args, **kwargs): - """Activate lv. - - Use with `patched` to patch `lvm.LVM.activate_lv` to ignore `EEXIST` - """ - try: - orig(*args, **kwargs) - except putils.ProcessExecutionError as exc: - if exc.exit_code != 5: - raise - else: - LOG.debug('`activate_lv` returned 5, ignored') - - def _get_lvm_vg(self, volume, create_vg=False): - # NOTE(joachim): One-device volume group to manage thin snapshots - # Get origin volume name even for snapshots - volume_name = self._get_volname(volume) - physical_volumes = [self._device_path(volume)] - - with patched(lvm.LVM, 'activate_lv', self._activate_lv): - return LVM(volume_name, utils.get_root_helper(), - create_vg=create_vg, - physical_volumes=physical_volumes, - lvm_type='thin', executor=self._execute) - - @staticmethod - def _volume_not_present(vg, volume_name): - # Used to avoid failing to delete a volume for which - # the create operation partly failed - return vg.get_volume(volume_name) is None - - def _create_file(self, volume): - message = _('Could not create volume on any configured REST server.') - - with handle_process_execution_error( - message=message, - info_message=_LI('Error creating Volume %s.') % volume['name'], - reraise=exception.VolumeBackendAPIException(data=message)): - size = self._size_int(volume['size']) * self.OVER_ALLOC_RATIO - - cmd = volume['name'] - cmd += ' %dG' % size - path = '/sys/class/srb/create' - putils.execute('tee', path, process_input=cmd, - root_helper=self.root_helper, run_as_root=True) - - return self._set_device_path(volume) - - def _extend_file(self, volume, new_size): - message = _('Could not extend volume on any configured REST server.') - - with handle_process_execution_error( - message=message, - info_message=(_LI('Error extending Volume %s.') - % volume['name']), - reraise=exception.VolumeBackendAPIException(data=message)): - size = self._size_int(new_size) * self.OVER_ALLOC_RATIO - - cmd = volume['name'] - cmd += ' %dG' % size - path = '/sys/class/srb/extend' - putils.execute('tee', path, process_input=cmd, - root_helper=self.root_helper, run_as_root=True) - - @staticmethod - def _destroy_file(volume): - message = _('Could not destroy volume on any configured REST server.') - - volname = volume['name'] - with handle_process_execution_error( - message=message, - info_message=_LI('Error destroying Volume %s.') % volname, - reraise=exception.VolumeBackendAPIException(data=message)): - cmd = volume['name'] - path = '/sys/class/srb/destroy' - putils.execute('tee', path, process_input=cmd, - root_helper=utils.get_root_helper(), - run_as_root=True) - - # NOTE(joachim): Must only be called within a function decorated by: - # @lockutils.synchronized('devices', 'cinder-srb-') - def _increment_attached_count(self, volume): - """Increments the attach count of the device""" - volid = self._get_volid(volume) - if volid not in self._attached_devices: - self._attached_devices[volid] = 1 - else: - self._attached_devices[volid] += 1 - - # NOTE(joachim): Must only be called within a function decorated by: - # @lockutils.synchronized('devices', 'cinder-srb-') - def _decrement_attached_count(self, volume): - """Decrements the attach count of the device""" - volid = self._get_volid(volume) - if volid not in self._attached_devices: - raise exception.VolumeBackendAPIException( - (_("Internal error in srb driver: " - "Trying to detach detached volume %s.")) - % (self._get_volname(volume)) - ) - - self._attached_devices[volid] -= 1 - - if self._attached_devices[volid] == 0: - del self._attached_devices[volid] - - # NOTE(joachim): Must only be called within a function decorated by: - # @lockutils.synchronized('devices', 'cinder-srb-') - def _get_attached_count(self, volume): - volid = self._get_volid(volume) - - return self._attached_devices.get(volid, 0) - - @lockutils.synchronized('devices', 'cinder-srb-') - def _is_attached(self, volume): - return self._get_attached_count(volume) > 0 - - @lockutils.synchronized('devices', 'cinder-srb-') - def _attach_file(self, volume): - name = self._get_volname(volume) - devname = self._device_name(volume) - LOG.debug('Attaching volume %(name)s as %(devname)s', - {'name': name, 'devname': devname}) - - count = self._get_attached_count(volume) - if count == 0: - message = (_('Could not attach volume %(vol)s as %(dev)s ' - 'on system.') - % {'vol': name, 'dev': devname}) - with handle_process_execution_error( - message=message, - info_message=_LI('Error attaching Volume'), - reraise=exception.VolumeBackendAPIException(data=message)): - cmd = name + ' ' + devname - path = '/sys/class/srb/attach' - putils.execute('tee', path, process_input=cmd, - root_helper=self.root_helper, run_as_root=True) - else: - LOG.debug('Volume %s already attached', name) - - self._increment_attached_count(volume) - - @retry(exceptions=(putils.ProcessExecutionError, ), - count=3, sleep_mechanism=retry.SLEEP_INCREMENT, sleep_factor=5) - def _do_deactivate(self, volume, vg): - vg.deactivate_vg() - - @retry(exceptions=(putils.ProcessExecutionError, ), - count=5, sleep_mechanism=retry.SLEEP_DOUBLE, sleep_factor=1) - def _do_detach(self, volume, vg): - devname = self._device_name(volume) - volname = self._get_volname(volume) - cmd = devname - path = '/sys/class/srb/detach' - try: - putils.execute('tee', path, process_input=cmd, - root_helper=self.root_helper, run_as_root=True) - except putils.ProcessExecutionError: - with excutils.save_and_reraise_exception(reraise=True): - try: - with patched(lvm.LVM, 'activate_lv', self._activate_lv): - vg.activate_lv(volname) - - self._do_deactivate(volume, vg) - except putils.ProcessExecutionError: - LOG.warning(_LW('All attempts to recover failed detach ' - 'of %(volume)s failed.'), - {'volume': volname}) - - @lockutils.synchronized('devices', 'cinder-srb-') - def _detach_file(self, volume): - name = self._get_volname(volume) - devname = self._device_name(volume) - vg = self._get_lvm_vg(volume) - LOG.debug('Detaching device %s', devname) - - count = self._get_attached_count(volume) - if count > 1: - LOG.info(_LI('Reference count of %(volume)s is %(count)d, ' - 'not detaching.'), - {'volume': volume['name'], 'count': count}) - return - - message = (_('Could not detach volume %(vol)s from device %(dev)s.') - % {'vol': name, 'dev': devname}) - with handle_process_execution_error( - message=message, - info_message=_LI('Error detaching Volume'), - reraise=exception.VolumeBackendAPIException(data=message)): - try: - if vg is not None: - self._do_deactivate(volume, vg) - except putils.ProcessExecutionError: - LOG.error(_LE('Could not deactivate volume group %s'), - self._get_volname(volume)) - raise - - try: - self._do_detach(volume, vg=vg) - except putils.ProcessExecutionError: - LOG.error(_LE('Could not detach volume %(vol)s from device ' - '%(dev)s.'), {'vol': name, 'dev': devname}) - raise - - self._decrement_attached_count(volume) - - def _setup_lvm(self, volume): - # NOTE(joachim): One-device volume group to manage thin snapshots - size = self._size_int(volume['size']) * self.OVER_ALLOC_RATIO - size_str = '%dg' % size - vg = self._get_lvm_vg(volume, create_vg=True) - vg.create_volume(volume['name'], size_str, lv_type='thin') - - def _destroy_lvm(self, volume): - vg = self._get_lvm_vg(volume) - if vg.lv_has_snapshot(volume['name']): - LOG.error(_LE('Unable to delete due to existing snapshot ' - 'for volume: %s.'), - volume['name']) - raise exception.VolumeIsBusy(volume_name=volume['name']) - vg.destroy_vg() - # NOTE(joachim) Force lvm vg flush through a vgs command - vgs = vg.get_all_volume_groups(root_helper=self.root_helper, - vg_name=vg.vg_name) - if len(vgs) != 0: - LOG.warning(_LW('Removed volume group %s still appears in vgs.'), - vg.vg_name) - - def _create_and_copy_volume(self, dstvol, srcvol): - """Creates a volume from a volume or a snapshot.""" - updates = self._create_file(dstvol) - - # We need devices attached for IO operations. - with temp_lvm_device(self, srcvol) as vg, \ - temp_raw_device(self, dstvol): - self._setup_lvm(dstvol) - - # Some configurations of LVM do not automatically activate - # ThinLVM snapshot LVs. - with patched(lvm.LVM, 'activate_lv', self._activate_lv): - vg.activate_lv(srcvol['name'], True) - - # copy_volume expects sizes in MiB, we store integer GiB - # be sure to convert before passing in - volutils.copy_volume(self._mapper_path(srcvol), - self._mapper_path(dstvol), - srcvol['volume_size'] * units.Ki, - self.configuration.volume_dd_blocksize, - execute=self._execute) - - return updates - - def create_volume(self, volume): - """Creates a volume. - - Can optionally return a Dictionary of changes to the volume object to - be persisted. - """ - updates = self._create_file(volume) - # We need devices attached for LVM operations. - with temp_raw_device(self, volume): - self._setup_lvm(volume) - return updates - - def create_volume_from_snapshot(self, volume, snapshot): - """Creates a volume from a snapshot.""" - - return self._create_and_copy_volume(volume, snapshot) - - def create_cloned_volume(self, volume, src_vref): - """Creates a clone of the specified volume.""" - LOG.info(_LI('Creating clone of volume: %s'), src_vref['id']) - - updates = None - with temp_lvm_device(self, src_vref): - with temp_snapshot(self, volume, src_vref) as snapshot: - updates = self._create_and_copy_volume(volume, snapshot) - - return updates - - def delete_volume(self, volume): - """Deletes a volume.""" - attached = False - if self._is_attached(volume): - attached = True - with temp_lvm_device(self, volume): - self._destroy_lvm(volume) - self._detach_file(volume) - - LOG.debug('Deleting volume %(volume_name)s, attached=%(attached)s', - {'volume_name': volume['name'], 'attached': attached}) - - self._destroy_file(volume) - - def create_snapshot(self, snapshot): - """Creates a snapshot.""" - with temp_lvm_device(self, snapshot) as vg: - # NOTE(joachim) we only want to support thin lvm_types - vg.create_lv_snapshot(self._escape_snapshot(snapshot['name']), - snapshot['volume_name'], - lv_type='thin') - - def delete_snapshot(self, snapshot): - """Deletes a snapshot.""" - with temp_lvm_device(self, snapshot) as vg: - if self._volume_not_present( - vg, self._escape_snapshot(snapshot['name'])): - # If the snapshot isn't present, then don't attempt to delete - LOG.warning(_LW("snapshot: %s not found, " - "skipping delete operations"), - snapshot['name']) - return - - vg.delete(self._escape_snapshot(snapshot['name'])) - - def get_volume_stats(self, refresh=False): - """Return the current state of the volume service.""" - stats = { - 'vendor_name': 'Scality', - 'driver_version': self.VERSION, - 'storage_protocol': 'Scality Rest Block Device', - 'total_capacity_gb': 'infinite', - 'free_capacity_gb': 'infinite', - 'reserved_percentage': 0, - 'volume_backend_name': self.backend_name, - } - return stats - - def copy_image_to_volume(self, context, volume, image_service, image_id): - """Fetch the image from image_service and write it to the volume.""" - with temp_lvm_device(self, volume): - image_utils.fetch_to_volume_format(context, - image_service, - image_id, - self._mapper_path(volume), - 'qcow2', - self.configuration. - volume_dd_blocksize, - size=volume['size']) - - def copy_volume_to_image(self, context, volume, image_service, image_meta): - """Copy the volume to the specified image.""" - with temp_lvm_device(self, volume): - image_utils.upload_volume(context, - image_service, - image_meta, - self._mapper_path(volume)) - - def extend_volume(self, volume, new_size): - new_alloc_size = self._size_int(new_size) * self.OVER_ALLOC_RATIO - new_size_str = '%dg' % new_alloc_size - self._extend_file(volume, new_size) - with temp_lvm_device(self, volume) as vg: - vg.pv_resize(self._device_path(volume), new_size_str) - vg.extend_thin_pool() - vg.extend_volume(volume['name'], new_size_str) - - -class SRBISCSIDriver(SRBDriver, driver.ISCSIDriver): - """Scality SRB volume driver with ISCSI support - - This driver manages volumes provisioned by the Scality REST Block driver - Linux kernel module, backed by RESTful storage providers (e.g. Scality - CDMI), and exports them through ISCSI to Nova. - """ - - VERSION = '1.0.0' - - def __init__(self, *args, **kwargs): - self.db = kwargs.get('db') - self.target_driver = \ - self.target_mapping[self.configuration.safe_get('iscsi_helper')] - super(SRBISCSIDriver, self).__init__(*args, **kwargs) - self.backend_name =\ - self.configuration.safe_get('volume_backend_name') or 'SRB_iSCSI' - self.protocol = 'iSCSI' - - def ensure_export(self, context, volume): - device_path = self._mapper_path(volume) - - model_update = self.target_driver.ensure_export(context, - volume, - device_path) - if model_update: - self.db.volume_update(context, volume['id'], model_update) - - def create_export(self, context, volume, connector): - """Creates an export for a logical volume.""" - self._attach_file(volume) - vg = self._get_lvm_vg(volume) - vg.activate_vg() - - # SRB uses the same name as the volume for the VG - volume_path = self._mapper_path(volume) - - data = self.target_driver.create_export(context, - volume, - volume_path) - return { - 'provider_location': data['location'], - 'provider_auth': data['auth'], - } - - def remove_export(self, context, volume): - # NOTE(joachim) Taken from iscsi._ExportMixin.remove_export - # This allows us to avoid "detaching" a device not attached by - # an export, and avoid screwing up the device attach refcount. - try: - # Raises exception.NotFound if export not provisioned - iscsi_target = self.target_driver._get_iscsi_target(context, - volume['id']) - # Raises an Exception if currently not exported - location = volume['provider_location'].split(' ') - iqn = location[1] - self.target_driver.show_target(iscsi_target, iqn=iqn) - - self.target_driver.remove_export(context, volume) - self._detach_file(volume) - except exception.NotFound: - LOG.warning(_LW('Volume %r not found while trying to remove.'), - volume['id']) - except Exception as exc: - LOG.warning(_LW('Error while removing export: %r'), exc) diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters index 5ba8e506fbc..2b755d5db61 100644 --- a/etc/cinder/rootwrap.d/volume.filters +++ b/etc/cinder/rootwrap.d/volume.filters @@ -28,20 +28,9 @@ lvdisplay_lvmconf: EnvFilter, env, root, LVM_SYSTEM_DIR=, LC_ALL=C, lvdisplay # this file. scsi_id: CommandFilter, /lib/udev/scsi_id, root -# cinder/volumes/drivers/srb.py: 'pvresize', '--setphysicalvolumesize', sizestr, pvname -pvresize: EnvFilter, env, root, LC_ALL=C, pvresize -pvresize_lvmconf: EnvFilter, env, root, LVM_SYSTEM_DIR=, LC_ALL=C, pvresize - # cinder/brick/local_dev/lvm.py: 'vgcreate', vg_name, pv_list vgcreate: CommandFilter, vgcreate, root -# cinder/volumes/drivers/srb.py: 'vgremove', '-f', vgname -vgremove: CommandFilter, vgremove, root - -# cinder/volumes/drivers/srb.py: 'vgchange', '-an', vgname -# cinder/volumes/drivers/srb.py: 'vgchange', '-ay', vgname -vgchange: CommandFilter, vgchange, root - # cinder/brick/local_dev/lvm.py: 'lvcreate', '-L', sizestr, '-n', volume_name,.. # cinder/brick/local_dev/lvm.py: 'lvcreate', '-L', ... lvcreate: EnvFilter, env, root, LC_ALL=C, lvcreate diff --git a/tests-py3.txt b/tests-py3.txt index d071dda3fc4..aa6008cc9e4 100644 --- a/tests-py3.txt +++ b/tests-py3.txt @@ -107,7 +107,6 @@ cinder.tests.unit.test_scality cinder.tests.unit.test_service cinder.tests.unit.test_sheepdog cinder.tests.unit.test_smbfs -cinder.tests.unit.test_srb cinder.tests.unit.test_solidfire cinder.tests.unit.test_ssh_utils cinder.tests.unit.test_storwize_svc