Revert "Add Scality SRB driver"

The Scality SRB kernel-driver is being re-designed to optimize for
specific workloads, which are (initially) not compatible with VM-style
block device access. Not to confuse users, we believe it's advisable to
remove the Cinder SRB driver in the meantime.

This reverts commit a23f17f8ce.

Some references to the SRB driver and test modules added in later
commits are removed as well.

Conflicts:
        cinder/tests/unit/test_srb.py
        cinder/volume/drivers/srb.py
        etc/cinder/rootwrap.d/volume.filters

See: 2f9e4163f4
See: 3fda737f53

Change-Id: Ic9d79b09363ef904932128f63371ba01a15c5d31
This commit is contained in:
Nicolas Trangez 2015-11-25 14:45:09 +01:00
parent 3754b3b063
commit 49b05a999c
5 changed files with 0 additions and 1861 deletions

View File

@ -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,

View File

@ -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'])

View File

@ -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)

View File

@ -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

View File

@ -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