VMAX driver - support for manage/unmanage snapshot
VMAX support for managing and unmanaging snapshots. Change-Id: I0717825dd47fb47c0a58040a13a9da09f6779955 Implements: blueprint vmax-manage-unmanage-snapshot
This commit is contained in:
parent
10e533005a
commit
7dda6ef758
@ -52,6 +52,7 @@ CINDER_EMC_CONFIG_DIR = '/etc/cinder/'
|
|||||||
class VMAXCommonData(object):
|
class VMAXCommonData(object):
|
||||||
# array info
|
# array info
|
||||||
array = '000197800123'
|
array = '000197800123'
|
||||||
|
uni_array = u'000197800123'
|
||||||
array_herc = '000197900123'
|
array_herc = '000197900123'
|
||||||
srp = 'SRP_1'
|
srp = 'SRP_1'
|
||||||
srp2 = 'SRP_2'
|
srp2 = 'SRP_2'
|
||||||
@ -90,6 +91,7 @@ class VMAXCommonData(object):
|
|||||||
target_group_name = 'Grp_target'
|
target_group_name = 'Grp_target'
|
||||||
storagegroup_name_with_id = 'GrpId_group_name'
|
storagegroup_name_with_id = 'GrpId_group_name'
|
||||||
rdf_managed_async_grp = "OS-%s-Asynchronous-rdf-sg" % rdf_group_name
|
rdf_managed_async_grp = "OS-%s-Asynchronous-rdf-sg" % rdf_group_name
|
||||||
|
volume_id = '2b06255d-f5f0-4520-a953-b029196add6a'
|
||||||
|
|
||||||
# connector info
|
# connector info
|
||||||
wwpn1 = "123456789012345"
|
wwpn1 = "123456789012345"
|
||||||
@ -157,6 +159,9 @@ class VMAXCommonData(object):
|
|||||||
provider_location3 = {'array': six.text_type(remote_array),
|
provider_location3 = {'array': six.text_type(remote_array),
|
||||||
'device_id': device_id2}
|
'device_id': device_id2}
|
||||||
|
|
||||||
|
provider_location4 = {'array': six.text_type(uni_array),
|
||||||
|
'device_id': device_id}
|
||||||
|
|
||||||
legacy_provider_location = {
|
legacy_provider_location = {
|
||||||
'classname': 'Symm_StorageVolume',
|
'classname': 'Symm_StorageVolume',
|
||||||
'keybindings': {'CreationClassName': u'Symm_StorageVolume',
|
'keybindings': {'CreationClassName': u'Symm_StorageVolume',
|
||||||
@ -198,7 +203,16 @@ class VMAXCommonData(object):
|
|||||||
provider_location=six.text_type(provider_location2),
|
provider_location=six.text_type(provider_location2),
|
||||||
host=fake_host)
|
host=fake_host)
|
||||||
|
|
||||||
|
test_volume_snap_manage = fake_volume.fake_volume_obj(
|
||||||
|
context=ctx, name='vol1', size=2, provider_auth=None,
|
||||||
|
display_name='vol1',
|
||||||
|
provider_location=six.text_type(provider_location),
|
||||||
|
volume_type=test_volume_type, host=fake_host,
|
||||||
|
replication_driver_data=six.text_type(provider_location4))
|
||||||
|
|
||||||
|
snapshot_display_id = 'my_snap'
|
||||||
snapshot_id = '390eeb4d-0f56-4a02-ba14-167167967014'
|
snapshot_id = '390eeb4d-0f56-4a02-ba14-167167967014'
|
||||||
|
managed_snap_id = 'OS-390eeb4d-0f56-4a02-ba14-167167967014'
|
||||||
test_snapshot_snap_name = 'OS-' + snapshot_id[:6] + snapshot_id[-9:]
|
test_snapshot_snap_name = 'OS-' + snapshot_id[:6] + snapshot_id[-9:]
|
||||||
|
|
||||||
snap_location = {'snap_name': test_snapshot_snap_name,
|
snap_location = {'snap_name': test_snapshot_snap_name,
|
||||||
@ -223,6 +237,13 @@ class VMAXCommonData(object):
|
|||||||
provider_location=six.text_type(snap_location),
|
provider_location=six.text_type(snap_location),
|
||||||
host=fake_host, volume=test_volume)
|
host=fake_host, volume=test_volume)
|
||||||
|
|
||||||
|
test_snapshot_manage = fake_snapshot.fake_snapshot_obj(
|
||||||
|
context=ctx, id=snapshot_id,
|
||||||
|
name='my_snap', size=2,
|
||||||
|
provider_location=six.text_type(snap_location),
|
||||||
|
host=fake_host, volume=test_volume_snap_manage,
|
||||||
|
display_name='my_snap')
|
||||||
|
|
||||||
location_info = {'location_info': '000197800123#SRP_1#Diamond#DSS',
|
location_info = {'location_info': '000197800123#SRP_1#Diamond#DSS',
|
||||||
'storage_protocol': 'FC'}
|
'storage_protocol': 'FC'}
|
||||||
test_host = {'capabilities': location_info,
|
test_host = {'capabilities': location_info,
|
||||||
@ -1458,6 +1479,24 @@ class VMAXUtilsTest(test.TestCase):
|
|||||||
self.assertTrue(self.utils.does_vol_need_rdf_management_group(
|
self.assertTrue(self.utils.does_vol_need_rdf_management_group(
|
||||||
extra_specs))
|
extra_specs))
|
||||||
|
|
||||||
|
def test_modify_snapshot_prefix_manage(self):
|
||||||
|
snap_name = self.data.snapshot_id
|
||||||
|
expected_snap_name = self.data.managed_snap_id
|
||||||
|
|
||||||
|
updated_name = self.utils.modify_snapshot_prefix(
|
||||||
|
snap_name, manage=True)
|
||||||
|
|
||||||
|
self.assertEqual(expected_snap_name, updated_name)
|
||||||
|
|
||||||
|
def test_modify_snapshot_prefix_unmanage(self):
|
||||||
|
snap_name = self.data.managed_snap_id
|
||||||
|
expected_snap_name = self.data.snapshot_id
|
||||||
|
|
||||||
|
updated_name = self.utils.modify_snapshot_prefix(
|
||||||
|
snap_name, unmanage=True)
|
||||||
|
|
||||||
|
self.assertEqual(expected_snap_name, updated_name)
|
||||||
|
|
||||||
|
|
||||||
class VMAXRestTest(test.TestCase):
|
class VMAXRestTest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -2784,6 +2823,19 @@ class VMAXRestTest(test.TestCase):
|
|||||||
self.data.device_id, self.data.device_id2,
|
self.data.device_id, self.data.device_id2,
|
||||||
self.data.extra_specs)
|
self.data.extra_specs)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.VMAXRest, 'modify_resource',
|
||||||
|
return_value=('200', 'JobComplete'))
|
||||||
|
def test_modify_volume_snap_rename(self, mock_modify):
|
||||||
|
array = self.data.array
|
||||||
|
source_id = self.data.device_id
|
||||||
|
old_snap_backend_name = self.data.snapshot_id
|
||||||
|
new_snap_backend_name = self.data.managed_snap_id
|
||||||
|
self.rest.modify_volume_snap(
|
||||||
|
array, source_id, source_id, old_snap_backend_name,
|
||||||
|
self.data.extra_specs, link=False, unlink=False,
|
||||||
|
rename=True, new_snap_name=new_snap_backend_name)
|
||||||
|
mock_modify.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
class VMAXProvisionTest(test.TestCase):
|
class VMAXProvisionTest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -4732,6 +4784,87 @@ class VMAXCommonTest(test.TestCase):
|
|||||||
kwargs = self.common.get_attributes_from_cinder_config()
|
kwargs = self.common.get_attributes_from_cinder_config()
|
||||||
self.assertIsNone(kwargs)
|
self.assertIsNone(kwargs)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.VMAXRest,
|
||||||
|
'get_size_of_device_on_array',
|
||||||
|
return_value=2.0)
|
||||||
|
def test_manage_snapshot_get_size_success(self, mock_get_size):
|
||||||
|
size = self.common.manage_existing_snapshot_get_size(
|
||||||
|
self.data.test_snapshot)
|
||||||
|
self.assertEqual(2, size)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.VMAXRest, 'get_volume_snap',
|
||||||
|
return_value={'snap_name': 'snap_name'})
|
||||||
|
def test_manage_snapshot_success(self, mock_snap):
|
||||||
|
snapshot = self.data.test_snapshot_manage
|
||||||
|
existing_ref = {u'source-name': u'test_snap'}
|
||||||
|
updates_response = self.common.manage_existing_snapshot(
|
||||||
|
snapshot, existing_ref)
|
||||||
|
|
||||||
|
prov_loc = {'source_id': self.data.device_id,
|
||||||
|
'snap_name': 'OS-%s' % existing_ref['source-name']}
|
||||||
|
|
||||||
|
updates = {
|
||||||
|
'display_name': self.data.test_snapshot_manage.display_name,
|
||||||
|
'provider_location': six.text_type(prov_loc)}
|
||||||
|
|
||||||
|
self.assertEqual(updates_response, updates)
|
||||||
|
|
||||||
|
def test_manage_snapshot_fail_already_managed(self):
|
||||||
|
snapshot = self.data.test_snapshot_manage
|
||||||
|
existing_ref = {u'source-name': u'OS-test_snap'}
|
||||||
|
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common.manage_existing_snapshot,
|
||||||
|
snapshot, existing_ref)
|
||||||
|
|
||||||
|
@mock.patch.object(utils.VMAXUtils,
|
||||||
|
'is_volume_failed_over',
|
||||||
|
return_value=True)
|
||||||
|
def test_manage_snapshot_fail_vol_failed_over(self, mock_failed):
|
||||||
|
snapshot = self.data.test_snapshot_manage
|
||||||
|
existing_ref = {u'source-name': u'test_snap'}
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common.manage_existing_snapshot,
|
||||||
|
snapshot, existing_ref)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.VMAXRest, 'get_volume_snap', return_value=False)
|
||||||
|
def test_manage_snapshot_fail_vol_not_snap_src(self, mock_snap):
|
||||||
|
snapshot = self.data.test_snapshot_manage
|
||||||
|
existing_ref = {u'source-name': u'test_snap'}
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common.manage_existing_snapshot,
|
||||||
|
snapshot, existing_ref)
|
||||||
|
|
||||||
|
@mock.patch.object(utils.VMAXUtils, 'modify_snapshot_prefix',
|
||||||
|
side_effect=exception.VolumeBackendAPIException)
|
||||||
|
def test_manage_snapshot_fail_add_prefix(self, mock_mod):
|
||||||
|
snapshot = self.data.test_snapshot_manage
|
||||||
|
existing_ref = {u'source-name': u'test_snap'}
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common.manage_existing_snapshot,
|
||||||
|
snapshot, existing_ref)
|
||||||
|
|
||||||
|
@mock.patch.object(common.VMAXCommon, '_sync_check')
|
||||||
|
@mock.patch.object(rest.VMAXRest, 'modify_volume_snap')
|
||||||
|
def test_unmanage_snapshot_success(self, mock_mod, mock_sync):
|
||||||
|
self.common.unmanage_snapshot(self.data.test_snapshot_manage)
|
||||||
|
mock_mod.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
utils.VMAXUtils, 'is_volume_failed_over', return_value=True)
|
||||||
|
def test_unmanage_snapshot_fail_failover(self, mock_failed):
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common.unmanage_snapshot,
|
||||||
|
self.data.test_snapshot_manage)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.VMAXRest,
|
||||||
|
'modify_volume_snap',
|
||||||
|
side_effect=exception.VolumeBackendAPIException)
|
||||||
|
def test_unmanage_snapshot_fail_rename(self, mock_snap):
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common.unmanage_snapshot,
|
||||||
|
self.data.test_snapshot_manage)
|
||||||
|
|
||||||
|
|
||||||
class VMAXFCTest(test.TestCase):
|
class VMAXFCTest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -1968,9 +1968,9 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
# Check if there are any replication sessions associated
|
# Check if there are any replication sessions associated
|
||||||
# with the volume.
|
# with the volume.
|
||||||
snapvx_tgt, snapvx_src, rdf = self.rest.is_vol_in_rep_session(
|
snapvx_tgt, __, rdf = self.rest.is_vol_in_rep_session(
|
||||||
array, device_id)
|
array, device_id)
|
||||||
if snapvx_tgt or snapvx_src or rdf:
|
if snapvx_tgt or rdf:
|
||||||
msg = (_("Unable to import volume %(device_id)s to cinder. "
|
msg = (_("Unable to import volume %(device_id)s to cinder. "
|
||||||
"It is part of a replication session.")
|
"It is part of a replication session.")
|
||||||
% {'device_id': device_id})
|
% {'device_id': device_id})
|
||||||
@ -2017,6 +2017,7 @@ class VMAXCommon(object):
|
|||||||
"""Export VMAX volume from Cinder.
|
"""Export VMAX volume from Cinder.
|
||||||
|
|
||||||
Leave the volume intact on the backend array.
|
Leave the volume intact on the backend array.
|
||||||
|
|
||||||
:param volume: the volume object
|
:param volume: the volume object
|
||||||
"""
|
"""
|
||||||
volume_name = volume.name
|
volume_name = volume.name
|
||||||
@ -2042,6 +2043,147 @@ class VMAXCommon(object):
|
|||||||
self.rest.rename_volume(
|
self.rest.rename_volume(
|
||||||
extra_specs[utils.ARRAY], device_id, volume_id)
|
extra_specs[utils.ARRAY], device_id, volume_id)
|
||||||
|
|
||||||
|
def manage_existing_snapshot(self, snapshot, existing_ref):
|
||||||
|
"""Manage an existing VMAX Snapshot (import to Cinder).
|
||||||
|
|
||||||
|
Renames the Snapshot to prefix it with OS- to indicate
|
||||||
|
it is managed by Cinder
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:param existing_ref: the snapshot name on the backend VMAX
|
||||||
|
:raises: VolumeBackendAPIException
|
||||||
|
:returns: model update
|
||||||
|
"""
|
||||||
|
volume = snapshot.volume
|
||||||
|
extra_specs = self._initial_setup(volume)
|
||||||
|
array = extra_specs[utils.ARRAY]
|
||||||
|
device_id = self._find_device_on_array(volume, extra_specs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
snap_name = existing_ref['source-name']
|
||||||
|
except KeyError:
|
||||||
|
snap_name = existing_ref['source-id']
|
||||||
|
|
||||||
|
if snapshot.display_name:
|
||||||
|
snap_display_name = snapshot.display_name
|
||||||
|
else:
|
||||||
|
snap_display_name = snapshot.id
|
||||||
|
|
||||||
|
if snap_name.startswith(utils.VOLUME_ELEMENT_NAME_PREFIX):
|
||||||
|
exception_message = (
|
||||||
|
_("Unable to manage existing Snapshot. Snapshot "
|
||||||
|
"%(snapshot)s is already managed by Cinder.") %
|
||||||
|
{'snapshot': snap_name})
|
||||||
|
raise exception.VolumeBackendAPIException(
|
||||||
|
data=exception_message)
|
||||||
|
|
||||||
|
if self.utils.is_volume_failed_over(volume):
|
||||||
|
exception_message = (
|
||||||
|
(_("Volume %(name)s is failed over from the source volume, "
|
||||||
|
"it is not possible to manage a snapshot of a failed over "
|
||||||
|
"volume.") % {'name': volume.id}))
|
||||||
|
LOG.exception(exception_message)
|
||||||
|
raise exception.VolumeBackendAPIException(
|
||||||
|
data=exception_message)
|
||||||
|
|
||||||
|
if not self.rest.get_volume_snap(array, device_id, snap_name):
|
||||||
|
exception_message = (
|
||||||
|
_("Snapshot %(snap_name)s is not associated with specified "
|
||||||
|
"volume %(device_id)s, it is not possible to manage a "
|
||||||
|
"snapshot that is not associated with the specified "
|
||||||
|
"volume.")
|
||||||
|
% {'device_id': device_id, 'snap_name': snap_name})
|
||||||
|
LOG.exception(exception_message)
|
||||||
|
raise exception.VolumeBackendAPIException(
|
||||||
|
data=exception_message)
|
||||||
|
|
||||||
|
snap_backend_name = self.utils.modify_snapshot_prefix(
|
||||||
|
snap_name, manage=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.rest.modify_volume_snap(
|
||||||
|
array, device_id, device_id, snap_name,
|
||||||
|
extra_specs, rename=True, new_snap_name=snap_backend_name)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
exception_message = (
|
||||||
|
_("There was an issue managing %(snap_name)s, it was not "
|
||||||
|
"possible to add the OS- prefix. Error Message: %(e)s.")
|
||||||
|
% {'snap_name': snap_name, 'e': six.text_type(e)})
|
||||||
|
LOG.exception(exception_message)
|
||||||
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||||
|
|
||||||
|
prov_loc = {'source_id': device_id, 'snap_name': snap_backend_name}
|
||||||
|
|
||||||
|
updates = {'display_name': snap_display_name,
|
||||||
|
'provider_location': six.text_type(prov_loc)}
|
||||||
|
|
||||||
|
LOG.info("Managing SnapVX Snapshot %(snap_name)s of source "
|
||||||
|
"volume %(device_id)s, OpenStack Snapshot display name: "
|
||||||
|
"%(snap_display_name)s", {
|
||||||
|
'snap_name': snap_name, 'device_id': device_id,
|
||||||
|
'snap_display_name': snap_display_name})
|
||||||
|
|
||||||
|
return updates
|
||||||
|
|
||||||
|
def manage_existing_snapshot_get_size(self, snapshot):
|
||||||
|
"""Return the size of the source volume for manage-existing-snapshot.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:returns: size of the source volume in GB
|
||||||
|
"""
|
||||||
|
volume = snapshot.volume
|
||||||
|
extra_specs = self._initial_setup(volume)
|
||||||
|
device_id = self._find_device_on_array(volume, extra_specs)
|
||||||
|
return self.rest.get_size_of_device_on_array(
|
||||||
|
extra_specs[utils.ARRAY], device_id)
|
||||||
|
|
||||||
|
def unmanage_snapshot(self, snapshot):
|
||||||
|
"""Export VMAX Snapshot from Cinder.
|
||||||
|
|
||||||
|
Leaves the snapshot intact on the backend VMAX
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:raises: VolumeBackendAPIException
|
||||||
|
"""
|
||||||
|
volume = snapshot.volume
|
||||||
|
extra_specs = self._initial_setup(volume)
|
||||||
|
array = extra_specs[utils.ARRAY]
|
||||||
|
device_id, snap_name = self._parse_snap_info(array, snapshot)
|
||||||
|
|
||||||
|
if self.utils.is_volume_failed_over(volume):
|
||||||
|
exception_message = (
|
||||||
|
_("It is not possible to unmanage a snapshot where the "
|
||||||
|
"source volume is failed-over, revert back to source "
|
||||||
|
"VMAX to unmanage snapshot %(snap_name)s")
|
||||||
|
% {'snap_name': snap_name})
|
||||||
|
|
||||||
|
LOG.exception(exception_message)
|
||||||
|
raise exception.VolumeBackendAPIException(
|
||||||
|
data=exception_message)
|
||||||
|
|
||||||
|
new_snap_backend_name = self.utils.modify_snapshot_prefix(
|
||||||
|
snap_name, unmanage=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.rest.modify_volume_snap(
|
||||||
|
array, device_id, device_id, snap_name, extra_specs,
|
||||||
|
rename=True, new_snap_name=new_snap_backend_name)
|
||||||
|
except Exception as e:
|
||||||
|
exception_message = (
|
||||||
|
_("There was an issue unmanaging Snapshot, it "
|
||||||
|
"was not possible to remove the OS- prefix. Error "
|
||||||
|
"message is: %(e)s.")
|
||||||
|
% {'snap_name': snap_name, 'e': six.text_type(e)})
|
||||||
|
LOG.exception(exception_message)
|
||||||
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||||
|
|
||||||
|
self._sync_check(array, device_id, volume.name, extra_specs)
|
||||||
|
|
||||||
|
LOG.info("Snapshot %(snap_name)s is no longer managed in "
|
||||||
|
"OpenStack but still remains on VMAX source "
|
||||||
|
"%(array_id)s", {'snap_name': snap_name, 'array_id': array})
|
||||||
|
|
||||||
def retype(self, volume, new_type, host):
|
def retype(self, volume, new_type, host):
|
||||||
"""Migrate volume to another host using retype.
|
"""Migrate volume to another host using retype.
|
||||||
|
|
||||||
|
@ -87,6 +87,8 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
|
|||||||
- Deprecate backend xml configuration
|
- Deprecate backend xml configuration
|
||||||
- Support for async replication (vmax-replication-enhancements)
|
- Support for async replication (vmax-replication-enhancements)
|
||||||
- Support for SRDF/Metro (vmax-replication-enhancements)
|
- Support for SRDF/Metro (vmax-replication-enhancements)
|
||||||
|
- Support for manage/unmanage snapshots
|
||||||
|
(vmax-manage-unmanage-snapshot)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "3.1.0"
|
VERSION = "3.1.0"
|
||||||
@ -480,6 +482,36 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
|
|||||||
"""
|
"""
|
||||||
return self.common.unmanage(volume)
|
return self.common.unmanage(volume)
|
||||||
|
|
||||||
|
def manage_existing_snapshot(self, snapshot, existing_ref):
|
||||||
|
"""Manage an existing VMAX Snapshot (import to Cinder).
|
||||||
|
|
||||||
|
Renames the Snapshot to prefix it with OS- to indicate
|
||||||
|
it is managed by Cinder.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:param existing_ref: the snapshot name on the backend VMAX
|
||||||
|
:returns: model_update
|
||||||
|
"""
|
||||||
|
return self.common.manage_existing_snapshot(snapshot, existing_ref)
|
||||||
|
|
||||||
|
def manage_existing_snapshot_get_size(self, snapshot, existing_ref):
|
||||||
|
"""Return the size of the source volume for manage-existing-snapshot.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:param existing_ref: the snapshot name on the backend VMAX
|
||||||
|
:returns: size of the source volume in GB
|
||||||
|
"""
|
||||||
|
return self.common.manage_existing_snapshot_get_size(snapshot)
|
||||||
|
|
||||||
|
def unmanage_snapshot(self, snapshot):
|
||||||
|
"""Export VMAX Snapshot from Cinder.
|
||||||
|
|
||||||
|
Leaves the snapshot intact on the backend VMAX.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
"""
|
||||||
|
self.common.unmanage_snapshot(snapshot)
|
||||||
|
|
||||||
def retype(self, ctxt, volume, new_type, diff, host):
|
def retype(self, ctxt, volume, new_type, diff, host):
|
||||||
"""Migrate volume to another host using retype.
|
"""Migrate volume to another host using retype.
|
||||||
|
|
||||||
|
@ -92,6 +92,8 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
|
|||||||
- Deprecate backend xml configuration
|
- Deprecate backend xml configuration
|
||||||
- Support for async replication (vmax-replication-enhancements)
|
- Support for async replication (vmax-replication-enhancements)
|
||||||
- Support for SRDF/Metro (vmax-replication-enhancements)
|
- Support for SRDF/Metro (vmax-replication-enhancements)
|
||||||
|
- Support for manage/unmanage snapshots
|
||||||
|
(vmax-manage-unmanage-snapshot)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "3.1.0"
|
VERSION = "3.1.0"
|
||||||
@ -404,6 +406,36 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
|
|||||||
"""
|
"""
|
||||||
return self.common.unmanage(volume)
|
return self.common.unmanage(volume)
|
||||||
|
|
||||||
|
def manage_existing_snapshot(self, snapshot, existing_ref):
|
||||||
|
"""Manage an existing VMAX Snapshot (import to Cinder).
|
||||||
|
|
||||||
|
Renames the Snapshot to prefix it with OS- to indicate
|
||||||
|
it is managed by Cinder.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:param existing_ref: the snapshot name on the backend VMAX
|
||||||
|
:returns: model_update
|
||||||
|
"""
|
||||||
|
return self.common.manage_existing_snapshot(snapshot, existing_ref)
|
||||||
|
|
||||||
|
def manage_existing_snapshot_get_size(self, snapshot, existing_ref):
|
||||||
|
"""Return the size of the source volume for manage-existing-snapshot.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
:param existing_ref: the snapshot name on the backend VMAX
|
||||||
|
:returns: size of the source volume in GB
|
||||||
|
"""
|
||||||
|
return self.common.manage_existing_snapshot_get_size(snapshot)
|
||||||
|
|
||||||
|
def unmanage_snapshot(self, snapshot):
|
||||||
|
"""Export VMAX Snapshot from Cinder.
|
||||||
|
|
||||||
|
Leaves the snapshot intact on the backend VMAX.
|
||||||
|
|
||||||
|
:param snapshot: the snapshot object
|
||||||
|
"""
|
||||||
|
self.common.unmanage_snapshot(snapshot)
|
||||||
|
|
||||||
def retype(self, ctxt, volume, new_type, diff, host):
|
def retype(self, ctxt, volume, new_type, diff, host):
|
||||||
"""Migrate volume to another host using retype.
|
"""Migrate volume to another host using retype.
|
||||||
|
|
||||||
|
@ -1540,7 +1540,8 @@ class VMAXRest(object):
|
|||||||
job, extra_specs)
|
job, extra_specs)
|
||||||
|
|
||||||
def modify_volume_snap(self, array, source_id, target_id, snap_name,
|
def modify_volume_snap(self, array, source_id, target_id, snap_name,
|
||||||
extra_specs, link=False, unlink=False):
|
extra_specs, link=False, unlink=False,
|
||||||
|
rename=False, new_snap_name=None):
|
||||||
"""Link or unlink a snapVx to or from a target volume.
|
"""Link or unlink a snapVx to or from a target volume.
|
||||||
|
|
||||||
:param array: the array serial number
|
:param array: the array serial number
|
||||||
@ -1550,20 +1551,32 @@ class VMAXRest(object):
|
|||||||
:param extra_specs: extra specifications
|
:param extra_specs: extra specifications
|
||||||
:param link: Flag to indicate action = Link
|
:param link: Flag to indicate action = Link
|
||||||
:param unlink: Flag to indicate action = Unlink
|
:param unlink: Flag to indicate action = Unlink
|
||||||
|
:param rename: Flag to indicate action = Rename
|
||||||
|
:param new_snap_name: Optional new snapshot name
|
||||||
"""
|
"""
|
||||||
action = ''
|
action = None
|
||||||
if link:
|
if link:
|
||||||
action = "Link"
|
action = "Link"
|
||||||
elif unlink:
|
elif unlink:
|
||||||
action = "Unlink"
|
action = "Unlink"
|
||||||
if action:
|
elif rename:
|
||||||
|
action = "Rename"
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
if action and link or unlink:
|
||||||
payload = {"deviceNameListSource": [{"name": source_id}],
|
payload = {"deviceNameListSource": [{"name": source_id}],
|
||||||
"deviceNameListTarget": [
|
"deviceNameListTarget": [{"name": target_id}],
|
||||||
{"name": target_id}],
|
|
||||||
"copy": 'true', "action": action,
|
"copy": 'true', "action": action,
|
||||||
"star": 'false', "force": 'false',
|
"star": 'false', "force": 'false',
|
||||||
"exact": 'false', "remote": 'false',
|
"exact": 'false', "remote": 'false',
|
||||||
"symforce": 'false', "nocopy": 'false'}
|
"symforce": 'false', "nocopy": 'false'}
|
||||||
|
|
||||||
|
elif action and rename:
|
||||||
|
payload = {"deviceNameListSource": [{"name": source_id}],
|
||||||
|
"deviceNameListTarget": [{"name": source_id}],
|
||||||
|
"action": action, "newsnapshotname": new_snap_name}
|
||||||
|
|
||||||
|
if action:
|
||||||
status_code, job = self.modify_resource(
|
status_code, job = self.modify_resource(
|
||||||
array, REPLICATION, 'snapshot', payload,
|
array, REPLICATION, 'snapshot', payload,
|
||||||
resource_name=snap_name, private='/private')
|
resource_name=snap_name, private='/private')
|
||||||
|
@ -224,6 +224,31 @@ class VMAXUtils(object):
|
|||||||
{'elementName': element_name})
|
{'elementName': element_name})
|
||||||
return element_name
|
return element_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def modify_snapshot_prefix(snapshot_name, manage=False, unmanage=False):
|
||||||
|
"""Modify a Snapshot prefix on VMAX backend.
|
||||||
|
|
||||||
|
Prepare a snapshot name for manage/unmanage snapshot process either
|
||||||
|
by adding or removing 'OS-' prefix.
|
||||||
|
|
||||||
|
:param snapshot_name: the old snapshot backend display name
|
||||||
|
:param manage: (bool) if the operation is managing a snapshot
|
||||||
|
:param unmanage: (bool) if the operation is unmanaging a snapshot
|
||||||
|
:return: snapshot name ready for backend VMAX assignment
|
||||||
|
"""
|
||||||
|
new_snap_name = None
|
||||||
|
if manage:
|
||||||
|
new_snap_name = ("%(prefix)s%(snapshot_name)s"
|
||||||
|
% {'prefix': 'OS-',
|
||||||
|
'snapshot_name': snapshot_name})
|
||||||
|
|
||||||
|
if unmanage:
|
||||||
|
snap_split = snapshot_name.split("-", 1)
|
||||||
|
if snap_split[0] == 'OS':
|
||||||
|
new_snap_name = snap_split[1]
|
||||||
|
|
||||||
|
return new_snap_name
|
||||||
|
|
||||||
def generate_unique_trunc_host(self, host_name):
|
def generate_unique_trunc_host(self, host_name):
|
||||||
"""Create a unique short host name under 16 characters.
|
"""Create a unique short host name under 16 characters.
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support for manage/ unmanage snapshots on VMAX cinder driver.
|
Loading…
x
Reference in New Issue
Block a user