diff --git a/doc/source/devref/share_back_ends_feature_support_mapping.rst b/doc/source/devref/share_back_ends_feature_support_mapping.rst
index c2141ebccf..482cf193d8 100644
--- a/doc/source/devref/share_back_ends_feature_support_mapping.rst
+++ b/doc/source/devref/share_back_ends_feature_support_mapping.rst
@@ -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 | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py
index d6357d1951..c669a13866 100644
--- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py
+++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py
@@ -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."""
diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py
index d6fa6e3e8b..460f70d7dd 100644
--- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py
+++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py
@@ -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,
diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py
index a4a6380aaf..a3690fe079 100644
--- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py
+++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py
@@ -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,
diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py
index 4a5b2169b8..590830f02a 100644
--- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py
+++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py
@@ -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.
diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py
index 8db9ff4f13..e526d7c61b 100644
--- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py
+++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py
@@ -1692,6 +1692,27 @@ LUN_GET_ITER_RESPONSE = etree.XML("""
'volume': SHARE_NAME,
})
+VOLUME_GET_ITER_NOT_UNIQUE_RESPONSE = etree.XML("""
+
+
+
+
+ %(volume1)s
+
+
+
+
+ %(volume2)s
+
+
+
+ 2
+
+""" % {
+ 'volume1': SHARE_NAME,
+ 'volume2': SHARE_NAME_2,
+})
+
VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE = etree.XML("""
diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py
index 2f03fff982..8f28e770f9 100644
--- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py
+++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py
@@ -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(
diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py
index 7a18724daa..920462e14f 100644
--- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py
+++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py
@@ -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,
diff --git a/manila/tests/share/drivers/netapp/dataontap/fakes.py b/manila/tests/share/drivers/netapp/dataontap/fakes.py
index b59da1aa20..ab97878d25 100644
--- a/manila/tests/share/drivers/netapp/dataontap/fakes.py
+++ b/manila/tests/share/drivers/netapp/dataontap/fakes.py
@@ -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,
diff --git a/releasenotes/notes/manage-unmanage-snapshot-in-netapp-cdot-driver-5cb4b1619c39625a.yaml b/releasenotes/notes/manage-unmanage-snapshot-in-netapp-cdot-driver-5cb4b1619c39625a.yaml
new file mode 100644
index 0000000000..56caca2871
--- /dev/null
+++ b/releasenotes/notes/manage-unmanage-snapshot-in-netapp-cdot-driver-5cb4b1619c39625a.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add support for snapshot manage/unmanage to the
+ NetApp cDOT driver.
+