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 0f9b6e9ac2)
This commit is contained in:
Jose Porrua 2017-02-07 10:33:26 -05:00 committed by Goutham Pacha Ravi
parent f01173fbe3
commit 109177a366
5 changed files with 196 additions and 19 deletions

View File

@ -1400,3 +1400,14 @@ VSERVER_DATA_LIST_RESPONSE = etree.XML("""
<num-records>1</num-records>
</results>
""" % {'vserver': VSERVER_NAME})
SYSTEM_NODE_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<node-details-info>
<node>%s</node>
</node-details-info>
</attributes-list>
<num-records>1</num-records>
</results>
""" % NODE_NAME)

View File

@ -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(

View File

@ -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 = (

View File

@ -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)

View File

@ -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: