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