Merge "Manage / unmanage snapshot in NetApp cDOT drivers"

This commit is contained in:
Jenkins 2016-07-01 01:40:17 +00:00 committed by Gerrit Code Review
commit 1e24c27317
10 changed files with 331 additions and 7 deletions

View File

@ -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 | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+

View File

@ -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."""

View File

@ -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,

View File

@ -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,

View File

@ -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.

View File

@ -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>

View File

@ -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(

View File

@ -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,

View File

@ -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,

View File

@ -0,0 +1,5 @@
---
features:
- Add support for snapshot manage/unmanage to the
NetApp cDOT driver.