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 dfce922ec..f34344809 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py @@ -1301,3 +1301,14 @@ SNAPMIRROR_INITIALIZE_RESULT = etree.XML(""" succeeded """) + +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 396c3110c..37d3ea6e3 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 @@ -1673,6 +1673,18 @@ class NetAppCmodeClientTestCase(test.TestCase): expected = {'compression': False, 'dedupe': False} self.assertEqual(expected, 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({'compression': False, 'dedupe': False}, result) + def test_is_flexvol_mirrored(self): api_response = netapp_api.NaElement( @@ -1920,6 +1932,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 @@ -1949,6 +2025,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') @@ -2175,6 +2263,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 9a3e5c650..255a7a221 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 @@ -82,12 +83,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( config_utils, 'get_backend_configuration', @@ -353,6 +358,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): self.mock_object(self.library, 'get_replication_backend_names', mock.Mock(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 16137c4ae..6b6372944 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 = cmode_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( @@ -264,12 +266,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(): diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py index eec38e5c6..f1862d890 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py @@ -20,6 +20,7 @@ import math import re from oslo_log import log as logging +from oslo_utils import excutils from oslo_utils import units import six @@ -752,6 +753,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.""" @@ -990,9 +1023,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('Dedupe 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 {'compression': False, 'dedupe': False} if self._get_record_count(result) != 1: @@ -1230,9 +1268,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: @@ -1302,9 +1344,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( @@ -1350,9 +1396,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: