Huawei: Add manage/unmanage snapshot support
Add manage/unmanage snapshot support for Huawei drivers. Also implement the required manage_existing_snapshot_get_size function. DocImpact Implements: blueprint huawei-manage-unmanage-snapshot Change-Id: I05f8a750a745498c879d8c734e661d778528258c
This commit is contained in:
parent
41f876f05e
commit
8d9f6be9de
@ -1640,7 +1640,7 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
|
||||
|
||||
def test_get_volume_status(self):
|
||||
data = self.driver.get_volume_stats()
|
||||
self.assertEqual('2.0.2', data['driver_version'])
|
||||
self.assertEqual('2.0.3', data['driver_version'])
|
||||
|
||||
def test_extend_volume(self):
|
||||
lun_info = self.driver.extend_volume(test_volume, 3)
|
||||
@ -2162,6 +2162,159 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
|
||||
self.driver.unmanage(test_volume)
|
||||
self.assertEqual(ddt_data[1], mock_rename.call_count)
|
||||
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_info',
|
||||
return_value={'ID': 'ID1',
|
||||
'NAME': 'test1',
|
||||
'PARENTID': '12',
|
||||
'USERCAPACITY': 2097152,
|
||||
'HEALTHSTATUS': '2'})
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_id_by_name',
|
||||
return_value='ID1')
|
||||
def test_manage_existing_snapshot_abnormal(self, mock_get_by_name,
|
||||
mock_get_info):
|
||||
with mock.patch.object(huawei_driver.HuaweiBaseDriver,
|
||||
'_get_snapshot_info_by_ref',
|
||||
return_value={'HEALTHSTATUS': '2',
|
||||
'PARENTID': '12'}):
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
external_ref = {'source-name': 'test1'}
|
||||
ex = self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing_snapshot,
|
||||
test_snapshot, external_ref)
|
||||
self.assertIsNotNone(re.search('Snapshot status is not normal',
|
||||
ex.msg))
|
||||
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_info',
|
||||
return_value={'ID': 'ID1',
|
||||
'EXPOSEDTOINITIATOR': 'true',
|
||||
'NAME': 'test1',
|
||||
'PARENTID': '12',
|
||||
'USERCAPACITY': 2097152,
|
||||
'HEALTHSTATUS': constants.STATUS_HEALTH})
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_id_by_name',
|
||||
return_value='ID1')
|
||||
def test_manage_existing_snapshot_with_lungroup(self, mock_get_by_name,
|
||||
mock_get_info):
|
||||
# Already in LUN group.
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
external_ref = {'source-name': 'test1'}
|
||||
ex = self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing_snapshot,
|
||||
test_snapshot, external_ref)
|
||||
self.assertIsNotNone(re.search('Snapshot is exposed to initiator',
|
||||
ex.msg))
|
||||
|
||||
@mock.patch.object(rest_client.RestClient, 'rename_snapshot')
|
||||
@mock.patch.object(huawei_driver.HuaweiBaseDriver,
|
||||
'_get_snapshot_info_by_ref',
|
||||
return_value={'ID': 'ID1',
|
||||
'EXPOSEDTOINITIATOR': 'false',
|
||||
'NAME': 'test1',
|
||||
'PARENTID': '12',
|
||||
'USERCAPACITY': 2097152,
|
||||
'HEALTHSTATUS': constants.STATUS_HEALTH})
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_info',
|
||||
return_value={'ID': 'ID1',
|
||||
'EXPOSEDTOINITIATOR': 'false',
|
||||
'NAME': 'test1',
|
||||
'PARENTID': '12',
|
||||
'USERCAPACITY': 2097152,
|
||||
'HEALTHSTATUS': constants.STATUS_HEALTH})
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_id_by_name',
|
||||
return_value='ID1')
|
||||
def test_manage_existing_snapshot_success(self, mock_get_by_name,
|
||||
mock_get_info,
|
||||
mock_check_snapshot,
|
||||
mock_rename):
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
external_ref = {'source-name': 'test1'}
|
||||
model_update = self.driver.manage_existing_snapshot(test_snapshot,
|
||||
external_ref)
|
||||
self.assertEqual({'provider_location': 'ID1'}, model_update)
|
||||
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
external_ref = {'source-id': 'ID1'}
|
||||
model_update = self.driver.manage_existing_snapshot(test_snapshot,
|
||||
external_ref)
|
||||
self.assertEqual({'provider_location': 'ID1'}, model_update)
|
||||
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_info',
|
||||
return_value={'ID': 'ID1',
|
||||
'EXPOSEDTOINITIATOR': 'false',
|
||||
'NAME': 'test1',
|
||||
'USERCAPACITY': 2097152,
|
||||
'PARENTID': '11',
|
||||
'HEALTHSTATUS': constants.STATUS_HEALTH})
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_id_by_name',
|
||||
return_value='ID1')
|
||||
def test_manage_existing_snapshot_mismatch_lun(self, mock_get_by_name,
|
||||
mock_get_info):
|
||||
external_ref = {'source-name': 'test1'}
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
ex = self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing_snapshot,
|
||||
test_snapshot, external_ref)
|
||||
self.assertIsNotNone(re.search("Snapshot doesn't belong to volume",
|
||||
ex.msg))
|
||||
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_info',
|
||||
return_value={'USERCAPACITY': 2097152})
|
||||
@mock.patch.object(rest_client.RestClient, 'get_snapshot_id_by_name',
|
||||
return_value='ID1')
|
||||
def test_manage_existing_snapshot_get_size_success(self,
|
||||
mock_get_id_by_name,
|
||||
mock_get_info):
|
||||
external_ref = {'source-name': 'test1',
|
||||
'source-id': 'ID1'}
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
size = self.driver.manage_existing_snapshot_get_size(test_snapshot,
|
||||
external_ref)
|
||||
self.assertEqual(1, size)
|
||||
|
||||
external_ref = {'source-name': 'test1'}
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
size = self.driver.manage_existing_snapshot_get_size(test_snapshot,
|
||||
external_ref)
|
||||
self.assertEqual(1, size)
|
||||
|
||||
external_ref = {'source-id': 'ID1'}
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'volume': {'provider_location': '12'}}
|
||||
size = self.driver.manage_existing_snapshot_get_size(test_snapshot,
|
||||
external_ref)
|
||||
self.assertEqual(1, size)
|
||||
|
||||
@mock.patch.object(rest_client.RestClient, 'rename_snapshot')
|
||||
def test_unmanage_snapshot(self, mock_rename):
|
||||
test_snapshot = {'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
|
||||
'id': '21ec7341-9256-497b-97d9-ef48edcf0635'}
|
||||
with mock.patch.object(rest_client.RestClient,
|
||||
'get_snapshot_id_by_name',
|
||||
return_value=None):
|
||||
self.driver.unmanage_snapshot(test_snapshot)
|
||||
self.assertEqual(0, mock_rename.call_count)
|
||||
|
||||
with mock.patch.object(rest_client.RestClient,
|
||||
'get_snapshot_id_by_name',
|
||||
return_value='ID1'):
|
||||
self.driver.unmanage_snapshot(test_snapshot)
|
||||
self.assertEqual(1, mock_rename.call_count)
|
||||
|
||||
|
||||
class FCSanLookupService(object):
|
||||
|
||||
@ -2236,7 +2389,7 @@ class HuaweiFCDriverTestCase(test.TestCase):
|
||||
def test_get_volume_status(self):
|
||||
|
||||
data = self.driver.get_volume_stats()
|
||||
self.assertEqual('2.0.2', data['driver_version'])
|
||||
self.assertEqual('2.0.3', data['driver_version'])
|
||||
|
||||
def test_extend_volume(self):
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
STATUS_HEALTH = '1'
|
||||
STATUS_ACTIVE = '43'
|
||||
STATUS_RUNNING = '10'
|
||||
STATUS_VOLUME_READY = '27'
|
||||
STATUS_LUNCOPY_READY = '40'
|
||||
|
@ -1142,6 +1142,103 @@ class HuaweiBaseDriver(driver.VolumeDriver):
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return int(size)
|
||||
|
||||
def _check_snapshot_valid_for_manage(self, snapshot_info, external_ref):
|
||||
snapshot_id = snapshot_info.get('ID')
|
||||
|
||||
# Check whether the snapshot is normal.
|
||||
if snapshot_info.get('HEALTHSTATUS') != constants.STATUS_HEALTH:
|
||||
msg = _("Can't import snapshot %s to Cinder. "
|
||||
"Snapshot status is not normal"
|
||||
" or running status is not online.") % snapshot_id
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=external_ref, reason=msg)
|
||||
|
||||
if snapshot_info.get('EXPOSEDTOINITIATOR') != 'false':
|
||||
msg = _("Can't import snapshot %s to Cinder. "
|
||||
"Snapshot is exposed to initiator.") % snapshot_id
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=external_ref, reason=msg)
|
||||
|
||||
def _get_snapshot_info_by_ref(self, external_ref):
|
||||
LOG.debug("Get snapshot external_ref: %s.", external_ref)
|
||||
name = external_ref.get('source-name')
|
||||
id = external_ref.get('source-id')
|
||||
if not (name or id):
|
||||
msg = _('Must specify snapshot source-name or source-id.')
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=external_ref, reason=msg)
|
||||
|
||||
snapshot_id = id or self.client.get_snapshot_id_by_name(name)
|
||||
if not snapshot_id:
|
||||
msg = _("Can't find snapshot on array, please check the "
|
||||
"source-name or source-id.")
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=external_ref, reason=msg)
|
||||
|
||||
snapshot_info = self.client.get_snapshot_info(snapshot_id)
|
||||
return snapshot_info
|
||||
|
||||
def manage_existing_snapshot(self, snapshot, existing_ref):
|
||||
snapshot_info = self._get_snapshot_info_by_ref(existing_ref)
|
||||
snapshot_id = snapshot_info.get('ID')
|
||||
volume = snapshot.get('volume')
|
||||
lun_id = volume.get('provider_location')
|
||||
if lun_id != snapshot_info.get('PARENTID'):
|
||||
msg = (_("Can't import snapshot %s to Cinder. "
|
||||
"Snapshot doesn't belong to volume."), snapshot_id)
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=existing_ref, reason=msg)
|
||||
|
||||
# Check whether this snapshot can be imported.
|
||||
self._check_snapshot_valid_for_manage(snapshot_info, existing_ref)
|
||||
|
||||
# Rename the snapshot to make it manageable for Cinder.
|
||||
description = snapshot['id']
|
||||
snapshot_name = huawei_utils.encode_name(snapshot['id'])
|
||||
self.client.rename_snapshot(snapshot_id, snapshot_name, description)
|
||||
if snapshot_info.get('RUNNINGSTATUS') != constants.STATUS_ACTIVE:
|
||||
self.client.activate_snapshot(snapshot_id)
|
||||
|
||||
LOG.debug("Rename snapshot %(old_name)s to %(new_name)s.",
|
||||
{'old_name': snapshot_info.get('NAME'),
|
||||
'new_name': snapshot_name})
|
||||
|
||||
return {'provider_location': snapshot_id}
|
||||
|
||||
def manage_existing_snapshot_get_size(self, snapshot, existing_ref):
|
||||
"""Get the size of the existing snapshot."""
|
||||
snapshot_info = self._get_snapshot_info_by_ref(existing_ref)
|
||||
size = (float(snapshot_info.get('USERCAPACITY'))
|
||||
// constants.CAPACITY_UNIT)
|
||||
remainder = (float(snapshot_info.get('USERCAPACITY'))
|
||||
% constants.CAPACITY_UNIT)
|
||||
if int(remainder) > 0:
|
||||
msg = _("Snapshot size must be multiple of 1 GB.")
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return int(size)
|
||||
|
||||
def unmanage_snapshot(self, snapshot):
|
||||
"""Unmanage the specified snapshot from Cinder management."""
|
||||
LOG.debug("Unmanage snapshot: %s.", snapshot['id'])
|
||||
snapshot_name = huawei_utils.encode_name(snapshot['id'])
|
||||
snapshot_id = self.client.get_snapshot_id_by_name(snapshot_name)
|
||||
if not snapshot_id:
|
||||
LOG.warning(_LW("Can't find snapshot on the array: %s."),
|
||||
snapshot_name)
|
||||
return
|
||||
new_name = 'unmged_' + snapshot_name
|
||||
LOG.debug("Rename snapshot %(snapshot_name)s to %(new_name)s.",
|
||||
{'snapshot_name': snapshot_name,
|
||||
'new_name': new_name})
|
||||
|
||||
try:
|
||||
self.client.rename_snapshot(snapshot_id, new_name)
|
||||
except Exception:
|
||||
LOG.warning(_LW("Failed to rename snapshot %(snapshot_id)s, "
|
||||
"snapshot name on array is %(snapshot_name)s."),
|
||||
{'snapshot_id': snapshot['id'],
|
||||
'snapshot_name': snapshot_name})
|
||||
|
||||
|
||||
class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
|
||||
"""ISCSI driver for Huawei storage arrays.
|
||||
@ -1159,9 +1256,10 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
|
||||
2.0.0 - Rename to HuaweiISCSIDriver
|
||||
2.0.1 - Manage/unmanage volume support
|
||||
2.0.2 - Refactor HuaweiISCSIDriver
|
||||
2.0.3 - Manage/unmanage snapshot support
|
||||
"""
|
||||
|
||||
VERSION = "2.0.2"
|
||||
VERSION = "2.0.3"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
|
||||
@ -1352,9 +1450,10 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
|
||||
2.0.0 - Rename to HuaweiFCDriver
|
||||
2.0.1 - Manage/unmanage volume support
|
||||
2.0.2 - Refactor HuaweiFCDriver
|
||||
2.0.3 - Manage/unmanage snapshot support
|
||||
"""
|
||||
|
||||
VERSION = "2.0.2"
|
||||
VERSION = "2.0.3"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HuaweiFCDriver, self).__init__(*args, **kwargs)
|
||||
|
@ -317,8 +317,12 @@ class RestClient(object):
|
||||
|
||||
def get_snapshot_id_by_name(self, name):
|
||||
url = "/snapshot?range=[0-32767]"
|
||||
description = 'The snapshot license file is unavailable.'
|
||||
result = self.call(url, None, "GET")
|
||||
self._assert_rest_result(result, _('Get snapshot id error.'))
|
||||
if 'error' in result:
|
||||
if description == result['error']['description']:
|
||||
return
|
||||
self._assert_rest_result(result, _('Get snapshot id error.'))
|
||||
|
||||
return self._get_id_from_result(result, name, 'NAME')
|
||||
|
||||
@ -1386,6 +1390,16 @@ class RestClient(object):
|
||||
|
||||
return result['data']
|
||||
|
||||
def get_snapshot_info(self, snapshot_id):
|
||||
url = "/snapshot/" + snapshot_id
|
||||
result = self.call(url, None, "GET")
|
||||
|
||||
msg = _('Get snapshot error.')
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
return result['data']
|
||||
|
||||
def extend_lun(self, lun_id, new_volume_size):
|
||||
url = "/lun/expand"
|
||||
data = {"TYPE": 11, "ID": lun_id,
|
||||
@ -1621,6 +1635,16 @@ class RestClient(object):
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
def rename_snapshot(self, snapshot_id, new_name, description=None):
|
||||
url = "/snapshot/" + snapshot_id
|
||||
data = {"NAME": new_name}
|
||||
if description:
|
||||
data.update({"DESCRIPTION": description})
|
||||
result = self.call(url, data, "PUT")
|
||||
msg = _('Rename snapshot on array error.')
|
||||
self._assert_rest_result(result, msg)
|
||||
self._assert_data_in_result(result, msg)
|
||||
|
||||
def is_fc_initiator_associated_to_host(self, ininame):
|
||||
"""Check whether the initiator is associated to the host."""
|
||||
url = '/fc_initiator?range=[0-256]'
|
||||
|
@ -0,0 +1,2 @@
|
||||
features:
|
||||
- Add manage/unmanage snapshot support for Huawei drivers.
|
Loading…
Reference in New Issue
Block a user