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 31c820cb336..ae922395960 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',
@@ -365,6 +370,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 731e02ec0e6..f1f3dec1ea5 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: