Unity: add thick volume support

Change-Id: Ic1a77f928ce0b8a51e78b33bc6025b7c9902c8fa
This commit is contained in:
Ryan Liang 2018-07-04 13:56:46 +08:00
parent 20f00d0d8c
commit e458bdbf84
8 changed files with 129 additions and 30 deletions

View File

@ -84,3 +84,11 @@ class AdapterSetupError(Exception):
class HostDeleteIsCalled(Exception):
pass
class UnityThinCloneNotAllowedError(StoropsException):
pass
class SystemAPINotSupported(StoropsException):
pass

View File

@ -78,8 +78,13 @@ class MockClient(object):
return test_client.MockResourceList(['pool0', 'pool1'])
@staticmethod
def create_lun(name, size, pool, description=None, io_limit_policy=None):
return test_client.MockResource(_id=name, name=name)
def create_lun(name, size, pool, description=None, io_limit_policy=None,
is_thin=None):
lun_id = name
if is_thin is not None and not is_thin:
lun_id += '_thick'
return test_client.MockResource(_id=lun_id, name=name)
@staticmethod
def get_lun(name=None, lun_id=None):
@ -198,6 +203,8 @@ class MockClient(object):
if (obj.name, name) in (
('snap_61', 'lun_60'), ('lun_63', 'lun_60')):
return test_client.MockResource(_id=name)
elif (obj.name, name) in (('snap_71', 'lun_70'), ('lun_72', 'lun_70')):
raise ex.UnityThinCloneNotAllowedError()
else:
raise ex.UnityThinCloneLimitExceededError
@ -237,8 +244,7 @@ def mock_adapter(driver_clz):
ret = driver_clz()
ret._client = MockClient()
with mock.patch('cinder.volume.drivers.dell_emc.unity.adapter.'
'CommonAdapter.validate_ports'), \
patch_storops():
'CommonAdapter.validate_ports'), patch_storops():
ret.do_setup(MockDriver(), MockConfig())
ret.lookup_service = MockLookupService()
return ret
@ -272,8 +278,17 @@ def get_connection_info(adapter, hlu, host, connector):
return {}
def get_volume_type_extra_specs(type_id):
if type_id == 'thick':
return {'provisioning:type': 'thick',
'thick_provisioning_support': '<is> True'}
return {}
def patch_for_unity_adapter(func):
@functools.wraps(func)
@mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs',
new=get_volume_type_extra_specs)
@mock.patch('cinder.volume.drivers.dell_emc.unity.utils.'
'get_backend_qos_specs',
new=get_backend_qos_specs)
@ -294,6 +309,7 @@ def patch_for_concrete_adapter(clz_str):
new=get_connection_info)
def func_wrapper(*args, **kwargs):
return func(*args, **kwargs)
return func_wrapper
return inner_decorator
@ -302,7 +318,6 @@ def patch_for_concrete_adapter(clz_str):
patch_for_iscsi_adapter = patch_for_concrete_adapter(
'cinder.volume.drivers.dell_emc.unity.adapter.ISCSIAdapter')
patch_for_fc_adapter = patch_for_concrete_adapter(
'cinder.volume.drivers.dell_emc.unity.adapter.FCAdapter')
@ -367,6 +382,15 @@ class CommonAdapterTest(test.TestCase):
expected = get_lun_pl('lun_3')
self.assertEqual(expected, ret['provider_location'])
@patch_for_unity_adapter
def test_create_volume_thick(self):
volume = MockOSResource(name='lun_3', size=5, host='unity#pool1',
volume_type_id='thick')
ret = self.adapter.create_volume(volume)
expected = get_lun_pl('lun_3_thick')
self.assertEqual(expected, ret['provider_location'])
def test_create_snapshot(self):
volume = MockOSResource(provider_location='id^lun_43')
snap = MockOSResource(volume=volume, name='abc-def_snap')
@ -405,7 +429,7 @@ class CommonAdapterTest(test.TestCase):
self.assertEqual(2, stats['free_capacity_gb'])
self.assertEqual(300, stats['max_over_subscription_ratio'])
self.assertEqual(5, stats['reserved_percentage'])
self.assertFalse(stats['thick_provisioning_support'])
self.assertTrue(stats['thick_provisioning_support'])
self.assertTrue(stats['thin_provisioning_support'])
def test_update_volume_stats(self):
@ -413,7 +437,7 @@ class CommonAdapterTest(test.TestCase):
self.assertEqual('backend', stats['volume_backend_name'])
self.assertEqual('unknown', stats['storage_protocol'])
self.assertTrue(stats['thin_provisioning_support'])
self.assertFalse(stats['thick_provisioning_support'])
self.assertTrue(stats['thick_provisioning_support'])
self.assertEqual(1, len(stats['pools']))
def test_serial_number(self):
@ -432,6 +456,7 @@ class CommonAdapterTest(test.TestCase):
'CommonAdapter.validate_ports'):
self.adapter._client.system.system_version = '4.0.0'
self.adapter.do_setup(self.adapter.driver, MockConfig())
self.assertRaises(exception.VolumeBackendAPIException, f)
def test_verify_cert_false_path_none(self):
@ -688,6 +713,20 @@ class CommonAdapterTest(test.TestCase):
new_dd_lun)
self.assertEqual(IdMatcher(test_client.MockResource(_id=lun_id)), ret)
@patch_for_unity_adapter
def test_thin_clone_thick(self):
lun_id = 'lun_70'
src_snap_id = 'snap_71'
volume = MockOSResource(name=lun_id, id=lun_id, size=1,
provider_location=get_snap_lun_pl(lun_id))
src_snap = test_client.MockResource(name=src_snap_id, _id=src_snap_id)
new_dd_lun = test_client.MockResource(name='lun_73')
with patch_storops(), patch_dd_copy(new_dd_lun) as dd:
vol_params = adapter.VolumeParams(self.adapter, volume)
ret = self.adapter._thin_clone(vol_params, src_snap)
dd.assert_called_with(vol_params, src_snap, src_lun=None)
self.assertEqual(ret, new_dd_lun)
def test_extend_volume_error(self):
def f():
volume = MockOSResource(id='l56',

View File

@ -48,6 +48,7 @@ class MockResource(object):
self.pool_name = 'Pool0'
self._storage_resource = None
self.host_cache = []
self.is_thin = None
@property
def id(self):
@ -111,13 +112,16 @@ class MockResource(object):
return self.alu_hlu_map.get(lun.get_id(), None)
@staticmethod
def create_lun(lun_name, size_gb, description=None, io_limit_policy=None):
def create_lun(lun_name, size_gb, description=None, io_limit_policy=None,
is_thin=None):
if lun_name == 'in_use':
raise ex.UnityLunNameInUseError()
ret = MockResource(lun_name, 'lun_2')
if io_limit_policy is not None:
ret.max_iops = io_limit_policy.max_iops
ret.max_kbps = io_limit_policy.max_kbps
if is_thin is not None:
ret.is_thin = is_thin
return ret
@staticmethod
@ -340,6 +344,13 @@ class ClientTest(unittest.TestCase):
lun = self.client.create_lun('LUN 4', 6, pool, io_limit_policy=limit)
self.assertEqual(100, lun.max_kbps)
def test_create_lun_thick(self):
name = 'thick_lun'
pool = MockResource('Pool 0')
lun = self.client.create_lun(name, 6, pool, is_thin=False)
self.assertIsNotNone(lun.is_thin)
self.assertFalse(lun.is_thin)
def test_thin_clone_success(self):
name = 'tc_77'
src_lun = MockResource(_id='id_77')

View File

@ -38,7 +38,6 @@ else:
# Set storops_ex to be None for unit test
storops_ex = None
LOG = logging.getLogger(__name__)
PROTOCOL_FC = 'FC'
@ -58,6 +57,7 @@ class VolumeParams(object):
else volume.display_name)
self._pool = None
self._io_limit_policy = None
self._is_thick = None
@property
def volume_id(self):
@ -109,11 +109,21 @@ class VolumeParams(object):
def io_limit_policy(self, value):
self._io_limit_policy = value
@property
def is_thick(self):
if self._is_thick is None:
provision = utils.get_extra_spec(self._volume, 'provisioning:type')
support = utils.get_extra_spec(self._volume,
'thick_provisioning_support')
self._is_thick = (provision == 'thick' and support == '<is> True')
return self._is_thick
def __eq__(self, other):
return (self.volume_id == other.volume_id and
self.name == other.name and
self.size == other.size and
self.io_limit_policy == other.io_limit_policy)
self.io_limit_policy == other.io_limit_policy and
self.is_thick == other.is_thick)
class CommonAdapter(object):
@ -149,8 +159,8 @@ class CommonAdapter(object):
self.reserved_percentage = self.config.reserved_percentage
self.max_over_subscription_ratio = (
self.config.max_over_subscription_ratio)
self.volume_backend_name = (
self.config.safe_get('volume_backend_name') or self.driver_name)
self.volume_backend_name = (self.config.safe_get('volume_backend_name')
or self.driver_name)
self.ip = self.config.san_ip
self.username = self.config.san_login
self.password = self.config.san_password
@ -274,18 +284,21 @@ class CommonAdapter(object):
'size': params.size,
'description': params.description,
'pool': params.pool,
'io_limit_policy': params.io_limit_policy}
'io_limit_policy': params.io_limit_policy,
'is_thick': params.is_thick
}
LOG.info('Create Volume: %(name)s, size: %(size)s, description: '
'%(description)s, pool: %(pool)s, io limit policy: '
'%(io_limit_policy)s.', log_params)
'%(io_limit_policy)s, thick: %(is_thick)s.', log_params)
return self.makeup_model(
self.client.create_lun(name=params.name,
size=params.size,
pool=params.pool,
description=params.description,
io_limit_policy=params.io_limit_policy))
io_limit_policy=params.io_limit_policy,
is_thin=False if params.is_thick else None))
def delete_volume(self, volume):
lun_id = self.get_lun_id(volume)
@ -404,7 +417,7 @@ class CommonAdapter(object):
'volume_backend_name': self.volume_backend_name,
'storage_protocol': self.protocol,
'thin_provisioning_support': True,
'thick_provisioning_support': False,
'thick_provisioning_support': True,
'pools': self.get_pools_stats(),
}
@ -428,7 +441,7 @@ class CommonAdapter(object):
{'pool_name': pool.name,
'array_serial': self.serial_number}),
'thin_provisioning_support': True,
'thick_provisioning_support': False,
'thick_provisioning_support': True,
'max_over_subscription_ratio': (
self.max_over_subscription_ratio)}
@ -583,7 +596,8 @@ class CommonAdapter(object):
dest_lun = self.client.create_lun(
name=vol_params.name, size=vol_params.size, pool=vol_params.pool,
description=vol_params.description,
io_limit_policy=vol_params.io_limit_policy)
io_limit_policy=vol_params.io_limit_policy,
is_thin=False if vol_params.is_thick else None)
src_id = src_snap.get_id()
try:
conn_props = cinder_utils.brick_get_connector_properties()
@ -653,6 +667,16 @@ class CommonAdapter(object):
'thin clone api. source snap: %(src_snap)s, lun: %(src_lun)s.',
{'src_snap': src_snap.name,
'src_lun': 'Unknown' if src_lun is None else src_lun.name})
except storops_ex.UnityThinCloneNotAllowedError:
# Thin clone not allowed on some resources,
# like thick luns and their snaps
lun = self._dd_copy(vol_params, src_snap, src_lun=src_lun)
LOG.debug(
'Volume copied via dd because source snap/lun is not allowed '
'to thin clone, i.e. it is thick. source snap: %(src_snap)s, '
'lun: %(src_lun)s.',
{'src_snap': src_snap.name,
'src_lun': 'Unknown' if src_lun is None else src_lun.name})
return lun
def create_volume_from_snapshot(self, volume, snapshot):

View File

@ -57,7 +57,7 @@ class UnityClient(object):
return self.system.serial_number
def create_lun(self, name, size, pool, description=None,
io_limit_policy=None):
io_limit_policy=None, is_thin=None):
"""Creates LUN on the Unity system.
:param name: lun name
@ -65,12 +65,14 @@ class UnityClient(object):
:param pool: UnityPool object represent to pool to place the lun
:param description: lun description
:param io_limit_policy: io limit on the LUN
:param is_thin: if False, a thick LUN will be created
:return: UnityLun object
"""
try:
lun = pool.create_lun(lun_name=name, size_gb=size,
description=description,
io_limit_policy=io_limit_policy)
io_limit_policy=io_limit_policy,
is_thin=is_thin)
except storops_ex.UnityLunNameInUseError:
LOG.debug("LUN %s already exists. Return the existing one.",
name)

View File

@ -10,13 +10,13 @@ and a Dell EMC distributed Python package
Prerequisites
~~~~~~~~~~~~~
+-------------------+----------------+
| Software | Version |
+===================+================+
| Unity OE | 4.1.X or newer |
+-------------------+----------------+
| storops | 0.5.7 or newer |
+-------------------+----------------+
+-------------------+-----------------+
| Software | Version |
+===================+=================+
| Unity OE | 4.1.X or newer |
+-------------------+-----------------+
| storops | 0.5.10 or newer |
+-------------------+-----------------+
Supported operations
@ -33,6 +33,7 @@ Supported operations
- Get volume statistics.
- Efficient non-disruptive volume backup.
- Revert a volume to a snapshot.
- Create thick volumes.
Driver configuration
~~~~~~~~~~~~~~~~~~~~
@ -237,7 +238,14 @@ To enable multipath in live migration:
Thin and thick provisioning
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Only thin volume provisioning is supported in Unity volume driver.
By default, the volume created by Unity driver is thin provisioned. Run the
following commands to create a thick volume.
.. code-block:: console
# openstack volume type create --property provisioning:type=thick \
--property thick_provisioning_support='<is> True' thick_volume_type
# openstack volume create --type thick_volume_type thick_volume
QoS support

View File

@ -31,8 +31,8 @@ pyxcli>=1.1.5 # Apache-2.0
rados # LGPLv2.1
rbd # LGPLv2.1
# Dell EMC VNX
storops>=0.5.7 # Apache-2.0
# Dell EMC VNX and Unity
storops>=0.5.10 # Apache-2.0
# Violin
vmemclient>=1.1.8 # Apache-2.0

View File

@ -0,0 +1,7 @@
---
features:
- |
Dell EMC Unity Driver: Add thick volume support. Refer to `Unity Cinder
Configuration document
<https://docs.openstack.org/cinder/latest/configuration/block-storage/drivers/dell-emc-unity-driver.html>`__
to create a thick volume.