Unity: add thick volume support
Change-Id: Ic1a77f928ce0b8a51e78b33bc6025b7c9902c8fa
This commit is contained in:
parent
20f00d0d8c
commit
e458bdbf84
@ -84,3 +84,11 @@ class AdapterSetupError(Exception):
|
||||
|
||||
class HostDeleteIsCalled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnityThinCloneNotAllowedError(StoropsException):
|
||||
pass
|
||||
|
||||
|
||||
class SystemAPINotSupported(StoropsException):
|
||||
pass
|
||||
|
@ -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',
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user