From dc2aa75e7cce09a6a3169063168b9c9d998511e2 Mon Sep 17 00:00:00 2001 From: Clinton Knight Date: Mon, 26 Jan 2015 16:30:31 -0500 Subject: [PATCH] Support Manila pools in NetApp Clustered Data ONTAP driver The Cinder pools feature is being ported to Manila, and drivers must be updated to report pools in order to use this feature. This patch adds support for pools to the NetApp Clustered Data ONTAP driver for Manila. Implements blueprint support-pools-in-netapp-cdot-driver Change-Id: Iba327a745227e6219e6e1fc77af407f097905d8f --- manila/exception.py | 4 + .../netapp/dataontap/client/client_cmode.py | 102 ++++++++++++----- .../dataontap/cluster_mode/drv_multi_svm.py | 3 + .../netapp/dataontap/cluster_mode/lib_base.py | 65 ++++++++--- manila/share/drivers/netapp/utils.py | 53 +++++---- .../drivers/netapp/dataontap/client/fakes.py | 33 +++++- .../dataontap/client/test_client_cmode.py | 105 ++++++++++++++---- .../dataontap/cluster_mode/test_lib_base.py | 80 +++++++++---- .../share/drivers/netapp/dataontap/fakes.py | 17 ++- .../dataontap/protocols/test_cifs_cmode.py | 20 ++-- .../dataontap/protocols/test_nfs_cmode.py | 8 +- .../tests/share/drivers/netapp/test_utils.py | 8 +- 12 files changed, 374 insertions(+), 124 deletions(-) diff --git a/manila/exception.py b/manila/exception.py index d33cb97b3c..6f57f2e195 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -149,6 +149,10 @@ class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") +class InvalidHost(Invalid): + message = _("Invalid host: %(reason)s") + + # Cannot be templated as the error syntax varies. # msg needs to be constructed when raised. class InvalidParameterValue(Invalid): diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 8ecb9c4e32..2c720388ac 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -127,8 +127,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): try: root_volume_name = vserver_info.get_child_by_name( - 'attributes-list').get_child_by_name('vserver-info')\ - .get_child_content('root-volume') + 'attributes-list').get_child_by_name( + 'vserver-info').get_child_content('root-volume') except AttributeError: msg = _('Could not determine root volume name ' 'for Vserver %s.') % vserver_name @@ -264,9 +264,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): } port_info = self.send_request('net-port-get-iter', api_args) try: - port = port_info.get_child_by_name('attributes-list')\ - .get_child_by_name('net-port-info')\ - .get_child_content('port') + port = port_info.get_child_by_name( + 'attributes-list').get_child_by_name( + 'net-port-info').get_child_content('port') except AttributeError: msg = _("Data port does not exist for node %s.") raise exception.NetAppException(msg % node) @@ -414,35 +414,48 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): self.send_request('net-interface-delete', api_args) @na_utils.trace - def calculate_aggregate_capacity(self, aggregate_names): - """Calculates capacity of one or more aggregates + def get_cluster_aggregate_capacities(self, aggregate_names): + """Calculates capacity of one or more aggregates. - Returns tuple (total, free) in bytes. + Returns dictionary of aggregate capacity metrics. + 'size-used' is the actual space consumed on the aggregate. + 'size-available' is the actual space remaining. + 'size-total' is the defined total aggregate size, such that + used + available = total. """ desired_attributes = { 'aggr-attributes': { 'aggregate-name': None, 'aggr-space-attributes': { - 'size-total': None, 'size-available': None, + 'size-total': None, + 'size-used': None, }, }, } aggrs = self._get_aggregates(aggregate_names=aggregate_names, desired_attributes=desired_attributes) - aggr_space_attrs = [aggr.get_child_by_name('aggr-space-attributes') - for aggr in aggrs] - total = sum([int(aggr.get_child_content('size-total')) - for aggr in aggr_space_attrs]) if aggr_space_attrs else 0 - free = max([int(aggr.get_child_content('size-available')) - for aggr in aggr_space_attrs]) if aggr_space_attrs else 0 - return total, free + aggr_space_dict = dict() + for aggr in aggrs: + aggr_name = aggr.get_child_content('aggregate-name') + aggr_space_attrs = aggr.get_child_by_name('aggr-space-attributes') + + aggr_space_dict[aggr_name] = { + 'available': + int(aggr_space_attrs.get_child_content('size-available')), + 'total': + int(aggr_space_attrs.get_child_content('size-total')), + 'used': + int(aggr_space_attrs.get_child_content('size-used')), + } + return aggr_space_dict @na_utils.trace - def get_aggregates_for_vserver(self, vserver_name): - """Returns aggregate list and size info for a Vserver. + def get_vserver_aggregate_capacities(self, vserver_name): + """Calculates capacity of one or more aggregates for a vserver. - Must be called against a Vserver API client. + Returns dictionary of aggregate capacity metrics. This must + be called against a Vserver LIF. """ LOG.debug('Finding available aggregates for Vserver %s', vserver_name) @@ -466,19 +479,19 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): vserver_aggr_info_list = vserver_aggr_info_element.get_children() if not vserver_aggr_info_list: - msg = _("No aggregates assigned to Vserver %s") - raise exception.NetAppException(msg % vserver_name) + LOG.warning(_LW('No aggregates assigned to Vserver %s.'), + vserver_name) # Return dict of key-value pair of aggr_name:aggr_size_available. - aggr_dict = {} + aggr_space_dict = {} for aggr_info in vserver_aggr_info_list: aggr_name = aggr_info.get_child_content('aggr-name') aggr_size = int(aggr_info.get_child_content('aggr-availsize')) - aggr_dict[aggr_name] = aggr_size + aggr_space_dict[aggr_name] = {'available': aggr_size} - LOG.debug('Found available aggregates: %s', aggr_dict) - return aggr_dict + LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict) + return aggr_space_dict def _get_aggregates(self, aggregate_names=None, desired_attributes=None): @@ -701,6 +714,45 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): result = self.send_request('volume-get-iter', api_args) return self._has_records(result) + @na_utils.trace + def get_aggregate_for_volume(self, volume_name): + """Get the name of the aggregate containing a volume.""" + + api_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'name': volume_name, + }, + }, + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': None, + 'name': None, + }, + }, + }, + } + result = self.send_request('volume-get-iter', api_args) + + attributes_list = result.get_child_by_name( + 'attributes-list') or netapp_api.NaElement('none') + volume_attributes = 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') + + aggregate = volume_id_attributes.get_child_content( + 'containing-aggregate-name') + + if not aggregate: + msg = _('Could not find aggregate for volume %s.') + raise exception.NetAppException(msg % volume_name) + + return aggregate + @na_utils.trace def create_volume_clone(self, volume_name, parent_volume_name, parent_snapshot_name=None): 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 2c068d32b7..f8f554bef1 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 @@ -40,6 +40,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver): def check_for_setup_error(self): self.library.check_for_setup_error() + def get_pool(self, share): + return self.library.get_pool(share) + def create_share(self, context, share, **kwargs): return self.library.create_share(context, share, **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 3f46a7f696..e375b74230 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py @@ -35,6 +35,7 @@ from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode from manila.share.drivers.netapp import options as na_opts from manila.share.drivers.netapp import utils as na_utils +from manila.share import utils as share_utils from manila import utils @@ -73,7 +74,7 @@ class NetAppCmodeFileStorageLibrary(object): AUTOSUPPORT_INTERVAL_SECONDS = 3600 # hourly def __init__(self, db, driver_name, **kwargs): - na_utils.validate_instantiation(**kwargs) + na_utils.validate_driver_instantiation(**kwargs) self.db = db self.driver_name = driver_name @@ -157,17 +158,39 @@ class NetAppCmodeFileStorageLibrary(object): @na_utils.trace def get_share_stats(self): """Retrieve stats info from Cluster Mode backend.""" - total, free = self._client.calculate_aggregate_capacity( + aggr_space = self._client.get_cluster_aggregate_capacities( self._find_matching_aggregates()) - data = dict( - share_backend_name=self.backend_name, - driver_name=self.driver_name, - vendor_name='NetApp', - driver_version='1.0', - storage_protocol='NFS_CIFS', - total_capacity_gb=(total / units.Gi), - free_capacity_gb=(free / units.Gi)) + data = { + 'share_backend_name': self.backend_name, + 'driver_name': self.driver_name, + 'vendor_name': 'NetApp', + 'driver_version': '1.0', + 'storage_protocol': 'NFS_CIFS', + 'total_capacity_gb': 0.0, + 'free_capacity_gb': 0.0, + } + + pools = [] + for aggr_name in sorted(aggr_space.keys()): + + total_capacity_gb = na_utils.round_down( + float(aggr_space[aggr_name]['total']) / units.Gi, '0.01') + free_capacity_gb = na_utils.round_down( + float(aggr_space[aggr_name]['available']) / units.Gi, '0.01') + allocated_capacity_gb = na_utils.round_down( + float(aggr_space[aggr_name]['used']) / units.Gi, '0.01') + + pools.append({ + 'pool_name': aggr_name, + 'total_capacity_gb': total_capacity_gb, + 'free_capacity_gb': free_capacity_gb, + 'allocated_capacity_gb': allocated_capacity_gb, + 'QoS_support': 'False', + 'reserved_percentage': 0, + }) + + data['pools'] = pools self._handle_ems_logging() @@ -317,6 +340,15 @@ class NetAppCmodeFileStorageLibrary(object): ip, netmask, vlan, node, port, vserver_name, allocation_id, self.configuration.netapp_lif_name_template) + @na_utils.trace + def get_pool(self, share): + pool = share_utils.extract_host(share['host'], level='pool') + if pool: + return pool + + volume_name = self._get_valid_share_name(share['id']) + return self._client.get_aggregate_for_volume(volume_name) + @ensure_vserver @na_utils.trace def create_share(self, context, share, share_server): @@ -340,12 +372,17 @@ class NetAppCmodeFileStorageLibrary(object): def _allocate_container(self, share, vserver, vserver_client): """Create new share on aggregate.""" share_name = self._get_valid_share_name(share['id']) - aggregates = vserver_client.get_aggregates_for_vserver(vserver) - aggregate = max(aggregates, key=lambda m: aggregates[m]) + + # Get Data ONTAP aggregate name as pool name. + aggregate_name = share_utils.extract_host(share['host'], level='pool') + + if aggregate_name is None: + msg = _("Pool is not available in the share host field.") + raise exception.InvalidHost(reason=msg) LOG.debug('Creating volume %(share_name)s on aggregate %(aggregate)s', - {'share_name': share_name, 'aggregate': aggregate}) - vserver_client.create_volume(aggregate, share_name, share['size']) + {'share_name': share_name, 'aggregate': aggregate_name}) + vserver_client.create_volume(aggregate_name, share_name, share['size']) @na_utils.trace def _allocate_container_from_snapshot(self, share, snapshot, diff --git a/manila/share/drivers/netapp/utils.py b/manila/share/drivers/netapp/utils.py index 3faf22b09a..8acd3236be 100644 --- a/manila/share/drivers/netapp/utils.py +++ b/manila/share/drivers/netapp/utils.py @@ -15,10 +15,12 @@ # under the License. """Utilities for NetApp drivers.""" +import decimal import platform from oslo_concurrency import processutils as putils from oslo_log import log +import six from manila import exception from manila.i18n import _, _LI, _LW @@ -32,6 +34,36 @@ TRACE_METHOD = False TRACE_API = False +def check_flags(required_flags, configuration): + """Ensure that the flags we care about are set.""" + for flag in required_flags: + if not getattr(configuration, flag, None): + msg = _('Configuration value %s is not set.') % flag + raise exception.InvalidInput(reason=msg) + + +def validate_driver_instantiation(**kwargs): + """Checks if a driver is instantiated other than by the unified driver. + + Helps check direct instantiation of netapp drivers. + Call this function in every netapp block driver constructor. + """ + if kwargs and kwargs.get('netapp_mode') == 'proxy': + return + LOG.warning(_LW('Please use NetAppDriver in the configuration file ' + 'to load the driver instead of directly specifying ' + 'the driver module name.')) + + +def round_down(value, precision): + """Round a number downward using a specified level of precision. + + Example: round_down(float(total_space_in_bytes) / units.Gi, '0.01') + """ + return float(decimal.Decimal(six.text_type(value)).quantize( + decimal.Decimal(precision), rounding=decimal.ROUND_DOWN)) + + def setup_tracing(trace_flags_string): global TRACE_METHOD global TRACE_API @@ -57,27 +89,6 @@ def trace(f): return trace_wrapper -def check_flags(required_flags, configuration): - """Ensure that the flags we care about are set.""" - for flag in required_flags: - if not getattr(configuration, flag, None): - msg = _('Configuration value %s is not set.') % flag - raise exception.InvalidInput(reason=msg) - - -def validate_instantiation(**kwargs): - """Checks if a driver is instantiated other than by the unified driver. - - Helps check direct instantiation of netapp drivers. - Call this function in every netapp block driver constructor. - """ - if kwargs and kwargs.get('netapp_mode') == 'proxy': - return - LOG.warning(_LW('Please use NetAppDriver in the configuration file ' - 'to load the driver instead of directly specifying ' - 'the driver module name.')) - - class OpenStackInfo(object): """OS/distribution, release, and version. diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py index 19287e25a5..ac11a52f98 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py @@ -123,7 +123,14 @@ VSERVER_DATA_LIST_RESPONSE = etree.XML(""" """ % {'vserver': VSERVER_NAME}) -VSERVER_AGGREGATES = {'aggr0': 45678592, 'manila': 6448431104} +VSERVER_AGGREGATES = { + 'aggr0': { + 'available': 45678592, + }, + 'manila': { + 'available': 6448431104, + }, +} VSERVER_GET_RESPONSE_NO_AGGREGATES = etree.XML(""" @@ -577,8 +584,9 @@ AGGR_GET_SPACE_RESPONSE = etree.XML(""" - 45678592 + 45670400 943718400 + 898048000 aggr0 @@ -599,8 +607,9 @@ AGGR_GET_SPACE_RESPONSE = etree.XML(""" - 6448435200 + 4267659264 7549747200 + 3282087936 manila @@ -965,3 +974,21 @@ NFS_EXPORTFS_LIST_RULES_2_RESPONSE = etree.XML(""" 'host1': NFS_EXPORT_RULES[0], 'host2': NFS_EXPORT_RULES[1], }) + +GET_AGGREGATE_FOR_VOLUME_RESPONSE = etree.XML(""" + + + + + %(aggr)s + %(share)s + os_aa666789-5576-4835-87b7-868069856459 + + + + 1 + +""" % { + 'aggr': SHARE_AGGREGATE_NAME, + 'share': 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 02365211d6..dffdc6243b 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 @@ -81,11 +81,11 @@ class NetAppClientCmodeTestCase(test.TestCase): 'desired-attributes': {'vserver-info': {'vserver-name': None}} } - response = self.client.vserver_exists(fake.VSERVER_NAME) + result = self.client.vserver_exists(fake.VSERVER_NAME) self.client.send_request.assert_has_calls([ mock.call('vserver-get-iter', vserver_get_args)]) - self.assertTrue(response) + self.assertTrue(result) def test_vserver_exists_not_found(self): @@ -137,11 +137,11 @@ class NetAppClientCmodeTestCase(test.TestCase): 'desired-attributes': {'vserver-info': {'root-volume': None}} } - response = self.client.get_vserver_root_volume_name(fake.VSERVER_NAME) + result = self.client.get_vserver_root_volume_name(fake.VSERVER_NAME) self.client.send_request.assert_has_calls([ mock.call('vserver-get-iter', vserver_get_args)]) - self.assertEqual(fake.ROOT_VOLUME_NAME, response) + self.assertEqual(fake.ROOT_VOLUME_NAME, result) def test_get_vserver_root_volume_name_not_found(self): @@ -587,13 +587,13 @@ class NetAppClientCmodeTestCase(test.TestCase): } } } - response = self.client.network_interface_exists( + result = self.client.network_interface_exists( fake.VSERVER_NAME, fake.NODE_NAME, fake.PORT, fake.IP_ADDRESS, fake.NETMASK, fake.VLAN) self.client.send_request.assert_has_calls([ mock.call('net-interface-get-iter', net_interface_get_args)]) - self.assertTrue(response) + self.assertTrue(result) def test_network_interface_exists_not_found(self): @@ -602,11 +602,11 @@ class NetAppClientCmodeTestCase(test.TestCase): 'send_request', mock.Mock(return_value=api_response)) - response = self.client.network_interface_exists( + result = self.client.network_interface_exists( fake.VSERVER_NAME, fake.NODE_NAME, fake.PORT, fake.IP_ADDRESS, fake.NETMASK, fake.VLAN) - self.assertFalse(response) + self.assertFalse(result) def test_list_network_interfaces(self): @@ -669,7 +669,7 @@ class NetAppClientCmodeTestCase(test.TestCase): self.client.send_request.assert_has_calls([ mock.call('net-interface-delete', net_interface_get_args)]) - def test_calculate_aggregate_capacity(self): + def test_get_cluster_aggregate_capacities(self): api_response = netapp_api.NaElement( fake.AGGR_GET_SPACE_RESPONSE).get_child_by_name( @@ -678,14 +678,15 @@ class NetAppClientCmodeTestCase(test.TestCase): '_get_aggregates', mock.Mock(return_value=api_response)) - result = self.client.calculate_aggregate_capacity(fake.AGGR_NAMES) + result = self.client.get_cluster_aggregate_capacities(fake.AGGR_NAMES) desired_attributes = { 'aggr-attributes': { 'aggregate-name': None, 'aggr-space-attributes': { - 'size-total': None, 'size-available': None, + 'size-total': None, + 'size-used': None, } } } @@ -694,27 +695,40 @@ class NetAppClientCmodeTestCase(test.TestCase): mock.call( aggregate_names=fake.AGGR_NAMES, desired_attributes=desired_attributes)]) - self.assertEqual((8493465600, 6448435200), result) - def test_calculate_aggregate_capacity_not_found(self): + expected = { + 'manila': { + 'available': 4267659264, + 'total': 7549747200, + 'used': 3282087936, + }, + 'aggr0': { + 'available': 45670400, + 'total': 943718400, + 'used': 898048000, + } + } + self.assertDictEqual(expected, result) + + def test_get_cluster_aggregate_capacities_not_found(self): api_response = netapp_api.NaElement('none').get_children() self.mock_object(self.client, '_get_aggregates', mock.Mock(return_value=api_response)) - result = self.client.calculate_aggregate_capacity(fake.AGGR_NAMES) + result = self.client.get_cluster_aggregate_capacities(fake.AGGR_NAMES) - self.assertEqual((0, 0), result) + self.assertEqual({}, result) - def test_get_aggregates_for_vserver(self): + def test_get_vserver_aggregate_capacities(self): api_response = netapp_api.NaElement(fake.VSERVER_GET_RESPONSE) self.mock_object(self.vserver_client, 'send_request', mock.Mock(return_value=api_response)) - result = self.vserver_client.get_aggregates_for_vserver( + result = self.vserver_client.get_vserver_aggregate_capacities( fake.VSERVER_NAME) vserver_args = { @@ -734,7 +748,7 @@ class NetAppClientCmodeTestCase(test.TestCase): mock.call('vserver-get', vserver_args)]) self.assertDictEqual(fake.VSERVER_AGGREGATES, result) - def test_get_aggregates_for_vserver_not_found(self): + def test_get_vserver_aggregate_capacities_not_found(self): api_response = netapp_api.NaElement( fake.VSERVER_GET_RESPONSE_NO_AGGREGATES) @@ -742,9 +756,11 @@ class NetAppClientCmodeTestCase(test.TestCase): 'send_request', mock.Mock(return_value=api_response)) - self.assertRaises(exception.NetAppException, - self.vserver_client.get_aggregates_for_vserver, - fake.VSERVER_NAME) + result = self.vserver_client.get_vserver_aggregate_capacities( + fake.VSERVER_NAME) + + self.assertDictEqual({}, result) + self.assertEqual(1, client_cmode.LOG.warning.call_count) def test_get_aggregates(self): @@ -1181,7 +1197,7 @@ class NetAppClientCmodeTestCase(test.TestCase): 'send_request', mock.Mock(return_value=api_response)) - response = self.client.volume_exists(fake.SHARE_NAME) + result = self.client.volume_exists(fake.SHARE_NAME) volume_get_iter_args = { 'query': { @@ -1202,7 +1218,7 @@ class NetAppClientCmodeTestCase(test.TestCase): self.client.send_request.assert_has_calls([ mock.call('volume-get-iter', volume_get_iter_args)]) - self.assertTrue(response) + self.assertTrue(result) def test_volume_exists_not_found(self): @@ -1213,6 +1229,49 @@ class NetAppClientCmodeTestCase(test.TestCase): self.assertFalse(self.client.volume_exists(fake.SHARE_NAME)) + def test_get_aggregate_for_volume(self): + + api_response = netapp_api.NaElement( + fake.GET_AGGREGATE_FOR_VOLUME_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.get_aggregate_for_volume(fake.SHARE_NAME) + + volume_get_iter_args = { + 'query': { + 'volume-attributes': { + 'volume-id-attributes': { + 'name': fake.SHARE_NAME + } + } + }, + 'desired-attributes': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': None, + 'name': None + } + } + } + } + + self.client.send_request.assert_has_calls([ + mock.call('volume-get-iter', volume_get_iter_args)]) + self.assertEqual(fake.SHARE_AGGREGATE_NAME, result) + + def test_get_aggregate_for_volume_not_found(self): + + api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + self.assertRaises(exception.NetAppException, + self.client.get_aggregate_for_volume, + fake.SHARE_NAME) + def test_create_volume_clone(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 50b711af52..9d0314ec0a 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 @@ -21,7 +21,6 @@ import socket import mock from oslo_utils import timeutils -from oslo_utils import units from manila import context from manila import exception @@ -99,7 +98,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): def setUp(self): super(NetAppFileStorageLibraryTestCase, self).setUp() - self.mock_object(na_utils, 'validate_instantiation') + self.mock_object(na_utils, 'validate_driver_instantiation') self.mock_object(na_utils, 'setup_tracing') self.mock_object(lib_base, 'LOG') @@ -126,8 +125,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): config.netapp_server_port = fake.CLIENT_KWARGS['port'] config.netapp_vserver = fake.VSERVER1 config.netapp_volume_name_template = fake.VOLUME_NAME_TEMPLATE - config.netapp_aggregate_name_search_pattern = \ - fake.AGGREGATE_NAME_SEARCH_PATTERN + config.netapp_aggregate_name_search_pattern = ( + fake.AGGREGATE_NAME_SEARCH_PATTERN) config.netapp_vserver_name_template = fake.VSERVER_NAME_TEMPLATE config.netapp_root_volume_aggregate = fake.ROOT_VOLUME_AGGREGATE config.netapp_root_volume = fake.ROOT_VOLUME @@ -137,7 +136,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): def test_init(self): self.assertEqual(fake.DRIVER_NAME, self.library.driver_name) self.assertEqual(self.mock_db, self.library.db) - self.assertEqual(1, na_utils.validate_instantiation.call_count) + self.assertEqual(1, na_utils.validate_driver_instantiation.call_count) self.assertEqual(1, na_utils.setup_tracing.call_count) self.assertIsNone(self.library._helpers) self.assertListEqual([], self.library._licenses) @@ -263,9 +262,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.mock_object(self.library, '_find_matching_aggregates') self.mock_object(self.client, - 'calculate_aggregate_capacity', - mock.Mock(return_value=(fake.TOTAL_CAPACITY, - fake.FREE_CAPACITY))) + 'get_cluster_aggregate_capacities', + mock.Mock(return_value=fake.AGGREGATE_CAPACITIES)) mock_handle_ems_logging = self.mock_object(self.library, '_handle_ems_logging') @@ -277,10 +275,27 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): 'vendor_name': 'NetApp', 'driver_version': '1.0', 'storage_protocol': 'NFS_CIFS', - 'total_capacity_gb': fake.TOTAL_CAPACITY / units.Gi, - 'free_capacity_gb': fake.FREE_CAPACITY / units.Gi + 'total_capacity_gb': 0.0, + 'free_capacity_gb': 0.0, + 'pools': [ + {'pool_name': 'manila1', + 'total_capacity_gb': 3.3, + 'free_capacity_gb': 1.1, + 'allocated_capacity_gb': 2.2, + 'QoS_support': 'False', + 'reserved_percentage': 0, + }, + {'pool_name': 'manila2', + 'total_capacity_gb': 6.0, + 'free_capacity_gb': 2.0, + 'allocated_capacity_gb': 4.0, + 'QoS_support': 'False', + 'reserved_percentage': 0, + }, + ] } + self.maxDiff = None self.assertDictEqual(expected, result) self.assertTrue(mock_handle_ems_logging.called) @@ -339,13 +354,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): 'list_aggregates', mock.Mock(return_value=fake.AGGREGATES)) - self.library.configuration.netapp_aggregate_name_search_pattern =\ - fake.AGGREGATE_NAME_SEARCH_PATTERN + self.library.configuration.netapp_aggregate_name_search_pattern = ( + fake.AGGREGATE_NAME_SEARCH_PATTERN) result = self.library._find_matching_aggregates() self.assertListEqual(result, fake.AGGREGATES) - self.library.configuration.netapp_aggregate_name_search_pattern =\ - 'aggr.*' + self.library.configuration.netapp_aggregate_name_search_pattern = ( + 'aggr.*') result = self.library._find_matching_aggregates() self.assertListEqual(result, ['aggr0']) @@ -618,6 +633,35 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.assertFalse(self.library._client.create_network_interface.called) + def test_get_pool_has_pool(self): + result = self.library.get_pool(fake.SHARE) + self.assertEqual(fake.POOL_NAME, result) + self.assertFalse(self.client.get_aggregate_for_volume.called) + + def test_get_pool_no_pool(self): + + fake_share = copy.deepcopy(fake.SHARE) + fake_share['host'] = '%(host)s@%(backend)s' % { + 'host': fake.HOST_NAME, 'backend': fake.BACKEND_NAME} + self.client.get_aggregate_for_volume.return_value = fake.POOL_NAME + + result = self.library.get_pool(fake_share) + + self.assertEqual(fake.POOL_NAME, result) + self.assertTrue(self.client.get_aggregate_for_volume.called) + + def test_get_pool_raises(self): + + fake_share = copy.deepcopy(fake.SHARE) + fake_share['host'] = '%(host)s@%(backend)s' % { + 'host': fake.HOST_NAME, 'backend': fake.BACKEND_NAME} + self.client.get_aggregate_for_volume.side_effect = ( + exception.NetAppException) + + self.assertRaises(exception.NetAppException, + self.library.get_pool, + fake_share) + def test_create_share(self): vserver_client = mock.Mock() @@ -674,16 +718,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): def test_allocate_container(self): - aggregates = {'aggr0': 10000000000, 'aggr1': 20000000000} vserver_client = mock.Mock() - vserver_client.get_aggregates_for_vserver.return_value = aggregates self.library._allocate_container(fake.SHARE, fake.VSERVER1, vserver_client) share_name = self.library._get_valid_share_name(fake.SHARE['id']) - vserver_client.create_volume.assert_called_with('aggr1', + vserver_client.create_volume.assert_called_with(fake.POOL_NAME, share_name, fake.SHARE['size']) @@ -938,8 +980,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): def test_get_network_allocations_number(self): - self.library._client.list_cluster_nodes.return_value = \ - fake.CLUSTER_NODES + self.library._client.list_cluster_nodes.return_value = ( + fake.CLUSTER_NODES) result = self.library.get_network_allocations_number() diff --git a/manila/tests/share/drivers/netapp/dataontap/fakes.py b/manila/tests/share/drivers/netapp/dataontap/fakes.py index 1b7bcf0aad..cf727e0b1b 100644 --- a/manila/tests/share/drivers/netapp/dataontap/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/fakes.py @@ -12,11 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. - BACKEND_NAME = 'fake_backend_name' DRIVER_NAME = 'fake_driver_name' APP_VERSION = 'fake_app_vsersion' HOST_NAME = 'fake_host' +POOL_NAME = 'fake_pool' VSERVER1 = 'fake_vserver_1' VSERVER2 = 'fake_vserver_2' LICENSES = ['base', 'cifs', 'fcp', 'flexclone', 'iscsi', 'nfs', 'snapmirror', @@ -51,6 +51,8 @@ CLIENT_KWARGS = { SHARE = { 'id': SHARE_ID, + 'host': '%(host)s@%(backend)s#%(pool)s' % { + 'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME}, 'project_id': TENANT_ID, 'name': SHARE_NAME, 'size': SHARE_SIZE, @@ -124,3 +126,16 @@ EMS_MESSAGE = { 'log-level': '6', 'auto-support': 'false' } + +AGGREGATE_CAPACITIES = { + 'manila1': { + 'available': 1181116007, # 1.1 GB + 'total': 3543348020, # 3.3 GB + 'used': 2362232013, # 2.2 GB + }, + 'manila2': { + 'available': 2147483648, # 2.0 GB + 'total': 6442450944, # 6.0 GB + 'used': 4294967296, # 4.0 GB + } +} 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 51f3044d51..443024e9ab 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 @@ -65,8 +65,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase): def test_allow_access_preexisting(self): - self.mock_client.add_cifs_share_access.side_effect = \ - netapp_api.NaApiError(code=netapp_api.EDUPLICATEENTRY) + self.mock_client.add_cifs_share_access.side_effect = ( + netapp_api.NaApiError(code=netapp_api.EDUPLICATEENTRY)) self.assertRaises(exception.ShareAccessExists, self.helper.allow_access, @@ -76,8 +76,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase): def test_allow_access_api_error(self): - self.mock_client.add_cifs_share_access.side_effect = \ - netapp_api.NaApiError() + self.mock_client.add_cifs_share_access.side_effect = ( + netapp_api.NaApiError()) self.assertRaises(netapp_api.NaApiError, self.helper.allow_access, @@ -105,8 +105,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase): def test_deny_access_nonexistent_user(self): - self.mock_client.remove_cifs_share_access.side_effect = \ - netapp_api.NaApiError(code=netapp_api.EONTAPI_EINVAL) + self.mock_client.remove_cifs_share_access.side_effect = ( + netapp_api.NaApiError(code=netapp_api.EONTAPI_EINVAL)) self.helper.deny_access(self.mock_context, fake.CIFS_SHARE, fake.ACCESS) @@ -117,8 +117,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase): def test_deny_access_nonexistent_rule(self): - self.mock_client.remove_cifs_share_access.side_effect = \ - netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND) + self.mock_client.remove_cifs_share_access.side_effect = ( + netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND)) self.helper.deny_access(self.mock_context, fake.CIFS_SHARE, fake.ACCESS) @@ -129,8 +129,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase): def test_deny_access_api_error(self): - self.mock_client.remove_cifs_share_access.side_effect = \ - netapp_api.NaApiError() + self.mock_client.remove_cifs_share_access.side_effect = ( + netapp_api.NaApiError()) self.assertRaises(netapp_api.NaApiError, self.helper.deny_access, 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 dd6b0038ed..62a888fdf7 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 @@ -40,8 +40,8 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase): def test_create_share(self): - self.mock_client.get_volume_junction_path.return_value = \ - fake.NFS_SHARE_PATH + self.mock_client.get_volume_junction_path.return_value = ( + fake.NFS_SHARE_PATH) result = self.helper.create_share(fake.SHARE_NAME, fake.SHARE_ADDRESS) @@ -145,8 +145,8 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase): def test_get_existing_rules(self): - self.mock_client.get_nfs_export_rules.return_value = \ - fake.NFS_ACCESS_HOSTS + self.mock_client.get_nfs_export_rules.return_value = ( + fake.NFS_ACCESS_HOSTS) result = self.helper._get_existing_rules(fake.NFS_SHARE) diff --git a/manila/tests/share/drivers/netapp/test_utils.py b/manila/tests/share/drivers/netapp/test_utils.py index ae01e5d47d..fd2da38391 100644 --- a/manila/tests/share/drivers/netapp/test_utils.py +++ b/manila/tests/share/drivers/netapp/test_utils.py @@ -84,18 +84,18 @@ class NetAppDriverUtilsTestCase(test.TestCase): self.assertEqual('OK', result) self.assertEqual(2, na_utils.LOG.debug.call_count) - def test_validate_instantiation_proxy(self): + def test_validate_driver_instantiation_proxy(self): kwargs = {'netapp_mode': 'proxy'} - na_utils.validate_instantiation(**kwargs) + na_utils.validate_driver_instantiation(**kwargs) self.assertEqual(0, na_utils.LOG.warning.call_count) - def test_validate_instantiation_no_proxy(self): + def test_validate_driver_instantiation_no_proxy(self): self.mock_object(na_utils, 'LOG') kwargs = {'netapp_mode': 'asdf'} - na_utils.validate_instantiation(**kwargs) + na_utils.validate_driver_instantiation(**kwargs) self.assertEqual(1, na_utils.LOG.warning.call_count)