From 153154c60c25ae13d41d7fd3c342d3d13d261477 Mon Sep 17 00:00:00 2001 From: Clinton Knight Date: Mon, 6 Jul 2015 22:22:19 -0400 Subject: [PATCH] Add manage/unmanage support to NetApp cDOT driver This commit adds support to the cDOT driver for the driver methods manage_existing and unmanage. The logic follows three sections: 1. Locate the resource to be managed, which is specified as the cDOT flexvol name in the export_path argument. 2. Ensure the flexvol to be brought under Manila management is of the appropriate type, the extra specs from the share type are valid and match the specified pool, and the flexvol has no LUNs or other flexvols mounted underneath it. 3. Rename the flexvol using normal Manila naming conventions, remount it on a new path matching the new name, update it to conform to all relevant qualified extra specs, and remove any existing export rules. Implements blueprint netapp-cdot-driver-manage-unmanage-share Change-Id: Ib6832baf24585791bf7ec292e5c6cdeb83fe280d --- .../netapp/dataontap/client/client_cmode.py | 280 ++++++++++ .../dataontap/cluster_mode/drv_multi_svm.py | 6 + .../dataontap/cluster_mode/drv_single_svm.py | 6 + .../netapp/dataontap/cluster_mode/lib_base.py | 116 ++++ .../netapp/dataontap/protocols/base.py | 4 + .../netapp/dataontap/protocols/cifs_cmode.py | 6 + .../netapp/dataontap/protocols/nfs_cmode.py | 9 +- .../drivers/netapp/dataontap/client/fakes.py | 76 +++ .../dataontap/client/test_client_cmode.py | 500 +++++++++++++++++- .../dataontap/cluster_mode/test_lib_base.py | 248 +++++++++ .../cluster_mode/test_lib_multi_svm.py | 1 + .../cluster_mode/test_lib_single_svm.py | 1 + .../share/drivers/netapp/dataontap/fakes.py | 16 +- .../netapp/dataontap/protocols/fakes.py | 4 + .../dataontap/protocols/test_cifs_cmode.py | 6 + .../dataontap/protocols/test_nfs_cmode.py | 21 + .../tests/share/drivers/netapp/test_common.py | 12 +- 17 files changed, 1290 insertions(+), 22 deletions(-) diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 6e373ebfd6..8b45833def 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -911,6 +911,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): api_args = {'path': '/vol/%s' % volume_name} self.send_request('sis-enable', api_args) + @na_utils.trace + def disable_dedup(self, volume_name): + """Disable deduplication on volume.""" + api_args = {'path': '/vol/%s' % volume_name} + self.send_request('sis-disable', api_args) + @na_utils.trace def enable_compression(self, volume_name): """Enable compression on volume.""" @@ -920,6 +926,45 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): } self.send_request('sis-set-config', api_args) + @na_utils.trace + def disable_compression(self, volume_name): + """Disable compression on volume.""" + api_args = { + 'path': '/vol/%s' % volume_name, + 'enable-compression': 'false' + } + self.send_request('sis-set-config', api_args) + + @na_utils.trace + def get_volume_efficiency_status(self, volume_name): + """Get dedupe & compression status for a volume.""" + api_args = { + 'query': { + 'sis-status-info': { + 'path': '/vol/%s' % volume_name, + }, + }, + 'desired-attributes': { + 'sis-status-info': { + 'state': None, + 'is-compression-enabled': None, + }, + }, + } + result = self.send_request('sis-get-iter', api_args) + + attributes_list = result.get_child_by_name( + 'attributes-list') or netapp_api.NaElement('none') + sis_status_info = attributes_list.get_child_by_name( + 'sis-status-info') or netapp_api.NaElement('none') + + return { + 'dedupe': True if 'enabled' == sis_status_info.get_child_content( + 'state') else False, + 'compression': True if 'true' == sis_status_info.get_child_content( + 'is-compression-enabled') else False, + } + @na_utils.trace def set_volume_max_files(self, volume_name, max_files): """Set flexvol file limit.""" @@ -971,6 +1016,78 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): errors[0].get_child_content('error-code'), errors[0].get_child_content('error-message')) + @na_utils.trace + def set_volume_name(self, volume_name, new_volume_name): + """Set flexvol name.""" + api_args = { + 'volume': volume_name, + 'new-volume-name': new_volume_name, + } + self.send_request('volume-rename', api_args) + + @na_utils.trace + def manage_volume(self, aggregate_name, volume_name, + thin_provisioned=False, snapshot_policy=None, + language=None, dedup_enabled=False, + compression_enabled=False, max_files=None): + """Update volume as needed to bring under management as a share.""" + api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': aggregate_name, + 'name': volume_name, + }, + }, + }, + 'attributes': { + 'volume-attributes': { + 'volume-inode-attributes': {}, + 'volume-language-attributes': {}, + 'volume-snapshot-attributes': {}, + 'volume-space-attributes': { + 'space-guarantee': ('none' if thin_provisioned else + 'volume') + }, + }, + }, + } + if language: + api_args['attributes']['volume-attributes'][ + 'volume-language-attributes']['language'] = language + if max_files: + api_args['attributes']['volume-attributes'][ + 'volume-inode-attributes']['files-total'] = max_files + if snapshot_policy: + api_args['attributes']['volume-attributes'][ + 'volume-snapshot-attributes'][ + 'snapshot-policy'] = snapshot_policy + + self.send_request('volume-modify-iter', api_args) + + # Efficiency options must be handled separately + self.update_volume_efficiency_attributes(volume_name, + dedup_enabled, + compression_enabled) + + @na_utils.trace + def update_volume_efficiency_attributes(self, volume_name, dedup_enabled, + compression_enabled): + """Update dedupe & compression attributes to match desired values.""" + efficiency_status = self.get_volume_efficiency_status(volume_name) + + if efficiency_status['compression'] != compression_enabled: + if compression_enabled: + self.enable_compression(volume_name) + else: + self.disable_compression(volume_name) + + if efficiency_status['dedupe'] != dedup_enabled: + if dedup_enabled: + self.enable_dedup(volume_name) + else: + self.disable_dedup(volume_name) + @na_utils.trace def volume_exists(self, volume_name): """Checks if volume exists.""" @@ -1034,6 +1151,159 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): return aggregate + @na_utils.trace + def volume_has_luns(self, volume_name): + """Checks if volume has LUNs.""" + LOG.debug('Checking if volume %s has LUNs', volume_name) + + api_args = { + 'query': { + 'lun-info': { + 'volume': volume_name, + }, + }, + 'desired-attributes': { + 'lun-info': { + 'path': None, + }, + }, + } + result = self.send_request('lun-get-iter', api_args) + return self._has_records(result) + + @na_utils.trace + def volume_has_junctioned_volumes(self, volume_name): + """Checks if volume has volumes mounted beneath its junction path.""" + junction_path = self.get_volume_junction_path(volume_name) + if not junction_path: + return False + + api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'junction-path': junction_path + '/*', + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'name': None, + }, + }, + }, + } + result = self.send_request('volume-get-iter', api_args) + return self._has_records(result) + + @na_utils.trace + def get_volume_at_junction_path(self, junction_path): + """Returns the volume with the specified junction path, if present.""" + if not junction_path: + return None + + api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'junction-path': junction_path, + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': None, + 'junction-path': None, + 'name': None, + 'type': None, + 'style': None, + }, + 'volume-space-attributes': { + 'size': None, + } + }, + }, + } + result = self.send_request('volume-get-iter', api_args) + if not self._has_records(result): + return None + + attributes_list = result.get_child_by_name( + 'attributes-list') or netapp_api.NaElement('none') + volume_attributes = attributes_list.get_child_by_name( + 'volume-attributes') or netapp_api.NaElement('none') + 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'), + '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_to_manage(self, aggregate_name, volume_name): + """Get flexvol to be managed by Manila.""" + + api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': aggregate_name, + 'name': volume_name, + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': None, + 'junction-path': None, + 'name': None, + 'type': None, + 'style': None, + }, + 'volume-space-attributes': { + 'size': None, + } + }, + }, + } + result = self.send_request('volume-get-iter', api_args) + if not self._has_records(result): + return None + + attributes_list = result.get_child_by_name( + 'attributes-list') or netapp_api.NaElement('none') + volume_attributes = attributes_list.get_child_by_name( + 'volume-attributes') or netapp_api.NaElement('none') + 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'), + '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 create_volume_clone(self, volume_name, parent_volume_name, parent_snapshot_name=None): @@ -1062,6 +1332,16 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): result = self.send_request('volume-get-volume-path', api_args) return result.get_child_content('junction') + @na_utils.trace + def mount_volume(self, volume_name, junction_path=None): + """Mounts a volume on a junction path.""" + api_args = { + 'volume-name': volume_name, + 'junction-path': (junction_path if junction_path + else '/%s' % volume_name) + } + self.send_request('volume-mount', api_args) + @na_utils.trace def offline_volume(self, volume_name): """Offlines a volume.""" 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 740e6ce7ad..4aa3390237 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 @@ -67,6 +67,12 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver): def ensure_share(self, context, share, **kwargs): pass + def manage_existing(self, share, driver_options): + raise NotImplementedError + + def unmanage(self, share): + raise NotImplementedError + def allow_access(self, context, share, access, **kwargs): self.library.allow_access(context, share, access, **kwargs) 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 4714b41928..9a0e16d358 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 @@ -67,6 +67,12 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver): def ensure_share(self, context, share, **kwargs): pass + def manage_existing(self, share, driver_options): + return self.library.manage_existing(share, driver_options) + + def unmanage(self, share): + self.library.unmanage(share) + def allow_access(self, context, share, access, **kwargs): self.library.allow_access(context, share, access, **kwargs) 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 98e3b40acc..d2d9e34d1c 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py @@ -20,6 +20,7 @@ single-SVM or multi-SVM functionality needed by the cDOT Manila drivers. """ import copy +import math import socket import time @@ -66,6 +67,7 @@ class NetAppCmodeFileStorageLibrary(object): self.driver_name = driver_name + self.private_storage = kwargs['private_storage'] self.configuration = kwargs['configuration'] self.configuration.append_config_values(na_opts.netapp_connection_opts) self.configuration.append_config_values(na_opts.netapp_basicauth_opts) @@ -483,6 +485,23 @@ class NetAppCmodeFileStorageLibrary(object): result.update(string_args) return result + @na_utils.trace + def _check_aggregate_extra_specs_validity(self, aggregate_name, specs): + + for specs_key in ('netapp_disk_type', 'netapp_raid_type'): + aggr_value = self._ssc_stats.get(aggregate_name, {}).get(specs_key) + specs_value = specs.get(specs_key) + + if aggr_value and specs_value and aggr_value != specs_value: + msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" ' + 'in aggregate %(aggr)s.') + msg_args = { + 'value': specs_value, + 'key': specs_key, + 'aggr': aggregate_name + } + raise exception.NetAppException(msg % msg_args) + @na_utils.trace def _allocate_container_from_snapshot(self, share, snapshot, vserver_client): @@ -631,6 +650,103 @@ class NetAppCmodeFileStorageLibrary(object): raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot_name) + @na_utils.trace + def manage_existing(self, share, driver_options): + vserver, vserver_client = self._get_vserver(share_server=None) + share_size = self._manage_container(share, vserver_client) + export_locations = self._create_export(share, vserver, vserver_client) + return {'size': share_size, 'export_locations': export_locations} + + @na_utils.trace + def unmanage(self, share): + pass + + @na_utils.trace + def _manage_container(self, share, vserver_client): + """Bring existing volume under management as a share.""" + + protocol_helper = self._get_helper(share) + protocol_helper.set_client(vserver_client) + + volume_name = protocol_helper.get_share_name_for_share(share) + if not volume_name: + msg = _('Volume could not be determined from export location ' + '%(export)s.') + msg_args = {'export': share['export_location']} + raise exception.ManageInvalidShare(reason=msg % msg_args) + + share_name = self._get_valid_share_name(share['id']) + aggregate_name = share_utils.extract_host(share['host'], level='pool') + + # Get existing volume info + volume = vserver_client.get_volume_to_manage(aggregate_name, + volume_name) + if not volume: + msg = _('Volume %(volume)s not found on aggregate %(aggr)s.') + msg_args = {'volume': volume_name, 'aggr': aggregate_name} + raise exception.ManageInvalidShare(reason=msg % msg_args) + + # Ensure volume is manageable + self._validate_volume_for_manage(volume, vserver_client) + + # Validate extra specs + extra_specs = share_types.get_extra_specs_from_share(share) + try: + self._check_extra_specs_validity(share, extra_specs) + self._check_aggregate_extra_specs_validity(aggregate_name, + extra_specs) + except exception.ManilaException as ex: + raise exception.ManageExistingShareTypeMismatch( + reason=six.text_type(ex)) + provisioning_options = self._get_provisioning_options(extra_specs) + + debug_args = { + 'share': share_name, + 'aggr': aggregate_name, + 'options': provisioning_options + } + LOG.debug('Managing share %(share)s on aggregate %(aggr)s with ' + 'provisioning options %(options)s', debug_args) + + # Rename & remount volume on new path + vserver_client.unmount_volume(volume_name) + vserver_client.set_volume_name(volume_name, share_name) + vserver_client.mount_volume(share_name) + + # Modify volume to match extra specs + vserver_client.manage_volume(aggregate_name, share_name, + **provisioning_options) + + # Save original volume info to private storage + original_data = { + 'original_name': volume['name'], + 'original_junction_path': volume['junction-path'] + } + self.private_storage.update(share['id'], original_data) + + # When calculating the size, round up to the next GB. + return int(math.ceil(float(volume['size']) / units.Gi)) + + @na_utils.trace + def _validate_volume_for_manage(self, volume, vserver_client): + """Ensure volume is a candidate for becoming a share.""" + + # Check volume info, extra specs validity + if volume['type'] != 'rw' or volume['style'] != 'flex': + msg = _('Volume %(volume)s must be a read-write flexible volume.') + msg_args = {'volume': volume['name']} + raise exception.ManageInvalidShare(reason=msg % msg_args) + + if vserver_client.volume_has_luns(volume['name']): + msg = _('Volume %(volume)s must not contain LUNs.') + msg_args = {'volume': volume['name']} + raise exception.ManageInvalidShare(reason=msg % msg_args) + + if vserver_client.volume_has_junctioned_volumes(volume['name']): + msg = _('Volume %(volume)s must not have junctioned volumes.') + msg_args = {'volume': volume['name']} + raise exception.ManageInvalidShare(reason=msg % msg_args) + @na_utils.trace def extend_share(self, share, new_size, share_server=None): """Extends size of existing share.""" diff --git a/manila/share/drivers/netapp/dataontap/protocols/base.py b/manila/share/drivers/netapp/dataontap/protocols/base.py index ac7e549505..c9fbd568f0 100644 --- a/manila/share/drivers/netapp/dataontap/protocols/base.py +++ b/manila/share/drivers/netapp/dataontap/protocols/base.py @@ -47,3 +47,7 @@ class NetAppBaseHelper(object): @abc.abstractmethod def get_target(self, share): """Returns host where the share located.""" + + @abc.abstractmethod + def get_share_name_for_share(self, share): + """Returns the flexvol name that hosts a share.""" \ No newline at end of file diff --git a/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py b/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py index 69c9a6e191..9f022d44de 100644 --- a/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py +++ b/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py @@ -84,6 +84,12 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper): """Returns OnTap target IP based on share export location.""" return self._get_export_location(share)[0] + @na_utils.trace + def get_share_name_for_share(self, share): + """Returns the flexvol name that hosts a share.""" + _, share_name = self._get_export_location(share) + return share_name + @staticmethod def _get_export_location(share): """Returns host ip and share name for a given CIFS share.""" diff --git a/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py b/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py index 55464ae04b..f6c77398c6 100644 --- a/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py +++ b/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py @@ -84,11 +84,18 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper): """Returns ID of target OnTap device based on export location.""" return self._get_export_location(share)[0] + @na_utils.trace + def get_share_name_for_share(self, share): + """Returns the flexvol name that hosts a share.""" + _, volume_junction_path = self._get_export_location(share) + volume = self._client.get_volume_at_junction_path(volume_junction_path) + return volume.get('name') if volume else None + @staticmethod def _get_export_location(share): """Returns IP address and export location of an NFS share.""" export_location = share['export_location'] or ':' - return export_location.split(':') + return export_location.rsplit(':', 1) @staticmethod def _get_export_policy_name(share): diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py index 6983917be0..e336fff269 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py @@ -35,10 +35,13 @@ SHARE_AGGREGATE_NAMES = ('fake_aggr1', 'fake_aggr2') SHARE_AGGREGATE_RAID_TYPES = ('raid4', 'raid_dp') SHARE_AGGREGATE_DISK_TYPE = 'FCAL' SHARE_NAME = 'fake_share' +SHARE_SIZE = '1000000000' SNAPSHOT_NAME = 'fake_snapshot' PARENT_SHARE_NAME = 'fake_parent_share' PARENT_SNAPSHOT_NAME = 'fake_parent_snapshot' MAX_FILES = 5000 +LANGUAGE = 'fake_language' +SNAPSHOT_POLICY_NAME = 'fake_snapshot_policy' EXPORT_POLICY_NAME = 'fake_export_policy' DELETED_EXPORT_POLICIES = { VSERVER_NAME: [ @@ -1366,3 +1369,76 @@ DELETED_EXPORT_POLICY_GET_ITER_RESPONSE = etree.XML(""" 'policy2': DELETED_EXPORT_POLICIES[VSERVER_NAME][1], 'policy3': DELETED_EXPORT_POLICIES[VSERVER_NAME_2][0], }) + +LUN_GET_ITER_RESPONSE = etree.XML(""" + + + + /vol/%(volume)s/fakelun + + %(volume)s + %(vserver)s + + + 1 + +""" % { + 'vserver': VSERVER_NAME, + 'volume': SHARE_NAME, +}) + +VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE = etree.XML(""" + + + + + fake_volume + test + + + + 1 + +""") + +VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE = etree.XML(""" + + + + + %(aggr)s + /%(volume)s + %(volume)s + %(vserver)s + + rw + + + %(size)s + + + + 1 + +""" % { + 'aggr': SHARE_AGGREGATE_NAME, + 'vserver': VSERVER_NAME, + 'volume': SHARE_NAME, + 'size': SHARE_SIZE, +}) + +SIS_GET_ITER_RESPONSE = etree.XML(""" + + + + true + /vol/%(volume)s + enabled + %(vserver)s + + + +""" % { + 'vserver': VSERVER_NAME, + 'volume': SHARE_NAME, +}) 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 2ecde734a1..11203aa1f3 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 @@ -1667,6 +1667,96 @@ class NetAppClientCmodeTestCase(test.TestCase): self.client.enable_dedup.assert_called_once_with(fake.SHARE_NAME) self.client.enable_compression.assert_called_once_with(fake.SHARE_NAME) + def test_enable_dedup(self): + + self.mock_object(self.client, 'send_request') + + self.client.enable_dedup(fake.SHARE_NAME) + + sis_enable_args = {'path': '/vol/%s' % fake.SHARE_NAME} + + self.client.send_request.assert_called_once_with('sis-enable', + sis_enable_args) + + def test_disable_dedup(self): + + self.mock_object(self.client, 'send_request') + + self.client.disable_dedup(fake.SHARE_NAME) + + sis_disable_args = {'path': '/vol/%s' % fake.SHARE_NAME} + + self.client.send_request.assert_called_once_with('sis-disable', + sis_disable_args) + + def test_enable_compression(self): + + self.mock_object(self.client, 'send_request') + + self.client.enable_compression(fake.SHARE_NAME) + + sis_set_config_args = { + 'path': '/vol/%s' % fake.SHARE_NAME, + 'enable-compression': 'true' + } + + self.client.send_request.assert_called_once_with('sis-set-config', + sis_set_config_args) + + def test_disable_compression(self): + + self.mock_object(self.client, 'send_request') + + self.client.disable_compression(fake.SHARE_NAME) + + sis_set_config_args = { + 'path': '/vol/%s' % fake.SHARE_NAME, + 'enable-compression': 'false' + } + + self.client.send_request.assert_called_once_with('sis-set-config', + sis_set_config_args) + + def test_get_volume_efficiency_status(self): + + api_response = netapp_api.NaElement(fake.SIS_GET_ITER_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.get_volume_efficiency_status(fake.SHARE_NAME) + + sis_get_iter_args = { + 'query': { + 'sis-status-info': { + 'path': '/vol/%s' % fake.SHARE_NAME, + }, + }, + 'desired-attributes': { + 'sis-status-info': { + 'state': None, + 'is-compression-enabled': None, + }, + }, + } + self.client.send_request.assert_has_calls([ + mock.call('sis-get-iter', sis_get_iter_args)]) + + expected = {'dedupe': True, 'compression': True} + self.assertDictEqual(expected, result) + + def test_get_volume_efficiency_status_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)) + + result = self.client.get_volume_efficiency_status(fake.SHARE_NAME) + + expected = {'dedupe': False, 'compression': False} + self.assertDictEqual(expected, result) + def test_set_volume_max_files(self): self.mock_object(self.client, 'send_request') @@ -1693,30 +1783,154 @@ class NetAppClientCmodeTestCase(test.TestCase): self.client.send_request.assert_called_once_with( 'volume-modify-iter', volume_modify_iter_api_args) - def test_enable_dedup(self): + def test_set_volume_name(self): self.mock_object(self.client, 'send_request') - self.client.enable_dedup(fake.SHARE_NAME) + self.client.set_volume_name(fake.SHARE_NAME, 'new_name') - sis_enable_args = {'path': '/vol/%s' % fake.SHARE_NAME} - - self.client.send_request.assert_called_once_with('sis-enable', - sis_enable_args) - - def test_enable_compression(self): - - self.mock_object(self.client, 'send_request') - - self.client.enable_compression(fake.SHARE_NAME) - - sis_set_config_args = { - 'path': '/vol/%s' % fake.SHARE_NAME, - 'enable-compression': 'true' + volume_rename_api_args = { + 'volume': fake.SHARE_NAME, + 'new-volume-name': 'new_name', } - self.client.send_request.assert_called_once_with('sis-set-config', - sis_set_config_args) + self.client.send_request.assert_called_once_with( + 'volume-rename', volume_rename_api_args) + + def test_manage_volume_no_optional_args(self): + + self.mock_object(self.client, 'send_request') + mock_update_volume_efficiency_attributes = self.mock_object( + self.client, 'update_volume_efficiency_attributes') + + self.client.manage_volume(fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME) + + volume_modify_iter_api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME, + 'name': fake.SHARE_NAME, + }, + }, + }, + 'attributes': { + 'volume-attributes': { + 'volume-inode-attributes': {}, + 'volume-language-attributes': {}, + 'volume-snapshot-attributes': {}, + 'volume-space-attributes': { + 'space-guarantee': 'volume', + }, + }, + }, + } + + self.client.send_request.assert_called_once_with( + 'volume-modify-iter', volume_modify_iter_api_args) + mock_update_volume_efficiency_attributes.assert_called_once_with( + fake.SHARE_NAME, False, False) + + def test_manage_volume_all_optional_args(self): + + self.mock_object(self.client, 'send_request') + mock_update_volume_efficiency_attributes = self.mock_object( + self.client, 'update_volume_efficiency_attributes') + + self.client.manage_volume(fake.SHARE_AGGREGATE_NAME, + fake.SHARE_NAME, + thin_provisioned=True, + snapshot_policy=fake.SNAPSHOT_POLICY_NAME, + language=fake.LANGUAGE, + dedup_enabled=True, + compression_enabled=False, + max_files=fake.MAX_FILES) + + volume_modify_iter_api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME, + 'name': fake.SHARE_NAME, + }, + }, + }, + 'attributes': { + 'volume-attributes': { + 'volume-inode-attributes': { + 'files-total': fake.MAX_FILES, + }, + 'volume-language-attributes': { + 'language': fake.LANGUAGE, + }, + 'volume-snapshot-attributes': { + 'snapshot-policy': fake.SNAPSHOT_POLICY_NAME, + }, + 'volume-space-attributes': { + 'space-guarantee': 'none', + }, + }, + }, + } + + self.client.send_request.assert_called_once_with( + 'volume-modify-iter', volume_modify_iter_api_args) + mock_update_volume_efficiency_attributes.assert_called_once_with( + fake.SHARE_NAME, True, False) + + @ddt.data( + {'existing': (True, True), 'desired': (True, True)}, + {'existing': (True, True), 'desired': (False, False)}, + {'existing': (True, True), 'desired': (True, False)}, + {'existing': (True, False), 'desired': (True, False)}, + {'existing': (True, False), 'desired': (False, False)}, + {'existing': (True, False), 'desired': (True, True)}, + {'existing': (False, False), 'desired': (False, False)}, + {'existing': (False, False), 'desired': (True, False)}, + {'existing': (False, False), 'desired': (True, True)}, + ) + @ddt.unpack + def test_update_volume_efficiency_attributes(self, existing, desired): + + existing_dedupe = existing[0] + existing_compression = existing[1] + desired_dedupe = desired[0] + desired_compression = desired[1] + + self.mock_object( + self.client, + 'get_volume_efficiency_status', + mock.Mock(return_value={'dedupe': existing_dedupe, + 'compression': existing_compression})) + mock_enable_compression = self.mock_object(self.client, + 'enable_compression') + mock_disable_compression = self.mock_object(self.client, + 'disable_compression') + mock_enable_dedup = self.mock_object(self.client, 'enable_dedup') + mock_disable_dedup = self.mock_object(self.client, 'disable_dedup') + + self.client.update_volume_efficiency_attributes( + fake.SHARE_NAME, desired_dedupe, desired_compression) + + if existing_dedupe == desired_dedupe: + self.assertFalse(mock_enable_dedup.called) + self.assertFalse(mock_disable_dedup.called) + elif existing_dedupe and not desired_dedupe: + self.assertFalse(mock_enable_dedup.called) + self.assertTrue(mock_disable_dedup.called) + elif not existing_dedupe and desired_dedupe: + self.assertTrue(mock_enable_dedup.called) + self.assertFalse(mock_disable_dedup.called) + + if existing_compression == desired_compression: + self.assertFalse(mock_enable_compression.called) + self.assertFalse(mock_disable_compression.called) + elif existing_compression and not desired_compression: + self.assertFalse(mock_enable_compression.called) + self.assertTrue(mock_disable_compression.called) + elif not existing_compression and desired_compression: + self.assertTrue(mock_enable_compression.called) + self.assertFalse(mock_disable_compression.called) def test_set_volume_size(self): @@ -1841,6 +2055,227 @@ class NetAppClientCmodeTestCase(test.TestCase): self.client.get_aggregate_for_volume, fake.SHARE_NAME) + def test_volume_has_luns(self): + + api_response = netapp_api.NaElement(fake.LUN_GET_ITER_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.volume_has_luns(fake.SHARE_NAME) + + lun_get_iter_args = { + 'query': { + 'lun-info': { + 'volume': fake.SHARE_NAME, + }, + }, + 'desired-attributes': { + 'lun-info': { + 'path': None, + }, + }, + } + + self.client.send_request.assert_has_calls([ + mock.call('lun-get-iter', lun_get_iter_args)]) + self.assertTrue(result) + + def test_volume_has_luns_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)) + + result = self.client.volume_has_luns(fake.SHARE_NAME) + + self.assertFalse(result) + + def test_volume_has_junctioned_volumes(self): + + api_response = netapp_api.NaElement( + fake.VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + fake_junction_path = '/%s' % fake.SHARE_NAME + self.mock_object(self.client, + 'get_volume_junction_path', + mock.Mock(return_value=fake_junction_path)) + + result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME) + + volume_get_iter_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'junction-path': fake_junction_path + '/*', + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'name': None, + }, + }, + }, + } + self.client.send_request.assert_has_calls([ + mock.call('volume-get-iter', volume_get_iter_args)]) + self.assertTrue(result) + + def test_volume_has_junctioned_volumes_no_junction_path(self): + + self.mock_object(self.client, + 'get_volume_junction_path', + mock.Mock(return_value='')) + + result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME) + + self.assertFalse(result) + + def test_volume_has_junctioned_volumes_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)) + + fake_junction_path = '/%s' % fake.SHARE_NAME + self.mock_object(self.client, + 'get_volume_junction_path', + mock.Mock(return_value=fake_junction_path)) + + result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME) + + self.assertFalse(result) + + def test_get_volume_at_junction_path(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)) + fake_junction_path = '/%s' % fake.SHARE_NAME + + result = self.client.get_volume_at_junction_path(fake_junction_path) + + volume_get_iter_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'junction-path': fake_junction_path, + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': None, + 'junction-path': None, + 'name': None, + 'type': None, + 'style': None, + }, + 'volume-space-attributes': { + 'size': None, + } + }, + }, + } + expected = { + 'aggregate': fake.SHARE_AGGREGATE_NAME, + 'junction-path': fake_junction_path, + 'name': fake.SHARE_NAME, + 'type': 'rw', + 'style': 'flex', + 'size': fake.SHARE_SIZE, + } + self.client.send_request.assert_has_calls([ + mock.call('volume-get-iter', volume_get_iter_args)]) + self.assertDictEqual(expected, result) + + def test_get_volume_at_junction_path_not_specified(self): + + result = self.client.get_volume_at_junction_path(None) + + self.assertIsNone(result) + + def test_get_volume_at_junction_path_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)) + fake_junction_path = '/%s' % fake.SHARE_NAME + + result = self.client.get_volume_at_junction_path(fake_junction_path) + + self.assertIsNone(result) + + def test_get_volume_to_manage(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_to_manage(fake.SHARE_AGGREGATE_NAME, + fake.SHARE_NAME) + + volume_get_iter_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME, + 'name': fake.SHARE_NAME, + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': None, + 'junction-path': None, + '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, + } + self.client.send_request.assert_has_calls([ + mock.call('volume-get-iter', volume_get_iter_args)]) + self.assertDictEqual(expected, result) + + def test_get_volume_to_manage_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)) + + result = self.client.get_volume_to_manage(fake.SHARE_AGGREGATE_NAME, + fake.SHARE_NAME) + + self.assertIsNone(result) + def test_create_volume_clone(self): self.mock_object(self.client, 'send_request') @@ -1909,6 +2344,35 @@ class NetAppClientCmodeTestCase(test.TestCase): mock.call('volume-get-volume-path', volume_get_volume_path_args)]) self.assertEqual(fake.VOLUME_JUNCTION_PATH_CIFS, result) + def test_mount_volume_default_junction_path(self): + + self.mock_object(self.client, 'send_request') + + self.client.mount_volume(fake.SHARE_NAME) + + volume_mount_args = { + 'volume-name': fake.SHARE_NAME, + 'junction-path': '/%s' % fake.SHARE_NAME, + } + + self.client.send_request.assert_has_calls([ + mock.call('volume-mount', volume_mount_args)]) + + def test_mount_volume(self): + + self.mock_object(self.client, 'send_request') + fake_path = '/fake_path' + + self.client.mount_volume(fake.SHARE_NAME, junction_path=fake_path) + + volume_mount_args = { + 'volume-name': fake.SHARE_NAME, + 'junction-path': fake_path, + } + + self.client.send_request.assert_has_calls([ + mock.call('volume-mount', volume_mount_args)]) + def test_offline_volume(self): self.mock_object(self.client, 'send_request') 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 184ea1982c..eeed47509f 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 @@ -17,6 +17,7 @@ Unit tests for the NetApp Data ONTAP cDOT base storage driver library. """ import copy +import math import socket import time @@ -24,6 +25,7 @@ import ddt import mock from oslo_log import log from oslo_service import loopingcall +from oslo_utils import units from manila import exception from manila.share.drivers.netapp.dataontap.client import client_cmode @@ -63,6 +65,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): kwargs = { 'configuration': fake.get_config_cmode(), + 'private_storage': mock.Mock(), 'app_version': fake.APP_VERSION } self.library = lib_base.NetAppCmodeFileStorageLibrary(fake.DRIVER_NAME, @@ -730,6 +733,26 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): fake_share, vserver_client) + def test_check_aggregate_extra_specs_validity(self): + + self.library._have_cluster_creds = True + self.library._ssc_stats = fake.SSC_INFO + + result = self.library._check_aggregate_extra_specs_validity( + fake.AGGREGATES[0], fake.EXTRA_SPEC) + + self.assertIsNone(result) + + def test_check_aggregate_extra_specs_validity_no_match(self): + + self.library._have_cluster_creds = True + self.library._ssc_stats = fake.SSC_INFO + + self.assertRaises(exception.NetAppException, + self.library._check_aggregate_extra_specs_validity, + fake.AGGREGATES[1], + fake.EXTRA_SPEC) + def test_allocate_container_from_snapshot(self): vserver_client = mock.Mock() @@ -1086,6 +1109,231 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): mock_sleep.assert_has_calls([mock.call(3)] * 20) self.assertEqual(20, lib_base.LOG.debug.call_count) + def test_manage_existing(self): + + vserver_client = mock.Mock() + self.mock_object(self.library, + '_get_vserver', + mock.Mock(return_value=(fake.VSERVER1, + vserver_client))) + mock_manage_container = self.mock_object( + self.library, + '_manage_container', + mock.Mock(return_value=fake.SHARE_SIZE)) + mock_create_export = self.mock_object( + self.library, + '_create_export', + mock.Mock(return_value=fake.NFS_EXPORTS)) + + result = self.library.manage_existing(fake.SHARE, {}) + + expected = { + 'size': fake.SHARE_SIZE, + 'export_locations': fake.NFS_EXPORTS + } + mock_manage_container.assert_called_once_with(fake.SHARE, + vserver_client) + mock_create_export.assert_called_once_with(fake.SHARE, + fake.VSERVER1, + vserver_client) + self.assertDictEqual(expected, result) + + def test_unmanage(self): + + result = self.library.unmanage(fake.SHARE) + + self.assertIsNone(result) + + def test_manage_container(self): + + vserver_client = mock.Mock() + + share_to_manage = copy.deepcopy(fake.SHARE) + share_to_manage['export_location'] = fake.EXPORT_LOCATION + + mock_helper = mock.Mock() + mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME + self.mock_object(self.library, + '_get_helper', + mock.Mock(return_value=mock_helper)) + + mock_get_volume_to_manage = self.mock_object( + vserver_client, + 'get_volume_to_manage', + mock.Mock(return_value=fake.FLEXVOL_TO_MANAGE)) + mock_validate_volume_for_manage = self.mock_object( + self.library, + '_validate_volume_for_manage') + self.mock_object(share_types, + 'get_extra_specs_from_share', + mock.Mock(return_value=fake.EXTRA_SPEC)) + mock_check_extra_specs_validity = self.mock_object( + self.library, + '_check_extra_specs_validity') + mock_check_aggregate_extra_specs_validity = self.mock_object( + self.library, + '_check_aggregate_extra_specs_validity') + + result = self.library._manage_container(share_to_manage, + vserver_client) + + mock_get_volume_to_manage.assert_called_once_with( + fake.POOL_NAME, fake.FLEXVOL_NAME) + mock_validate_volume_for_manage.assert_called_once_with( + fake.FLEXVOL_TO_MANAGE, vserver_client) + mock_check_extra_specs_validity.assert_called_once_with( + share_to_manage, fake.EXTRA_SPEC) + mock_check_aggregate_extra_specs_validity.assert_called_once_with( + fake.POOL_NAME, fake.EXTRA_SPEC) + vserver_client.unmount_volume.assert_called_once_with( + fake.FLEXVOL_NAME) + vserver_client.set_volume_name.assert_called_once_with( + fake.FLEXVOL_NAME, fake.SHARE_NAME) + vserver_client.mount_volume.assert_called_once_with( + fake.SHARE_NAME) + vserver_client.manage_volume.assert_called_once_with( + fake.POOL_NAME, fake.SHARE_NAME, + **self.library._get_provisioning_options(fake.EXTRA_SPEC)) + + original_data = { + 'original_name': fake.FLEXVOL_TO_MANAGE['name'], + 'original_junction_path': fake.FLEXVOL_TO_MANAGE['junction-path'], + } + self.library.private_storage.update.assert_called_once_with( + fake.SHARE['id'], original_data) + + expected_size = int( + math.ceil(float(fake.FLEXVOL_TO_MANAGE['size']) / units.Gi)) + self.assertEqual(expected_size, result) + + def test_manage_container_invalid_export_location(self): + + vserver_client = mock.Mock() + + share_to_manage = copy.deepcopy(fake.SHARE) + share_to_manage['export_location'] = fake.EXPORT_LOCATION + + mock_helper = mock.Mock() + mock_helper.get_share_name_for_share.return_value = None + self.mock_object(self.library, + '_get_helper', + mock.Mock(return_value=mock_helper)) + + self.assertRaises(exception.ManageInvalidShare, + self.library._manage_container, + share_to_manage, + vserver_client) + + def test_manage_container_not_found(self): + + vserver_client = mock.Mock() + + share_to_manage = copy.deepcopy(fake.SHARE) + share_to_manage['export_location'] = fake.EXPORT_LOCATION + + mock_helper = mock.Mock() + mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME + self.mock_object(self.library, + '_get_helper', + mock.Mock(return_value=mock_helper)) + + self.mock_object(vserver_client, + 'get_volume_to_manage', + mock.Mock(return_value=None)) + + self.assertRaises(exception.ManageInvalidShare, + self.library._manage_container, + share_to_manage, + vserver_client) + + def test_manage_container_invalid_extra_specs(self): + + vserver_client = mock.Mock() + + share_to_manage = copy.deepcopy(fake.SHARE) + share_to_manage['export_location'] = fake.EXPORT_LOCATION + + mock_helper = mock.Mock() + mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME + self.mock_object(self.library, + '_get_helper', + mock.Mock(return_value=mock_helper)) + + self.mock_object(vserver_client, + 'get_volume_to_manage', + mock.Mock(return_value=fake.FLEXVOL_TO_MANAGE)) + self.mock_object(self.library, '_validate_volume_for_manage') + self.mock_object(share_types, + 'get_extra_specs_from_share', + mock.Mock(return_value=fake.EXTRA_SPEC)) + self.mock_object(self.library, + '_check_extra_specs_validity', + mock.Mock(side_effect=exception.NetAppException)) + + self.assertRaises(exception.ManageExistingShareTypeMismatch, + self.library._manage_container, + share_to_manage, + vserver_client) + + def test_validate_volume_for_manage(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) + + result = self.library._validate_volume_for_manage( + fake.FLEXVOL_TO_MANAGE, vserver_client) + + self.assertIsNone(result) + + @ddt.data({ + 'attribute': 'type', + 'value': 'dp', + }, { + 'attribute': 'style', + 'value': 'infinitevol', + }) + @ddt.unpack + def test_validate_volume_for_manage_invalid_volume(self, attribute, value): + + flexvol_to_manage = copy.deepcopy(fake.FLEXVOL_TO_MANAGE) + flexvol_to_manage[attribute] = value + + 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) + + self.assertRaises(exception.ManageInvalidShare, + self.library._validate_volume_for_manage, + flexvol_to_manage, + vserver_client) + + def test_validate_volume_for_manage_luns_present(self): + + vserver_client = mock.Mock() + vserver_client.volume_has_luns = mock.Mock(return_value=True) + vserver_client.volume_has_junctioned_volumes = mock.Mock( + return_value=False) + + self.assertRaises(exception.ManageInvalidShare, + self.library._validate_volume_for_manage, + fake.FLEXVOL_TO_MANAGE, + vserver_client) + + def test_validate_volume_for_manage_junctioned_volumes_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=True) + + self.assertRaises(exception.ManageInvalidShare, + self.library._validate_volume_for_manage, + fake.FLEXVOL_TO_MANAGE, + vserver_client) + def test_extend_share(self): vserver_client = mock.Mock() diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py index abe2ec54e7..4b664f86f7 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py @@ -50,6 +50,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): kwargs = { 'configuration': fake.get_config_cmode(), + 'private_storage': mock.Mock(), 'app_version': fake.APP_VERSION } diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_single_svm.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_single_svm.py index fb432e4d1b..e683c0dcaa 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_single_svm.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_single_svm.py @@ -44,6 +44,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): kwargs = { 'configuration': config, + 'private_storage': mock.Mock(), 'app_version': fake.APP_VERSION } diff --git a/manila/tests/share/drivers/netapp/dataontap/fakes.py b/manila/tests/share/drivers/netapp/dataontap/fakes.py index 5d5c5af025..19c4877a36 100644 --- a/manila/tests/share/drivers/netapp/dataontap/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/fakes.py @@ -31,6 +31,9 @@ VOLUME_NAME_TEMPLATE = 'share_%(share_id)s' VSERVER_NAME_TEMPLATE = 'os_%s' AGGREGATE_NAME_SEARCH_PATTERN = '(.*)' SHARE_NAME = 'share_7cf7c200_d3af_4e05_b87e_9167c95dfcad' +FLEXVOL_NAME = 'fake_volume' +JUNCTION_PATH = '/%s' % FLEXVOL_NAME +EXPORT_LOCATION = '%s:%s' % (HOST_NAME, JUNCTION_PATH) SNAPSHOT_NAME = 'fake_snapshot' SHARE_SIZE = 10 TENANT_ID = '24cb2448-13d8-4f41-afd9-eff5c4fd2a57' @@ -73,7 +76,16 @@ SHARE = { 'share_server_id': '7e6a2cc8-871f-4b1d-8364-5aad0f98da86', 'network_info': { 'network_allocations': [{'ip_address': 'ip'}] - } + }, +} + +FLEXVOL_TO_MANAGE = { + 'aggregate': POOL_NAME, + 'junction-path': '/%s' % FLEXVOL_NAME, + 'name': FLEXVOL_NAME, + 'type': 'rw', + 'style': 'flex', + 'size': '1610612736', # rounds down to 1 GB } EXTRA_SPEC = { @@ -83,6 +95,8 @@ EXTRA_SPEC = { 'netapp:dedup': 'True', 'netapp:compression': 'false', 'netapp:max_files': 5000, + 'netapp_disk_type': 'FCAL', + 'netapp_raid_type': 'raid4', } PROVISIONING_OPTIONS = { diff --git a/manila/tests/share/drivers/netapp/dataontap/protocols/fakes.py b/manila/tests/share/drivers/netapp/dataontap/protocols/fakes.py index 708eaeaac5..be18b56150 100644 --- a/manila/tests/share/drivers/netapp/dataontap/protocols/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/protocols/fakes.py @@ -45,3 +45,7 @@ USER_ACCESS = { 'access_to': 'fake_user', 'access_level': constants.ACCESS_LEVEL_RW, } + +VOLUME = { + 'name': SHARE_NAME, +} diff --git a/manila/tests/share/drivers/netapp/dataontap/protocols/test_cifs_cmode.py b/manila/tests/share/drivers/netapp/dataontap/protocols/test_cifs_cmode.py index 442ce9fe9b..84eadba298 100644 --- a/manila/tests/share/drivers/netapp/dataontap/protocols/test_cifs_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/protocols/test_cifs_cmode.py @@ -185,6 +185,12 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase): target = self.helper.get_target({'export_location': ''}) self.assertEqual('', target) + def test_get_share_name_for_share(self): + + share_name = self.helper.get_share_name_for_share(fake.CIFS_SHARE) + + self.assertEqual(fake.SHARE_NAME, share_name) + @ddt.data( { 'location': r'\\%s\%s' % (fake.SHARE_ADDRESS_1, fake.SHARE_NAME), diff --git a/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py b/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py index 66459776de..c13508b571 100644 --- a/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py @@ -172,6 +172,27 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase): target = self.helper.get_target(fake.NFS_SHARE) self.assertEqual(fake.SHARE_ADDRESS_1, target) + def test_get_share_name_for_share(self): + + self.mock_client.get_volume_at_junction_path.return_value = ( + fake.VOLUME) + + share_name = self.helper.get_share_name_for_share(fake.NFS_SHARE) + + self.assertEqual(fake.SHARE_NAME, share_name) + self.mock_client.get_volume_at_junction_path.assert_called_once_with( + fake.NFS_SHARE_PATH) + + def test_get_share_name_for_share_not_found(self): + + self.mock_client.get_volume_at_junction_path.return_value = None + + share_name = self.helper.get_share_name_for_share(fake.NFS_SHARE) + + self.assertIsNone(share_name) + self.mock_client.get_volume_at_junction_path.assert_called_once_with( + fake.NFS_SHARE_PATH) + def test_get_target_missing_location(self): target = self.helper.get_target({'export_location': ''}) diff --git a/manila/tests/share/drivers/netapp/test_common.py b/manila/tests/share/drivers/netapp/test_common.py index 5cfe78c465..ce250e4f69 100644 --- a/manila/tests/share/drivers/netapp/test_common.py +++ b/manila/tests/share/drivers/netapp/test_common.py @@ -121,7 +121,11 @@ class NetAppDriverFactoryTestCase(test.TestCase): config = na_fakes.create_configuration() config.local_conf.set_override('driver_handles_share_servers', mode == na_common.MULTI_SVM) - kwargs = {'configuration': config, 'app_version': 'fake_info'} + kwargs = { + 'configuration': config, + 'private_storage': mock.Mock(), + 'app_version': 'fake_info' + } driver = na_common.NetAppDriver._create_driver( family, mode, **kwargs) @@ -133,7 +137,11 @@ class NetAppDriverFactoryTestCase(test.TestCase): config = na_fakes.create_configuration() config.local_conf.set_override('driver_handles_share_servers', True) - kwargs = {'configuration': config, 'app_version': 'fake_info'} + kwargs = { + 'configuration': config, + 'private_storage': mock.Mock(), + 'app_version': 'fake_info' + } driver = na_common.NetAppDriver._create_driver('ONTAP_CLUSTER', na_common.MULTI_SVM,