+++ /dev/null
-# 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'])
+++ /dev/null
-# 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)