Merge "NetApp: Add aggregate capacity info to scheduler"
This commit is contained in:
@@ -652,6 +652,30 @@ AGGR_INFO_SSC = {
|
||||
'raid-type': AGGR_RAID_TYPE,
|
||||
}
|
||||
|
||||
AGGR_SIZE_TOTAL = 107374182400
|
||||
AGGR_SIZE_AVAILABLE = 59055800320
|
||||
AGGR_USED_PERCENT = 45
|
||||
AGGR_GET_ITER_CAPACITY_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<aggr-attributes>
|
||||
<aggr-space-attributes>
|
||||
<percent-used-capacity>%(used)s</percent-used-capacity>
|
||||
<size-total>%(total_size)s</size-total>
|
||||
<size-available>%(available_size)s</size-available>
|
||||
</aggr-space-attributes>
|
||||
<aggregate-name>%(aggr)s</aggregate-name>
|
||||
</aggr-attributes>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>
|
||||
""" % {
|
||||
'aggr': VOLUME_AGGREGATE_NAME,
|
||||
'used': AGGR_USED_PERCENT,
|
||||
'available_size': AGGR_SIZE_AVAILABLE,
|
||||
'total_size': AGGR_SIZE_TOTAL,
|
||||
})
|
||||
|
||||
VOLUME_SIZE_TOTAL = 19922944
|
||||
VOLUME_SIZE_AVAILABLE = 19791872
|
||||
VOLUME_GET_ITER_CAPACITY_RESPONSE = etree.XML("""
|
||||
|
||||
@@ -1725,6 +1725,114 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual('unknown', result)
|
||||
|
||||
def test_get_aggregate_capacities(self):
|
||||
|
||||
aggr1_capacities = {
|
||||
'percent-used': 50,
|
||||
'size-available': 100.0,
|
||||
'size-total': 200.0,
|
||||
}
|
||||
aggr2_capacities = {
|
||||
'percent-used': 75,
|
||||
'size-available': 125.0,
|
||||
'size-total': 500.0,
|
||||
}
|
||||
mock_get_aggregate_capacity = self.mock_object(
|
||||
self.client, 'get_aggregate_capacity',
|
||||
mock.Mock(side_effect=[aggr1_capacities, aggr2_capacities]))
|
||||
|
||||
result = self.client.get_aggregate_capacities(['aggr1', 'aggr2'])
|
||||
|
||||
expected = {
|
||||
'aggr1': aggr1_capacities,
|
||||
'aggr2': aggr2_capacities,
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
mock_get_aggregate_capacity.assert_has_calls([
|
||||
mock.call('aggr1'),
|
||||
mock.call('aggr2'),
|
||||
])
|
||||
|
||||
def test_get_aggregate_capacities_not_found(self):
|
||||
|
||||
mock_get_aggregate_capacity = self.mock_object(
|
||||
self.client, 'get_aggregate_capacity',
|
||||
mock.Mock(side_effect=[{}, {}]))
|
||||
|
||||
result = self.client.get_aggregate_capacities(['aggr1', 'aggr2'])
|
||||
|
||||
expected = {
|
||||
'aggr1': {},
|
||||
'aggr2': {},
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
mock_get_aggregate_capacity.assert_has_calls([
|
||||
mock.call('aggr1'),
|
||||
mock.call('aggr2'),
|
||||
])
|
||||
|
||||
def test_get_aggregate_capacities_not_list(self):
|
||||
|
||||
result = self.client.get_aggregate_capacities('aggr1')
|
||||
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_get_aggregate_capacity(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake_client.AGGR_GET_ITER_CAPACITY_RESPONSE).get_child_by_name(
|
||||
'attributes-list').get_children()
|
||||
self.mock_object(self.client,
|
||||
'_get_aggregates',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.client.get_aggregate_capacity(
|
||||
fake_client.VOLUME_AGGREGATE_NAME)
|
||||
|
||||
desired_attributes = {
|
||||
'aggr-attributes': {
|
||||
'aggr-space-attributes': {
|
||||
'percent-used-capacity': None,
|
||||
'size-available': None,
|
||||
'size-total': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
self.client._get_aggregates.assert_has_calls([
|
||||
mock.call(
|
||||
aggregate_names=[fake_client.VOLUME_AGGREGATE_NAME],
|
||||
desired_attributes=desired_attributes)])
|
||||
|
||||
expected = {
|
||||
'percent-used': float(fake_client.AGGR_USED_PERCENT),
|
||||
'size-available': float(fake_client.AGGR_SIZE_AVAILABLE),
|
||||
'size-total': float(fake_client.AGGR_SIZE_TOTAL),
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_aggregate_capacity_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.get_aggregate_capacity(
|
||||
fake_client.VOLUME_AGGREGATE_NAME)
|
||||
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_get_aggregate_capacity_api_error(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(side_effect=self._mock_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(
|
||||
|
||||
@@ -310,6 +310,9 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
mock_get_ssc = self.mock_object(self.library.ssc_library,
|
||||
'get_ssc',
|
||||
mock.Mock(return_value=ssc))
|
||||
mock_get_aggrs = self.mock_object(self.library.ssc_library,
|
||||
'get_ssc_aggregates',
|
||||
mock.Mock(return_value=['aggr1']))
|
||||
|
||||
self.library.reserved_percentage = 5
|
||||
self.library.max_over_subscription_ratio = 10
|
||||
@@ -323,6 +326,17 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
self.zapi_client, 'get_flexvol_capacity',
|
||||
mock.Mock(return_value=mock_capacities))
|
||||
|
||||
aggr_capacities = {
|
||||
'aggr1': {
|
||||
'percent-used': 45,
|
||||
'size-available': 59055800320.0,
|
||||
'size-total': 107374182400.0,
|
||||
},
|
||||
}
|
||||
mock_get_aggr_capacities = self.mock_object(
|
||||
self.zapi_client, 'get_aggregate_capacities',
|
||||
mock.Mock(return_value=aggr_capacities))
|
||||
|
||||
result = self.library._get_pool_stats(filter_function='filter',
|
||||
goodness_function='goodness')
|
||||
|
||||
@@ -335,6 +349,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
'total_capacity_gb': 10.0,
|
||||
'free_capacity_gb': 2.0,
|
||||
'provisioned_capacity_gb': 8.0,
|
||||
'aggregate_used_percent': 45,
|
||||
'utilization': 30.0,
|
||||
'filter_function': 'filter',
|
||||
'goodness_function': 'goodness',
|
||||
@@ -351,6 +366,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
mock_get_ssc.assert_called_once_with()
|
||||
mock_get_aggrs.assert_called_once_with()
|
||||
mock_get_aggr_capacities.assert_called_once_with(['aggr1'])
|
||||
|
||||
@ddt.data({}, None)
|
||||
def test_get_pool_stats_no_ssc_vols(self, ssc):
|
||||
|
||||
@@ -83,6 +83,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
|
||||
def test_get_pool_stats(self):
|
||||
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
ssc = {
|
||||
'vola': {
|
||||
'pool_name': '10.10.10.10:/vola',
|
||||
@@ -100,6 +101,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
mock_get_ssc = self.mock_object(self.driver.ssc_library,
|
||||
'get_ssc',
|
||||
mock.Mock(return_value=ssc))
|
||||
mock_get_aggrs = self.mock_object(self.driver.ssc_library,
|
||||
'get_ssc_aggregates',
|
||||
mock.Mock(return_value=['aggr1']))
|
||||
|
||||
total_capacity_gb = na_utils.round_down(
|
||||
fake.TOTAL_BYTES // units.Gi, '0.01')
|
||||
@@ -117,6 +121,17 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
'_get_share_capacity_info',
|
||||
mock.Mock(return_value=capacity))
|
||||
|
||||
aggr_capacities = {
|
||||
'aggr1': {
|
||||
'percent-used': 45,
|
||||
'size-available': 59055800320.0,
|
||||
'size-total': 107374182400.0,
|
||||
},
|
||||
}
|
||||
mock_get_aggr_capacities = self.mock_object(
|
||||
self.driver.zapi_client, 'get_aggregate_capacities',
|
||||
mock.Mock(return_value=aggr_capacities))
|
||||
|
||||
self.driver.perf_library.get_node_utilization_for_pool = (
|
||||
mock.Mock(return_value=30.0))
|
||||
|
||||
@@ -131,6 +146,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb,
|
||||
'provisioned_capacity_gb': provisioned_capacity_gb,
|
||||
'aggregate_used_percent': 45,
|
||||
'utilization': 30.0,
|
||||
'filter_function': 'filter',
|
||||
'goodness_function': 'goodness',
|
||||
@@ -147,6 +163,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
mock_get_ssc.assert_called_once_with()
|
||||
mock_get_aggrs.assert_called_once_with()
|
||||
mock_get_aggr_capacities.assert_called_once_with(['aggr1'])
|
||||
|
||||
@ddt.data({}, None)
|
||||
def test_get_pool_stats_no_ssc_vols(self, ssc):
|
||||
|
||||
@@ -104,6 +104,12 @@ class CapabilitiesLibraryTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_get_ssc_aggregates(self):
|
||||
|
||||
result = self.ssc_library.get_ssc_aggregates()
|
||||
|
||||
self.assertEqual(list(fake.SSC_AGGREGATES), result)
|
||||
|
||||
def test_update_ssc(self):
|
||||
|
||||
mock_get_ssc_flexvol_info = self.mock_object(
|
||||
|
||||
@@ -216,8 +216,13 @@ 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)
|
||||
|
||||
# Get up-to-date aggregate capacities just once
|
||||
aggregates = self.ssc_library.get_ssc_aggregates()
|
||||
aggr_capacities = self.zapi_client.get_aggregate_capacities(aggregates)
|
||||
|
||||
for ssc_vol_name, ssc_vol_info in ssc.items():
|
||||
|
||||
pool = dict()
|
||||
@@ -245,6 +250,11 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
|
||||
pool['provisioned_capacity_gb'] = round(
|
||||
pool['total_capacity_gb'] - pool['free_capacity_gb'], 2)
|
||||
|
||||
aggregate_name = ssc_vol_info.get('aggregate')
|
||||
aggr_capacity = aggr_capacities.get(aggregate_name, {})
|
||||
pool['aggregate_used_percent'] = aggr_capacity.get(
|
||||
'percent-used', 0)
|
||||
|
||||
# Add utilization data
|
||||
utilization = self.perf_library.get_node_utilization_for_pool(
|
||||
ssc_vol_name)
|
||||
|
||||
@@ -23,7 +23,7 @@ from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LW
|
||||
from cinder.i18n import _, _LW, _LE
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||
@@ -951,8 +951,8 @@ class Client(client_base.Client):
|
||||
try:
|
||||
result = self.send_iter_request('sis-get-iter', api_args)
|
||||
except netapp_api.NaApiError:
|
||||
msg = _('Failed to get dedupe info for volume %s.')
|
||||
LOG.exception(msg % flexvol_name)
|
||||
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:
|
||||
@@ -993,8 +993,8 @@ class Client(client_base.Client):
|
||||
try:
|
||||
result = self.send_iter_request('snapmirror-get-iter', api_args)
|
||||
except netapp_api.NaApiError:
|
||||
msg = _('Failed to get SnapMirror info for volume %s.')
|
||||
LOG.exception(msg % flexvol_name)
|
||||
msg = _LE('Failed to get SnapMirror info for volume %s.')
|
||||
LOG.exception(msg, flexvol_name)
|
||||
return False
|
||||
|
||||
if not self._has_records(result):
|
||||
@@ -1090,8 +1090,8 @@ class Client(client_base.Client):
|
||||
aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
|
||||
desired_attributes=desired_attributes)
|
||||
except netapp_api.NaApiError:
|
||||
msg = _('Failed to get info for aggregate %s.')
|
||||
LOG.exception(msg % aggregate_name)
|
||||
msg = _LE('Failed to get info for aggregate %s.')
|
||||
LOG.exception(msg, aggregate_name)
|
||||
return {}
|
||||
|
||||
if len(aggrs) < 1:
|
||||
@@ -1136,8 +1136,8 @@ class Client(client_base.Client):
|
||||
result = self.send_request('storage-disk-get-iter', api_args,
|
||||
enable_tunneling=False)
|
||||
except netapp_api.NaApiError:
|
||||
msg = _('Failed to get disk info for aggregate %s.')
|
||||
LOG.exception(msg % aggregate_name)
|
||||
msg = _LE('Failed to get disk info for aggregate %s.')
|
||||
LOG.exception(msg, aggregate_name)
|
||||
return 'unknown'
|
||||
|
||||
if self._get_record_count(result) != 1:
|
||||
@@ -1156,6 +1156,60 @@ class Client(client_base.Client):
|
||||
|
||||
return 'unknown'
|
||||
|
||||
def get_aggregate_capacities(self, aggregate_names):
|
||||
"""Gets capacity info for multiple aggregates."""
|
||||
|
||||
if not isinstance(aggregate_names, list):
|
||||
return {}
|
||||
|
||||
aggregates = {}
|
||||
for aggregate_name in aggregate_names:
|
||||
aggregates[aggregate_name] = self.get_aggregate_capacity(
|
||||
aggregate_name)
|
||||
|
||||
return aggregates
|
||||
|
||||
def get_aggregate_capacity(self, aggregate_name):
|
||||
"""Gets capacity info for an aggregate."""
|
||||
|
||||
desired_attributes = {
|
||||
'aggr-attributes': {
|
||||
'aggr-space-attributes': {
|
||||
'percent-used-capacity': None,
|
||||
'size-available': None,
|
||||
'size-total': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
return {}
|
||||
|
||||
if len(aggrs) < 1:
|
||||
return {}
|
||||
|
||||
aggr_attributes = aggrs[0]
|
||||
aggr_space_attributes = aggr_attributes.get_child_by_name(
|
||||
'aggr-space-attributes') or netapp_api.NaElement('none')
|
||||
|
||||
percent_used = int(aggr_space_attributes.get_child_content(
|
||||
'percent-used-capacity'))
|
||||
size_available = float(aggr_space_attributes.get_child_content(
|
||||
'size-available'))
|
||||
size_total = float(
|
||||
aggr_space_attributes.get_child_content('size-total'))
|
||||
|
||||
return {
|
||||
'percent-used': percent_used,
|
||||
'size-available': size_available,
|
||||
'size-total': size_total,
|
||||
}
|
||||
|
||||
def get_performance_instance_uuids(self, object_name, node_name):
|
||||
"""Get UUIDs of performance instances for a cluster node."""
|
||||
|
||||
|
||||
@@ -185,8 +185,13 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
||||
if not ssc:
|
||||
return pools
|
||||
|
||||
# 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)
|
||||
|
||||
for ssc_vol_name, ssc_vol_info in ssc.items():
|
||||
|
||||
pool = dict()
|
||||
@@ -202,6 +207,11 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
||||
capacity = self._get_share_capacity_info(nfs_share)
|
||||
pool.update(capacity)
|
||||
|
||||
aggregate_name = ssc_vol_info.get('aggregate')
|
||||
aggr_capacity = aggr_capacities.get(aggregate_name, {})
|
||||
pool['aggregate_used_percent'] = aggr_capacity.get(
|
||||
'percent-used', 0)
|
||||
|
||||
# Add utilization data
|
||||
utilization = self.perf_library.get_node_utilization_for_pool(
|
||||
ssc_vol_name)
|
||||
|
||||
@@ -93,6 +93,15 @@ class CapabilitiesLibrary(object):
|
||||
|
||||
return copy.deepcopy(self.ssc.get(flexvol_name, {}))
|
||||
|
||||
def get_ssc_aggregates(self):
|
||||
"""Get a list of aggregates for all SSC flexvols."""
|
||||
|
||||
aggregates = set()
|
||||
for __, flexvol_info in self.ssc.items():
|
||||
if 'aggregate' in flexvol_info:
|
||||
aggregates.add(flexvol_info['aggregate'])
|
||||
return list(aggregates)
|
||||
|
||||
def update_ssc(self, flexvol_map):
|
||||
"""Periodically runs to update Storage Service Catalog data.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user