Merge "NetApp: Track SVM and Cluster scoped credentials"
This commit is contained in:
@@ -1400,3 +1400,14 @@ VSERVER_DATA_LIST_RESPONSE = etree.XML("""
|
|||||||
<num-records>1</num-records>
|
<num-records>1</num-records>
|
||||||
</results>
|
</results>
|
||||||
""" % {'vserver': VSERVER_NAME})
|
""" % {'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)
|
||||||
|
|||||||
@@ -1814,6 +1814,19 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||||||
self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA,
|
self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA,
|
||||||
result)
|
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):
|
def test_get_flexvol_dedupe_used_percent(self):
|
||||||
|
|
||||||
self.client.features.add_feature('CLONE_SPLIT_STATUS')
|
self.client.features.add_feature('CLONE_SPLIT_STATUS')
|
||||||
@@ -2199,6 +2212,70 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual({}, result)
|
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']},
|
@ddt.data({'types': {'FCAL'}, 'expected': ['FCAL']},
|
||||||
{'types': {'SATA', 'SSD'}, 'expected': ['SATA', 'SSD']},)
|
{'types': {'SATA', 'SSD'}, 'expected': ['SATA', 'SSD']},)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
@@ -2226,6 +2303,18 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||||||
mock_get_aggregate_disk_types.assert_called_once_with(
|
mock_get_aggregate_disk_types.assert_called_once_with(
|
||||||
fake_client.VOLUME_AGGREGATE_NAME)
|
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):
|
def test_get_aggregate_disk_types_shared(self):
|
||||||
|
|
||||||
self.client.features.add_feature('ADVANCED_DISK_PARTITIONING')
|
self.client.features.add_feature('ADVANCED_DISK_PARTITIONING')
|
||||||
@@ -2451,6 +2540,16 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual({}, result)
|
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):
|
def test_get_performance_instance_uuids(self):
|
||||||
|
|
||||||
self.mock_send_request.return_value = netapp_api.NaElement(
|
self.mock_send_request.return_value = netapp_api.NaElement(
|
||||||
|
|||||||
@@ -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 import block_cmode
|
||||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
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_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.performance import perf_cmode
|
||||||
from cinder.volume.drivers.netapp.dataontap.utils import data_motion
|
from cinder.volume.drivers.netapp.dataontap.utils import data_motion
|
||||||
from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
|
from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
|
||||||
@@ -79,12 +80,16 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
config.netapp_vserver = 'openstack'
|
config.netapp_vserver = 'openstack'
|
||||||
return config
|
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(perf_cmode, 'PerformanceCmodeLibrary', mock.Mock())
|
||||||
@mock.patch.object(client_base.Client, 'get_ontapi_version',
|
@mock.patch.object(client_base.Client, 'get_ontapi_version',
|
||||||
mock.MagicMock(return_value=(1, 20)))
|
mock.MagicMock(return_value=(1, 20)))
|
||||||
@mock.patch.object(na_utils, 'check_flags')
|
@mock.patch.object(na_utils, 'check_flags')
|
||||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, 'do_setup')
|
@mock.patch.object(block_base.NetAppBlockStorageLibrary, 'do_setup')
|
||||||
def test_do_setup(self, super_do_setup, mock_check_flags):
|
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(client_base.Client, '_init_ssh_client')
|
||||||
self.mock_object(
|
self.mock_object(
|
||||||
dot_utils, 'get_backend_configuration',
|
dot_utils, 'get_backend_configuration',
|
||||||
@@ -365,6 +370,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
self.mock_object(self.library, 'get_replication_backend_names',
|
self.mock_object(self.library, 'get_replication_backend_names',
|
||||||
return_value=replication_backends)
|
return_value=replication_backends)
|
||||||
|
|
||||||
|
self.library.using_cluster_credentials = True
|
||||||
self.library.reserved_percentage = 5
|
self.library.reserved_percentage = 5
|
||||||
self.library.max_over_subscription_ratio = 10
|
self.library.max_over_subscription_ratio = 10
|
||||||
self.library.perf_library.get_node_utilization_for_pool = (
|
self.library.perf_library.get_node_utilization_for_pool = (
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
|||||||
self.zapi_client = dot_utils.get_client_for_backend(
|
self.zapi_client = dot_utils.get_client_for_backend(
|
||||||
self.failed_over_backend_name or self.backend_name)
|
self.failed_over_backend_name or self.backend_name)
|
||||||
self.vserver = self.zapi_client.vserver
|
self.vserver = self.zapi_client.vserver
|
||||||
|
self.using_cluster_credentials = \
|
||||||
|
self.zapi_client.check_for_cluster_credentials()
|
||||||
|
|
||||||
# Performance monitoring library
|
# Performance monitoring library
|
||||||
self.perf_library = perf_cmode.PerformanceCmodeLibrary(
|
self.perf_library = perf_cmode.PerformanceCmodeLibrary(
|
||||||
@@ -275,12 +277,18 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
|||||||
if not ssc:
|
if not ssc:
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
|
# Utilization and performance metrics require cluster-scoped
|
||||||
|
# credentials
|
||||||
|
if self.using_cluster_credentials:
|
||||||
# Get up-to-date node utilization metrics just once
|
# Get up-to-date node utilization metrics just once
|
||||||
self.perf_library.update_performance_cache(ssc)
|
self.perf_library.update_performance_cache(ssc)
|
||||||
|
|
||||||
# Get up-to-date aggregate capacities just once
|
# Get up-to-date aggregate capacities just once
|
||||||
aggregates = self.ssc_library.get_ssc_aggregates()
|
aggregates = self.ssc_library.get_ssc_aggregates()
|
||||||
aggr_capacities = self.zapi_client.get_aggregate_capacities(aggregates)
|
aggr_capacities = self.zapi_client.get_aggregate_capacities(
|
||||||
|
aggregates)
|
||||||
|
else:
|
||||||
|
aggr_capacities = {}
|
||||||
|
|
||||||
for ssc_vol_name, ssc_vol_info in ssc.items():
|
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['provisioned_capacity_gb'] = round(
|
||||||
pool['total_capacity_gb'] - pool['free_capacity_gb'], 2)
|
pool['total_capacity_gb'] - pool['free_capacity_gb'], 2)
|
||||||
|
|
||||||
|
if self.using_cluster_credentials:
|
||||||
dedupe_used = self.zapi_client.get_flexvol_dedupe_used_percent(
|
dedupe_used = self.zapi_client.get_flexvol_dedupe_used_percent(
|
||||||
ssc_vol_name)
|
ssc_vol_name)
|
||||||
|
else:
|
||||||
|
dedupe_used = 0.0
|
||||||
pool['netapp_dedupe_used_percent'] = na_utils.round_down(
|
pool['netapp_dedupe_used_percent'] = na_utils.round_down(
|
||||||
dedupe_used)
|
dedupe_used)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import math
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@@ -819,6 +820,38 @@ class Client(client_base.Client):
|
|||||||
|
|
||||||
return True
|
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):
|
def get_operational_lif_addresses(self):
|
||||||
"""Gets the IP addresses of operational LIFs on the vserver."""
|
"""Gets the IP addresses of operational LIFs on the vserver."""
|
||||||
|
|
||||||
@@ -1066,7 +1099,12 @@ class Client(client_base.Client):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.send_iter_request('sis-get-iter', api_args)
|
result = self.send_iter_request('sis-get-iter', api_args)
|
||||||
except netapp_api.NaApiError:
|
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.')
|
msg = _LE('Failed to get dedupe info for volume %s.')
|
||||||
LOG.exception(msg, flexvol_name)
|
LOG.exception(msg, flexvol_name)
|
||||||
return no_dedupe_response
|
return no_dedupe_response
|
||||||
@@ -1389,7 +1427,11 @@ class Client(client_base.Client):
|
|||||||
try:
|
try:
|
||||||
aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
|
aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
|
||||||
desired_attributes=desired_attributes)
|
desired_attributes=desired_attributes)
|
||||||
except netapp_api.NaApiError:
|
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.')
|
msg = _LE('Failed to get info for aggregate %s.')
|
||||||
LOG.exception(msg, aggregate_name)
|
LOG.exception(msg, aggregate_name)
|
||||||
return {}
|
return {}
|
||||||
@@ -1461,7 +1503,11 @@ class Client(client_base.Client):
|
|||||||
try:
|
try:
|
||||||
result = self.send_iter_request(
|
result = self.send_iter_request(
|
||||||
'storage-disk-get-iter', api_args, enable_tunneling=False)
|
'storage-disk-get-iter', api_args, enable_tunneling=False)
|
||||||
except netapp_api.NaApiError:
|
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.')
|
msg = _LE('Failed to get disk info for aggregate %s.')
|
||||||
LOG.exception(msg, aggregate_name)
|
LOG.exception(msg, aggregate_name)
|
||||||
return disk_types
|
return disk_types
|
||||||
@@ -1509,7 +1555,11 @@ class Client(client_base.Client):
|
|||||||
try:
|
try:
|
||||||
aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
|
aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
|
||||||
desired_attributes=desired_attributes)
|
desired_attributes=desired_attributes)
|
||||||
except netapp_api.NaApiError:
|
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.')
|
msg = _LE('Failed to get info for aggregate %s.')
|
||||||
LOG.exception(msg, aggregate_name)
|
LOG.exception(msg, aggregate_name)
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
Reference in New Issue
Block a user