From 109177a366b2440f31d5994df0318bdc0124a88b Mon Sep 17 00:00:00 2001 From: Jose Porrua Date: Tue, 7 Feb 2017 10:33:26 -0500 Subject: [PATCH] NetApp: Track SVM and Cluster scoped credentials This fix avoids logging an exception when a user chooses to use an SVM scoped account. cDOT driver requires cluster scoped privileges to gather backend statistics, performance counters, etc. These APIs are not available for SVM scoped credentials. Change-Id: If2e3bae98db225ff0cfc9e868eaaeef088135562 Closes-Bug: #1660870 (cherry picked from commit 0f9b6e9ac2ddfd4f90b1a3a71ec46fa56008aaee) --- .../drivers/netapp/dataontap/client/fakes.py | 11 +++ .../dataontap/client/test_client_cmode.py | 99 +++++++++++++++++++ .../netapp/dataontap/test_block_cmode.py | 6 ++ .../drivers/netapp/dataontap/block_cmode.py | 25 +++-- .../netapp/dataontap/client/client_cmode.py | 74 +++++++++++--- 5 files changed, 196 insertions(+), 19 deletions(-) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py index 47f2065c751..40bb4e022d6 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py @@ -1400,3 +1400,14 @@ VSERVER_DATA_LIST_RESPONSE = etree.XML(""" 1 """ % {'vserver': VSERVER_NAME}) + +SYSTEM_NODE_GET_ITER_RESPONSE = etree.XML(""" + + + + %s + + + 1 + +""" % NODE_NAME) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py index dc47d6add44..eafb8ae87b5 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py @@ -1814,6 +1814,19 @@ class NetAppCmodeClientTestCase(test.TestCase): self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA, result) + def test_get_flexvol_dedupe_info_api_insufficient_privileges(self): + + api_error = netapp_api.NaApiError(code=netapp_api.EAPIPRIVILEGE) + self.mock_object(self.client, + 'send_iter_request', + side_effect=api_error) + + result = self.client.get_flexvol_dedupe_info( + fake_client.VOLUME_NAMES[0]) + + self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA, + result) + def test_get_flexvol_dedupe_used_percent(self): self.client.features.add_feature('CLONE_SPLIT_STATUS') @@ -2199,6 +2212,70 @@ class NetAppCmodeClientTestCase(test.TestCase): self.assertEqual({}, result) + def test_get_aggregate_api_not_found(self): + + api_error = netapp_api.NaApiError(code=netapp_api.EAPINOTFOUND) + self.mock_object(self.client, + 'send_iter_request', + side_effect=api_error) + + result = self.client.get_aggregate(fake_client.VOLUME_AGGREGATE_NAME) + + self.assertEqual({}, result) + + def test_list_cluster_nodes(self): + + api_response = netapp_api.NaElement( + fake_client.SYSTEM_NODE_GET_ITER_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.list_cluster_nodes() + + self.assertListEqual([fake_client.NODE_NAME], result) + + def test_list_cluster_nodes_not_found(self): + + api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.list_cluster_nodes() + + self.assertListEqual([], result) + + def test_check_for_cluster_credentials(self): + + self.mock_object(self.client, + 'list_cluster_nodes', + mock.Mock(return_value=fake_client.NODE_NAMES)) + + result = self.client.check_for_cluster_credentials() + + self.assertTrue(result) + + def test_check_for_cluster_credentials_not_found(self): + + api_error = netapp_api.NaApiError(code=netapp_api.EAPINOTFOUND) + self.mock_object(self.client, + 'list_cluster_nodes', + side_effect=api_error) + + result = self.client.check_for_cluster_credentials() + + self.assertFalse(result) + + def test_check_for_cluster_credentials_api_error(self): + + self.mock_object(self.client, + 'list_cluster_nodes', + self._mock_api_error()) + + self.assertRaises(netapp_api.NaApiError, + self.client.check_for_cluster_credentials) + @ddt.data({'types': {'FCAL'}, 'expected': ['FCAL']}, {'types': {'SATA', 'SSD'}, 'expected': ['SATA', 'SSD']},) @ddt.unpack @@ -2226,6 +2303,18 @@ class NetAppCmodeClientTestCase(test.TestCase): mock_get_aggregate_disk_types.assert_called_once_with( fake_client.VOLUME_AGGREGATE_NAME) + def test_get_aggregate_disk_types_api_not_found(self): + + api_error = netapp_api.NaApiError(code=netapp_api.EAPINOTFOUND) + self.mock_object(self.client, + 'send_iter_request', + side_effect=api_error) + + result = self.client.get_aggregate_disk_types( + fake_client.VOLUME_AGGREGATE_NAME) + + self.assertIsNone(result) + def test_get_aggregate_disk_types_shared(self): self.client.features.add_feature('ADVANCED_DISK_PARTITIONING') @@ -2451,6 +2540,16 @@ class NetAppCmodeClientTestCase(test.TestCase): self.assertEqual({}, result) + def test_get_aggregate_capacity_api_not_found(self): + + api_error = netapp_api.NaApiError(code=netapp_api.EAPINOTFOUND) + self.mock_object(self.client, 'send_request', side_effect=api_error) + + result = self.client.get_aggregate_capacity( + fake_client.VOLUME_AGGREGATE_NAME) + + self.assertEqual({}, result) + def test_get_performance_instance_uuids(self): self.mock_send_request.return_value = netapp_api.NaElement( diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py index 80b5fc6f059..e9eed0f8bc2 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py @@ -31,6 +31,7 @@ from cinder.volume.drivers.netapp.dataontap import block_base from cinder.volume.drivers.netapp.dataontap import block_cmode from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api from cinder.volume.drivers.netapp.dataontap.client import client_base +from cinder.volume.drivers.netapp.dataontap.client import client_cmode from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode from cinder.volume.drivers.netapp.dataontap.utils import data_motion from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls @@ -79,12 +80,16 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): config.netapp_vserver = 'openstack' return config + @mock.patch.object(client_cmode.Client, 'check_for_cluster_credentials', + mock.MagicMock(return_value=False)) @mock.patch.object(perf_cmode, 'PerformanceCmodeLibrary', mock.Mock()) @mock.patch.object(client_base.Client, 'get_ontapi_version', mock.MagicMock(return_value=(1, 20))) @mock.patch.object(na_utils, 'check_flags') @mock.patch.object(block_base.NetAppBlockStorageLibrary, 'do_setup') def test_do_setup(self, super_do_setup, mock_check_flags): + self.zapi_client.check_for_cluster_credentials = mock.MagicMock( + return_value=True) self.mock_object(client_base.Client, '_init_ssh_client') self.mock_object( dot_utils, 'get_backend_configuration', @@ -368,6 +373,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): self.mock_object(self.library, 'get_replication_backend_names', return_value=replication_backends) + self.library.using_cluster_credentials = True self.library.reserved_percentage = 5 self.library.max_over_subscription_ratio = 10 self.library.perf_library.get_node_utilization_for_pool = ( diff --git a/cinder/volume/drivers/netapp/dataontap/block_cmode.py b/cinder/volume/drivers/netapp/dataontap/block_cmode.py index 15639b1093a..59218df41fe 100644 --- a/cinder/volume/drivers/netapp/dataontap/block_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/block_cmode.py @@ -72,6 +72,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary, self.zapi_client = dot_utils.get_client_for_backend( self.failed_over_backend_name or self.backend_name) self.vserver = self.zapi_client.vserver + self.using_cluster_credentials = \ + self.zapi_client.check_for_cluster_credentials() # Performance monitoring library self.perf_library = perf_cmode.PerformanceCmodeLibrary( @@ -275,12 +277,18 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary, if not ssc: return pools - # Get up-to-date node utilization metrics just once - self.perf_library.update_performance_cache(ssc) + # Utilization and performance metrics require cluster-scoped + # credentials + if self.using_cluster_credentials: + # Get up-to-date node utilization metrics just once + self.perf_library.update_performance_cache(ssc) - # Get up-to-date aggregate capacities just once - aggregates = self.ssc_library.get_ssc_aggregates() - aggr_capacities = self.zapi_client.get_aggregate_capacities(aggregates) + # Get up-to-date aggregate capacities just once + aggregates = self.ssc_library.get_ssc_aggregates() + aggr_capacities = self.zapi_client.get_aggregate_capacities( + aggregates) + else: + aggr_capacities = {} for ssc_vol_name, ssc_vol_info in ssc.items(): @@ -310,8 +318,11 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary, pool['provisioned_capacity_gb'] = round( pool['total_capacity_gb'] - pool['free_capacity_gb'], 2) - dedupe_used = self.zapi_client.get_flexvol_dedupe_used_percent( - ssc_vol_name) + if self.using_cluster_credentials: + dedupe_used = self.zapi_client.get_flexvol_dedupe_used_percent( + ssc_vol_name) + else: + dedupe_used = 0.0 pool['netapp_dedupe_used_percent'] = na_utils.round_down( dedupe_used) diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py index 3cb8b8d8d95..edfe5a05e83 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py @@ -21,6 +21,7 @@ import math import re from oslo_log import log as logging +from oslo_utils import excutils from oslo_utils import units import six @@ -819,6 +820,38 @@ class Client(client_base.Client): return True + def list_cluster_nodes(self): + """Get all available cluster nodes.""" + + api_args = { + 'desired-attributes': { + 'node-details-info': { + 'node': None, + }, + }, + } + result = self.send_iter_request('system-node-get-iter', api_args) + nodes_info_list = result.get_child_by_name( + 'attributes-list') or netapp_api.NaElement('none') + return [node_info.get_child_content('node') for node_info + in nodes_info_list.get_children()] + + def check_for_cluster_credentials(self): + """Checks whether cluster-scoped credentials are being used or not.""" + + try: + self.list_cluster_nodes() + # API succeeded, so definitely a cluster management LIF + return True + except netapp_api.NaApiError as e: + if e.code == netapp_api.EAPINOTFOUND: + LOG.debug('Not connected to cluster management LIF.') + else: + with excutils.save_and_reraise_exception(): + msg = _LE('Failed to get the list of nodes.') + LOG.exception(msg) + return False + def get_operational_lif_addresses(self): """Gets the IP addresses of operational LIFs on the vserver.""" @@ -1066,9 +1099,14 @@ class Client(client_base.Client): try: result = self.send_iter_request('sis-get-iter', api_args) - except netapp_api.NaApiError: - msg = _LE('Failed to get dedupe info for volume %s.') - LOG.exception(msg, flexvol_name) + except netapp_api.NaApiError as e: + if e.code == netapp_api.EAPIPRIVILEGE: + LOG.debug('Dedup info for volume %(name)s will not be ' + 'collected. This API requires cluster-scoped ' + 'credentials.', {'name': flexvol_name}) + else: + msg = _LE('Failed to get dedupe info for volume %s.') + LOG.exception(msg, flexvol_name) return no_dedupe_response if self._get_record_count(result) != 1: @@ -1389,9 +1427,13 @@ class Client(client_base.Client): try: aggrs = self._get_aggregates(aggregate_names=[aggregate_name], desired_attributes=desired_attributes) - except netapp_api.NaApiError: - msg = _LE('Failed to get info for aggregate %s.') - LOG.exception(msg, aggregate_name) + except netapp_api.NaApiError as e: + if e.code == netapp_api.EAPINOTFOUND: + LOG.debug('Aggregate info can only be collected with ' + 'cluster-scoped credentials.') + else: + msg = _LE('Failed to get info for aggregate %s.') + LOG.exception(msg, aggregate_name) return {} if len(aggrs) < 1: @@ -1461,9 +1503,13 @@ class Client(client_base.Client): try: result = self.send_iter_request( 'storage-disk-get-iter', api_args, enable_tunneling=False) - except netapp_api.NaApiError: - msg = _LE('Failed to get disk info for aggregate %s.') - LOG.exception(msg, aggregate_name) + except netapp_api.NaApiError as e: + if e.code == netapp_api.EAPINOTFOUND: + LOG.debug('Disk types can only be collected with ' + 'cluster scoped credentials.') + else: + msg = _LE('Failed to get disk info for aggregate %s.') + LOG.exception(msg, aggregate_name) return disk_types attributes_list = result.get_child_by_name( @@ -1509,9 +1555,13 @@ class Client(client_base.Client): try: aggrs = self._get_aggregates(aggregate_names=[aggregate_name], desired_attributes=desired_attributes) - except netapp_api.NaApiError: - msg = _LE('Failed to get info for aggregate %s.') - LOG.exception(msg, aggregate_name) + except netapp_api.NaApiError as e: + if e.code == netapp_api.EAPINOTFOUND: + LOG.debug('Aggregate capacity can only be collected with ' + 'cluster scoped credentials.') + else: + msg = _LE('Failed to get info for aggregate %s.') + LOG.exception(msg, aggregate_name) return {} if len(aggrs) < 1: