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 commita23f17f8ce
. 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:
parent
3754b3b063
commit
49b05a999c
@ -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 sheepdog as cinder_volume_drivers_sheepdog
|
||||||
from cinder.volume.drivers import smbfs as cinder_volume_drivers_smbfs
|
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 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 import tintri as cinder_volume_drivers_tintri
|
||||||
from cinder.volume.drivers.violin import v6000_common as \
|
from cinder.volume.drivers.violin import v6000_common as \
|
||||||
cinder_volume_drivers_violin_v6000common
|
cinder_volume_drivers_violin_v6000common
|
||||||
@ -255,7 +254,6 @@ def list_opts():
|
|||||||
cinder_volume_drivers_remotefs.nas_opts,
|
cinder_volume_drivers_remotefs.nas_opts,
|
||||||
cinder_volume_drivers_remotefs.volume_opts,
|
cinder_volume_drivers_remotefs.volume_opts,
|
||||||
cinder_volume_drivers_violin_v6000common.violin_opts,
|
cinder_volume_drivers_violin_v6000common.violin_opts,
|
||||||
cinder_volume_drivers_srb.srb_opts,
|
|
||||||
cinder_volume_drivers_emc_xtremio.XTREMIO_OPTS,
|
cinder_volume_drivers_emc_xtremio.XTREMIO_OPTS,
|
||||||
[cinder_api_middleware_auth.use_forwarded_for_opt],
|
[cinder_api_middleware_auth.use_forwarded_for_opt],
|
||||||
cinder_volume_drivers_hitachi_hbsdcommon.volume_opts,
|
cinder_volume_drivers_hitachi_hbsdcommon.volume_opts,
|
||||||
|
@ -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'])
|
|
@ -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)
|
|
@ -28,20 +28,9 @@ lvdisplay_lvmconf: EnvFilter, env, root, LVM_SYSTEM_DIR=, LC_ALL=C, lvdisplay
|
|||||||
# this file.
|
# this file.
|
||||||
scsi_id: CommandFilter, /lib/udev/scsi_id, root
|
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
|
# cinder/brick/local_dev/lvm.py: 'vgcreate', vg_name, pv_list
|
||||||
vgcreate: CommandFilter, vgcreate, root
|
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', sizestr, '-n', volume_name,..
|
||||||
# cinder/brick/local_dev/lvm.py: 'lvcreate', '-L', ...
|
# cinder/brick/local_dev/lvm.py: 'lvcreate', '-L', ...
|
||||||
lvcreate: EnvFilter, env, root, LC_ALL=C, lvcreate
|
lvcreate: EnvFilter, env, root, LC_ALL=C, lvcreate
|
||||||
|
@ -107,7 +107,6 @@ cinder.tests.unit.test_scality
|
|||||||
cinder.tests.unit.test_service
|
cinder.tests.unit.test_service
|
||||||
cinder.tests.unit.test_sheepdog
|
cinder.tests.unit.test_sheepdog
|
||||||
cinder.tests.unit.test_smbfs
|
cinder.tests.unit.test_smbfs
|
||||||
cinder.tests.unit.test_srb
|
|
||||||
cinder.tests.unit.test_solidfire
|
cinder.tests.unit.test_solidfire
|
||||||
cinder.tests.unit.test_ssh_utils
|
cinder.tests.unit.test_ssh_utils
|
||||||
cinder.tests.unit.test_storwize_svc
|
cinder.tests.unit.test_storwize_svc
|
||||||
|
Loading…
Reference in New Issue
Block a user