Merge "Manage / unmanage snapshot in NetApp cDOT drivers"
This commit is contained in:
commit
1e24c27317
@ -37,7 +37,7 @@ Mapping of share drivers and share features support
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| Generic (Cinder as back-end) | J | K | L | L | J | J | M |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | \- |
|
||||
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
| EMC VNX | J | \- | \- | \- | J | J | \- |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
||||
|
@ -1527,6 +1527,68 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
result = self.send_iter_request('volume-get-iter', api_args)
|
||||
return self._has_records(result)
|
||||
|
||||
@na_utils.trace
|
||||
def get_volume(self, volume_name):
|
||||
"""Returns the volume with the specified name, if present."""
|
||||
|
||||
api_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'name': volume_name,
|
||||
},
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'containing-aggregate-name': None,
|
||||
'junction-path': None,
|
||||
'name': None,
|
||||
'owning-vserver-name': None,
|
||||
'type': None,
|
||||
'style': None,
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
result = self.send_request('volume-get-iter', api_args)
|
||||
|
||||
attributes_list = result.get_child_by_name(
|
||||
'attributes-list') or netapp_api.NaElement('none')
|
||||
volume_attributes_list = attributes_list.get_children()
|
||||
|
||||
if not self._has_records(result):
|
||||
raise exception.StorageResourceNotFound(name=volume_name)
|
||||
elif len(volume_attributes_list) > 1:
|
||||
msg = _('Could not find unique volume %(vol)s.')
|
||||
msg_args = {'vol': volume_name}
|
||||
raise exception.NetAppException(msg % msg_args)
|
||||
|
||||
volume_attributes = volume_attributes_list[0]
|
||||
|
||||
volume_id_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-id-attributes') or netapp_api.NaElement('none')
|
||||
volume_space_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-space-attributes') or netapp_api.NaElement('none')
|
||||
|
||||
volume = {
|
||||
'aggregate': volume_id_attributes.get_child_content(
|
||||
'containing-aggregate-name'),
|
||||
'junction-path': volume_id_attributes.get_child_content(
|
||||
'junction-path'),
|
||||
'name': volume_id_attributes.get_child_content('name'),
|
||||
'owning-vserver-name': volume_id_attributes.get_child_content(
|
||||
'owning-vserver-name'),
|
||||
'type': volume_id_attributes.get_child_content('type'),
|
||||
'style': volume_id_attributes.get_child_content('style'),
|
||||
'size': volume_space_attributes.get_child_content('size'),
|
||||
}
|
||||
return volume
|
||||
|
||||
@na_utils.trace
|
||||
def get_volume_at_junction_path(self, junction_path):
|
||||
"""Returns the volume with the specified junction path, if present."""
|
||||
|
@ -53,7 +53,7 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
||||
snapshot, **kwargs)
|
||||
|
||||
def create_snapshot(self, context, snapshot, **kwargs):
|
||||
self.library.create_snapshot(context, snapshot, **kwargs)
|
||||
return self.library.create_snapshot(context, snapshot, **kwargs)
|
||||
|
||||
def delete_share(self, context, share, **kwargs):
|
||||
self.library.delete_share(context, share, **kwargs)
|
||||
@ -95,6 +95,12 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
||||
def unmanage(self, share):
|
||||
raise NotImplementedError
|
||||
|
||||
def manage_existing_snapshot(self, snapshot, driver_options):
|
||||
raise NotImplementedError
|
||||
|
||||
def unmanage_snapshot(self, snapshot):
|
||||
raise NotImplementedError
|
||||
|
||||
def update_access(self, context, share, access_rules, add_rules,
|
||||
delete_rules, **kwargs):
|
||||
self.library.update_access(context, share, access_rules, add_rules,
|
||||
|
@ -95,6 +95,12 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
|
||||
def unmanage(self, share):
|
||||
self.library.unmanage(share)
|
||||
|
||||
def manage_existing_snapshot(self, snapshot, driver_options):
|
||||
return self.library.manage_existing_snapshot(snapshot, driver_options)
|
||||
|
||||
def unmanage_snapshot(self, snapshot):
|
||||
self.library.unmanage_snapshot(snapshot)
|
||||
|
||||
def update_access(self, context, share, access_rules, add_rules,
|
||||
delete_rules, **kwargs):
|
||||
self.library.update_access(context, share, access_rules, add_rules,
|
||||
|
@ -873,6 +873,59 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
msg_args = {'volume': volume['name']}
|
||||
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||
|
||||
@na_utils.trace
|
||||
def manage_existing_snapshot(self, snapshot, driver_options):
|
||||
"""Brings an existing snapshot under Manila management."""
|
||||
vserver, vserver_client = self._get_vserver(share_server=None)
|
||||
share_name = self._get_backend_share_name(snapshot['share_id'])
|
||||
existing_snapshot_name = snapshot.get('provider_location')
|
||||
new_snapshot_name = self._get_backend_snapshot_name(snapshot['id'])
|
||||
|
||||
if not existing_snapshot_name:
|
||||
msg = _('provider_location not specified.')
|
||||
raise exception.ManageInvalidShareSnapshot(reason=msg)
|
||||
|
||||
# Get the volume containing the snapshot so we can report its size
|
||||
try:
|
||||
volume = vserver_client.get_volume(share_name)
|
||||
except (netapp_api.NaApiError,
|
||||
exception.StorageResourceNotFound,
|
||||
exception.NetAppException):
|
||||
msg = _('Could not determine snapshot %(snap)s size from '
|
||||
'volume %(vol)s.')
|
||||
msg_args = {'snap': existing_snapshot_name, 'vol': share_name}
|
||||
LOG.exception(msg % msg_args)
|
||||
raise exception.ShareNotFound(share_id=snapshot['share_id'])
|
||||
|
||||
# Ensure there aren't any mirrors on this volume
|
||||
if vserver_client.volume_has_snapmirror_relationships(volume):
|
||||
msg = _('Share %s has SnapMirror relationships.')
|
||||
msg_args = {'vol': share_name}
|
||||
raise exception.ManageInvalidShareSnapshot(reason=msg % msg_args)
|
||||
|
||||
# Rename snapshot
|
||||
try:
|
||||
vserver_client.rename_snapshot(share_name,
|
||||
existing_snapshot_name,
|
||||
new_snapshot_name)
|
||||
except netapp_api.NaApiError:
|
||||
msg = _('Could not rename snapshot %(snap)s in share %(vol)s.')
|
||||
msg_args = {'snap': existing_snapshot_name, 'vol': share_name}
|
||||
raise exception.ManageInvalidShareSnapshot(reason=msg % msg_args)
|
||||
|
||||
# Save original snapshot info to private storage
|
||||
original_data = {'original_name': existing_snapshot_name}
|
||||
self.private_storage.update(snapshot['id'], original_data)
|
||||
|
||||
# When calculating the size, round up to the next GB.
|
||||
size = int(math.ceil(float(volume['size']) / units.Gi))
|
||||
|
||||
return {'size': size, 'provider_location': new_snapshot_name}
|
||||
|
||||
@na_utils.trace
|
||||
def unmanage_snapshot(self, snapshot):
|
||||
"""Removes the specified snapshot from Manila management."""
|
||||
|
||||
@na_utils.trace
|
||||
def create_consistency_group(self, context, cg_dict, share_server=None):
|
||||
"""Creates a consistency group.
|
||||
|
@ -1692,6 +1692,27 @@ LUN_GET_ITER_RESPONSE = etree.XML("""
|
||||
'volume': SHARE_NAME,
|
||||
})
|
||||
|
||||
VOLUME_GET_ITER_NOT_UNIQUE_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<volume-attributes>
|
||||
<volume-id-attributes>
|
||||
<name>%(volume1)s</name>
|
||||
</volume-id-attributes>
|
||||
</volume-attributes>
|
||||
<volume-attributes>
|
||||
<volume-id-attributes>
|
||||
<name>%(volume2)s</name>
|
||||
</volume-id-attributes>
|
||||
</volume-attributes>
|
||||
</attributes-list>
|
||||
<num-records>2</num-records>
|
||||
</results>
|
||||
""" % {
|
||||
'volume1': SHARE_NAME,
|
||||
'volume2': SHARE_NAME_2,
|
||||
})
|
||||
|
||||
VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
|
@ -2782,6 +2782,77 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_get_volume(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.client.get_volume(fake.SHARE_NAME)
|
||||
|
||||
volume_get_iter_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'name': fake.SHARE_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'containing-aggregate-name': None,
|
||||
'junction-path': None,
|
||||
'name': None,
|
||||
'owning-vserver-name': None,
|
||||
'type': None,
|
||||
'style': None,
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected = {
|
||||
'aggregate': fake.SHARE_AGGREGATE_NAME,
|
||||
'junction-path': '/%s' % fake.SHARE_NAME,
|
||||
'name': fake.SHARE_NAME,
|
||||
'type': 'rw',
|
||||
'style': 'flex',
|
||||
'size': fake.SHARE_SIZE,
|
||||
'owning-vserver-name': fake.VSERVER_NAME,
|
||||
}
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||
self.assertDictEqual(expected, result)
|
||||
|
||||
def test_get_volume_not_found(self):
|
||||
|
||||
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
self.assertRaises(exception.StorageResourceNotFound,
|
||||
self.client.get_volume,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
def test_get_volume_not_unique(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.VOLUME_GET_ITER_NOT_UNIQUE_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.client.get_volume,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
def test_get_volume_at_junction_path(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
|
@ -1512,14 +1512,106 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
fake.FLEXVOL_TO_MANAGE,
|
||||
vserver_client)
|
||||
|
||||
def test_manage_existing_snapshot(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
mock_get_vserver = self.mock_object(
|
||||
self.library, '_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
|
||||
vserver_client.get_volume.return_value = fake.FLEXVOL_TO_MANAGE
|
||||
vserver_client.volume_has_snapmirror_relationships.return_value = False
|
||||
result = self.library.manage_existing_snapshot(
|
||||
fake.SNAPSHOT_TO_MANAGE, {})
|
||||
|
||||
share_name = self.library._get_backend_share_name(
|
||||
fake.SNAPSHOT['share_id'])
|
||||
new_snapshot_name = self.library._get_backend_snapshot_name(
|
||||
fake.SNAPSHOT['id'])
|
||||
mock_get_vserver.assert_called_once_with(share_server=None)
|
||||
(vserver_client.volume_has_snapmirror_relationships.
|
||||
assert_called_once_with(fake.FLEXVOL_TO_MANAGE))
|
||||
vserver_client.rename_snapshot.assert_called_once_with(
|
||||
share_name, fake.SNAPSHOT_NAME, new_snapshot_name)
|
||||
self.library.private_storage.update.assert_called_once_with(
|
||||
fake.SNAPSHOT['id'], {'original_name': fake.SNAPSHOT_NAME})
|
||||
self.assertEqual({'size': 2, 'provider_location': new_snapshot_name},
|
||||
result)
|
||||
|
||||
def test_manage_existing_snapshot_no_snapshot_name(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(self.library,
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
vserver_client)))
|
||||
vserver_client.get_volume.return_value = fake.FLEXVOL_TO_MANAGE
|
||||
vserver_client.volume_has_snapmirror_relationships.return_value = False
|
||||
fake_snapshot = copy.deepcopy(fake.SNAPSHOT_TO_MANAGE)
|
||||
fake_snapshot['provider_location'] = ''
|
||||
|
||||
self.assertRaises(exception.ManageInvalidShareSnapshot,
|
||||
self.library.manage_existing_snapshot,
|
||||
fake_snapshot, {})
|
||||
|
||||
@ddt.data(netapp_api.NaApiError,
|
||||
exception.NetAppException)
|
||||
def test_manage_existing_snapshot_get_volume_error(self, exception_type):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(self.library,
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
vserver_client)))
|
||||
vserver_client.get_volume.side_effect = exception_type
|
||||
self.mock_object(self.client,
|
||||
'volume_has_snapmirror_relationships',
|
||||
mock.Mock(return_value=False))
|
||||
|
||||
self.assertRaises(exception.ShareNotFound,
|
||||
self.library.manage_existing_snapshot,
|
||||
fake.SNAPSHOT_TO_MANAGE, {})
|
||||
|
||||
def test_manage_existing_snapshot_mirrors_present(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(self.library,
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
vserver_client)))
|
||||
vserver_client.get_volume.return_value = fake.FLEXVOL_TO_MANAGE
|
||||
vserver_client.volume_has_snapmirror_relationships.return_value = True
|
||||
|
||||
self.assertRaises(exception.ManageInvalidShareSnapshot,
|
||||
self.library.manage_existing_snapshot,
|
||||
fake.SNAPSHOT_TO_MANAGE, {})
|
||||
|
||||
def test_manage_existing_snapshot_rename_snapshot_error(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(self.library,
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
vserver_client)))
|
||||
vserver_client.get_volume.return_value = fake.FLEXVOL_TO_MANAGE
|
||||
vserver_client.volume_has_snapmirror_relationships.return_value = False
|
||||
vserver_client.rename_snapshot.side_effect = netapp_api.NaApiError
|
||||
|
||||
self.assertRaises(exception.ManageInvalidShareSnapshot,
|
||||
self.library.manage_existing_snapshot,
|
||||
fake.SNAPSHOT_TO_MANAGE, {})
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
|
||||
result = self.library.unmanage_snapshot(fake.SNAPSHOT)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_validate_volume_for_manage_snapmirror_relationships_present(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
vserver_client.volume_has_luns = mock.Mock(return_value=False)
|
||||
vserver_client.volume_has_junctioned_volumes = mock.Mock(
|
||||
return_value=False)
|
||||
vserver_client.volume_has_snapmirror_relationships = mock.Mock(
|
||||
return_value=True)
|
||||
vserver_client.volume_has_luns.return_value = False
|
||||
vserver_client.volume_has_junctioned_volumes.return_value = False
|
||||
vserver_client.volume_has_snapmirror_relationships.return_value = True
|
||||
|
||||
self.assertRaises(exception.ManageInvalidShare,
|
||||
self.library._validate_volume_for_manage,
|
||||
|
@ -268,6 +268,14 @@ SNAPSHOT = {
|
||||
'provider_location': None,
|
||||
}
|
||||
|
||||
SNAPSHOT_TO_MANAGE = {
|
||||
'id': SNAPSHOT_ID,
|
||||
'project_id': TENANT_ID,
|
||||
'share_id': PARENT_SHARE_ID,
|
||||
'status': constants.STATUS_CREATING,
|
||||
'provider_location': SNAPSHOT_NAME,
|
||||
}
|
||||
|
||||
CDOT_SNAPSHOT = {
|
||||
'name': SNAPSHOT_NAME,
|
||||
'volume': SHARE_NAME,
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- Add support for snapshot manage/unmanage to the
|
||||
NetApp cDOT driver.
|
||||
|
Loading…
Reference in New Issue
Block a user