diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 6f0db79c3b..9d1e5c1504 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -916,6 +916,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.""" @@ -925,6 +931,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.""" @@ -976,6 +1021,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.""" @@ -1039,6 +1156,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): @@ -1067,6 +1337,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 b4730d8084..bce85b2c6e 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 @@ -70,6 +70,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 418d9d48db..c9531e0c97 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 @@ -70,6 +70,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 fb06418ed9..cdb70f4aeb 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 14e016c693..8f4b8e6a9e 100644 --- a/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py +++ b/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py @@ -88,6 +88,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 5844576bb7..e6f7956d50 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 @@ -1668,6 +1668,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') @@ -1694,30 +1784,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): @@ -1842,6 +2056,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') @@ -1910,6 +2345,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 c5ca8415fa..7942136d64 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 96c68fa771..705d1a9c13 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 @@ -51,6 +51,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 b58b5a29b8..26fb731b18 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 @@ -189,6 +189,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 a1f2cc6986..8cf0cf0f77 100644 --- a/manila/tests/share/drivers/netapp/test_common.py +++ b/manila/tests/share/drivers/netapp/test_common.py @@ -124,7 +124,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) @@ -136,7 +140,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,