NetApp ONTAP: Add option to report storage provisioned capacity
This patch adds option ´netapp_driver_reports_provisioned_capacity´, for driver to calculate the storage provisioned capacity by queryng volume sizes directly from the storage system and reporting as a pool capability ´provisioned_capacity_gb´, instead of relying on the default behavior of using scheduler and volume service internal states as done by ´allocated_capacity_gb´. Implements: blueprint ontap-report-provisioned-capacity Change-Id: I97625de865a63b0cf725f61d9d2ea3c44740d9e7
This commit is contained in:
parent
1f96b386b3
commit
6906cd2a7b
@ -1389,6 +1389,34 @@ CLUSTER_PEER_POLICY_GET_RESPONSE = etree.XML("""
|
|||||||
</results>
|
</results>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
FILE_SIZES_BY_DIR_GET_ITER_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<file-info>
|
||||||
|
<name>%(name)s</name>
|
||||||
|
<file-size>1024</file-size>
|
||||||
|
</file-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""" % {
|
||||||
|
'name': fake.VOLUME_NAME
|
||||||
|
})
|
||||||
|
|
||||||
|
LUN_SIZES_BY_VOLUME_GET_ITER_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<lun-info>
|
||||||
|
<path>%(path)s</path>
|
||||||
|
<size>1024</size>
|
||||||
|
</lun-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""" % {
|
||||||
|
'path': fake.VOLUME_PATH
|
||||||
|
})
|
||||||
|
|
||||||
VSERVER_PEER_GET_ITER_RESPONSE = etree.XML("""
|
VSERVER_PEER_GET_ITER_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<attributes-list>
|
<attributes-list>
|
||||||
|
@ -3379,6 +3379,98 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||||||
self.client.connection.send_request.assert_has_calls([
|
self.client.connection.send_request.assert_has_calls([
|
||||||
mock.call('vserver-peer-accept', vserver_peer_accept_args)])
|
mock.call('vserver-peer-accept', vserver_peer_accept_args)])
|
||||||
|
|
||||||
|
def test_get_file_sizes_by_dir(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake_client.FILE_SIZES_BY_DIR_GET_ITER_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
return_value=api_response)
|
||||||
|
|
||||||
|
result = self.client.get_file_sizes_by_dir(fake.NETAPP_VOLUME)
|
||||||
|
|
||||||
|
get_get_file_sizes_by_dir_get_iter_args = {
|
||||||
|
'path': '/vol/%s' % fake.NETAPP_VOLUME,
|
||||||
|
'query': {
|
||||||
|
'file-info': {
|
||||||
|
'file-type': 'file',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'file-info': {
|
||||||
|
'name': None,
|
||||||
|
'file-size': None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.client.send_iter_request.assert_has_calls([
|
||||||
|
mock.call('file-list-directory-iter',
|
||||||
|
get_get_file_sizes_by_dir_get_iter_args,
|
||||||
|
max_page_length=100)])
|
||||||
|
|
||||||
|
expected = [{
|
||||||
|
'name': fake.VOLUME_NAME,
|
||||||
|
'file-size': float(1024)
|
||||||
|
}]
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_get_file_sizes_by_dir_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
return_value=api_response)
|
||||||
|
|
||||||
|
result = self.client.get_file_sizes_by_dir(fake.NETAPP_VOLUME)
|
||||||
|
|
||||||
|
self.assertEqual([], result)
|
||||||
|
self.assertTrue(self.client.send_iter_request.called)
|
||||||
|
|
||||||
|
def test_get_lun_sizes_by_volume(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake_client.LUN_SIZES_BY_VOLUME_GET_ITER_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
return_value=api_response)
|
||||||
|
|
||||||
|
result = self.client.get_lun_sizes_by_volume(fake.NETAPP_VOLUME)
|
||||||
|
|
||||||
|
get_lun_sizes_by_volume_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'lun-info': {
|
||||||
|
'volume': fake.NETAPP_VOLUME,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'lun-info': {
|
||||||
|
'path': None,
|
||||||
|
'size': None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.client.send_iter_request.assert_has_calls([
|
||||||
|
mock.call('lun-get-iter', get_lun_sizes_by_volume_get_iter_args,
|
||||||
|
max_page_length=100)])
|
||||||
|
|
||||||
|
expected = [{
|
||||||
|
'path': fake.VOLUME_PATH,
|
||||||
|
'size': float(1024)
|
||||||
|
}]
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_get_lun_sizes_by_volume_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
return_value=api_response)
|
||||||
|
|
||||||
|
result = self.client.get_lun_sizes_by_volume(fake.NETAPP_VOLUME)
|
||||||
|
|
||||||
|
self.assertEqual([], result)
|
||||||
|
self.assertTrue(self.client.send_iter_request.called)
|
||||||
|
|
||||||
def test_get_vserver_peers(self):
|
def test_get_vserver_peers(self):
|
||||||
|
|
||||||
api_response = netapp_api.NaElement(
|
api_response = netapp_api.NaElement(
|
||||||
|
@ -333,6 +333,7 @@ CLONE_DESTINATION = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VOLUME_NAME = 'volume-fake_volume_id'
|
VOLUME_NAME = 'volume-fake_volume_id'
|
||||||
|
VOLUME_PATH = '/vol/%s/%s' % (NETAPP_VOLUME, VOLUME_NAME)
|
||||||
MOUNT_PATH = '168.10.16.11:/' + VOLUME_ID
|
MOUNT_PATH = '168.10.16.11:/' + VOLUME_ID
|
||||||
SNAPSHOT_NAME = 'fake_snapshot_name'
|
SNAPSHOT_NAME = 'fake_snapshot_name'
|
||||||
SNAPSHOT_LUN_HANDLE = 'fake_snapshot_lun_handle'
|
SNAPSHOT_LUN_HANDLE = 'fake_snapshot_lun_handle'
|
||||||
|
@ -353,12 +353,19 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
fake.VOLUME_ID, fake.LUN_ID, fake.LUN_SIZE, fake.LUN_METADATA,
|
fake.VOLUME_ID, fake.LUN_ID, fake.LUN_SIZE, fake.LUN_METADATA,
|
||||||
None, False)
|
None, False)
|
||||||
|
|
||||||
@ddt.data({'replication_backends': [], 'cluster_credentials': False},
|
@ddt.data({'replication_backends': [], 'cluster_credentials': False,
|
||||||
|
'report_provisioned_capacity': False},
|
||||||
{'replication_backends': ['target_1', 'target_2'],
|
{'replication_backends': ['target_1', 'target_2'],
|
||||||
'cluster_credentials': True})
|
'cluster_credentials': True,
|
||||||
|
'report_provisioned_capacity': True})
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_get_pool_stats(self, replication_backends, cluster_credentials):
|
def test_get_pool_stats(self, replication_backends, cluster_credentials,
|
||||||
|
report_provisioned_capacity):
|
||||||
self.library.using_cluster_credentials = cluster_credentials
|
self.library.using_cluster_credentials = cluster_credentials
|
||||||
|
conf = self.library.configuration
|
||||||
|
conf.netapp_driver_reports_provisioned_capacity = (
|
||||||
|
report_provisioned_capacity)
|
||||||
|
|
||||||
ssc = {
|
ssc = {
|
||||||
'vola': {
|
'vola': {
|
||||||
'pool_name': 'vola',
|
'pool_name': 'vola',
|
||||||
@ -391,9 +398,19 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
'size-total': 10737418240.0,
|
'size-total': 10737418240.0,
|
||||||
'size-available': 2147483648.0,
|
'size-available': 2147483648.0,
|
||||||
}
|
}
|
||||||
|
luns_provisioned_cap = [{
|
||||||
|
'path': '/vol/volume-ae947c9b-2392-4956-b373-aaac4521f37e',
|
||||||
|
'size': 5368709120.0 # 5GB
|
||||||
|
}, {
|
||||||
|
'path': '/vol/snapshot-527eedad-a431-483d-b0ca-18995dd65b66',
|
||||||
|
'size': 1073741824.0 # 1GB
|
||||||
|
}]
|
||||||
self.mock_object(self.zapi_client,
|
self.mock_object(self.zapi_client,
|
||||||
'get_flexvol_capacity',
|
'get_flexvol_capacity',
|
||||||
return_value=mock_capacities)
|
return_value=mock_capacities)
|
||||||
|
self.mock_object(self.zapi_client,
|
||||||
|
'get_lun_sizes_by_volume',
|
||||||
|
return_value=luns_provisioned_cap)
|
||||||
self.mock_object(self.zapi_client,
|
self.mock_object(self.zapi_client,
|
||||||
'get_flexvol_dedupe_used_percent',
|
'get_flexvol_dedupe_used_percent',
|
||||||
return_value=55.0)
|
return_value=55.0)
|
||||||
@ -440,6 +457,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
'online_extend_support': True,
|
'online_extend_support': True,
|
||||||
'netapp_is_flexgroup': 'false',
|
'netapp_is_flexgroup': 'false',
|
||||||
}]
|
}]
|
||||||
|
if report_provisioned_capacity:
|
||||||
|
expected[0].update({'provisioned_capacity_gb': 5.0})
|
||||||
|
|
||||||
expected[0].update({'QoS_support': cluster_credentials})
|
expected[0].update({'QoS_support': cluster_credentials})
|
||||||
if not cluster_credentials:
|
if not cluster_credentials:
|
||||||
|
@ -157,16 +157,22 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||||||
self.assertEqual(expected_stats, self.driver._stats)
|
self.assertEqual(expected_stats, self.driver._stats)
|
||||||
|
|
||||||
@ddt.data({'replication_backends': [],
|
@ddt.data({'replication_backends': [],
|
||||||
'cluster_credentials': False, 'is_fg': False},
|
'cluster_credentials': False, 'is_fg': False,
|
||||||
|
'report_provisioned_capacity': True},
|
||||||
{'replication_backends': ['target_1', 'target_2'],
|
{'replication_backends': ['target_1', 'target_2'],
|
||||||
'cluster_credentials': True, 'is_fg': False},
|
'cluster_credentials': True, 'is_fg': False,
|
||||||
|
'report_provisioned_capacity': False},
|
||||||
{'replication_backends': ['target_1', 'target_2'],
|
{'replication_backends': ['target_1', 'target_2'],
|
||||||
'cluster_credentials': True, 'is_fg': True}
|
'cluster_credentials': True, 'is_fg': True,
|
||||||
|
'report_provisioned_capacity': False}
|
||||||
)
|
)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_get_pool_stats(self, replication_backends, cluster_credentials,
|
def test_get_pool_stats(self, replication_backends, cluster_credentials,
|
||||||
is_fg):
|
is_fg, report_provisioned_capacity):
|
||||||
self.driver.using_cluster_credentials = cluster_credentials
|
self.driver.using_cluster_credentials = cluster_credentials
|
||||||
|
conf = self.driver.configuration
|
||||||
|
conf.netapp_driver_reports_provisioned_capacity = (
|
||||||
|
report_provisioned_capacity)
|
||||||
self.driver.zapi_client = mock.Mock()
|
self.driver.zapi_client = mock.Mock()
|
||||||
ssc = {
|
ssc = {
|
||||||
'vola': {
|
'vola': {
|
||||||
@ -204,9 +210,19 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||||||
'total_capacity_gb': total_capacity_gb,
|
'total_capacity_gb': total_capacity_gb,
|
||||||
'free_capacity_gb': free_capacity_gb,
|
'free_capacity_gb': free_capacity_gb,
|
||||||
}
|
}
|
||||||
|
files_provisioned_cap = [{
|
||||||
|
'name': 'volume-ae947c9b-2392-4956-b373-aaac4521f37e',
|
||||||
|
'file-size': 5368709120.0 # 5GB
|
||||||
|
}, {
|
||||||
|
'name': 'snapshot-527eedad-a431-483d-b0ca-18995dd65b66',
|
||||||
|
'file-size': 1073741824.0 # 1GB
|
||||||
|
}]
|
||||||
self.mock_object(self.driver,
|
self.mock_object(self.driver,
|
||||||
'_get_share_capacity_info',
|
'_get_share_capacity_info',
|
||||||
return_value=capacity)
|
return_value=capacity)
|
||||||
|
self.mock_object(self.driver.zapi_client,
|
||||||
|
'get_file_sizes_by_dir',
|
||||||
|
return_value=files_provisioned_cap)
|
||||||
self.mock_object(self.driver.zapi_client,
|
self.mock_object(self.driver.zapi_client,
|
||||||
'get_flexvol_dedupe_used_percent',
|
'get_flexvol_dedupe_used_percent',
|
||||||
return_value=55.0)
|
return_value=55.0)
|
||||||
@ -255,6 +271,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||||||
'online_extend_support': False,
|
'online_extend_support': False,
|
||||||
'netapp_is_flexgroup': 'false',
|
'netapp_is_flexgroup': 'false',
|
||||||
}]
|
}]
|
||||||
|
if report_provisioned_capacity:
|
||||||
|
expected[0].update({'provisioned_capacity_gb': 5.0})
|
||||||
|
|
||||||
expected[0].update({'QoS_support': cluster_credentials})
|
expected[0].update({'QoS_support': cluster_credentials})
|
||||||
if not cluster_credentials:
|
if not cluster_credentials:
|
||||||
|
@ -333,6 +333,19 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
|||||||
size_available_gb = capacity['size-available'] / units.Gi
|
size_available_gb = capacity['size-available'] / units.Gi
|
||||||
pool['free_capacity_gb'] = na_utils.round_down(size_available_gb)
|
pool['free_capacity_gb'] = na_utils.round_down(size_available_gb)
|
||||||
|
|
||||||
|
if self.configuration.netapp_driver_reports_provisioned_capacity:
|
||||||
|
luns = self.zapi_client.get_lun_sizes_by_volume(
|
||||||
|
ssc_vol_name)
|
||||||
|
provisioned_cap = 0
|
||||||
|
for lun in luns:
|
||||||
|
lun_name = lun['path'].split('/')[-1]
|
||||||
|
# Filtering luns that matches the volume name template to
|
||||||
|
# exclude snapshots
|
||||||
|
if volume_utils.extract_id_from_volume_name(lun_name):
|
||||||
|
provisioned_cap = provisioned_cap + lun['size']
|
||||||
|
pool['provisioned_capacity_gb'] = na_utils.round_down(
|
||||||
|
float(provisioned_cap) / units.Gi)
|
||||||
|
|
||||||
if self.using_cluster_credentials:
|
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)
|
||||||
|
@ -390,6 +390,71 @@ class Client(client_base.Client):
|
|||||||
LOG.debug('No iSCSI service found for vserver %s', self.vserver)
|
LOG.debug('No iSCSI service found for vserver %s', self.vserver)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_lun_sizes_by_volume(self, volume_name):
|
||||||
|
""""Gets the list of LUNs and their sizes from a given volume name"""
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'lun-info': {
|
||||||
|
'volume': volume_name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'lun-info': {
|
||||||
|
'path': None,
|
||||||
|
'size': None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.send_iter_request(
|
||||||
|
'lun-get-iter', api_args, max_page_length=100)
|
||||||
|
|
||||||
|
if not self._has_records(result):
|
||||||
|
return []
|
||||||
|
|
||||||
|
attributes_list = result.get_child_by_name('attributes-list')
|
||||||
|
|
||||||
|
luns = []
|
||||||
|
for lun_info in attributes_list.get_children():
|
||||||
|
luns.append({
|
||||||
|
'path': lun_info.get_child_content('path'),
|
||||||
|
'size': float(lun_info.get_child_content('size'))
|
||||||
|
})
|
||||||
|
return luns
|
||||||
|
|
||||||
|
def get_file_sizes_by_dir(self, dir_path):
|
||||||
|
"""Gets the list of files and their sizes from a given directory."""
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'path': '/vol/%s' % dir_path,
|
||||||
|
'query': {
|
||||||
|
'file-info': {
|
||||||
|
'file-type': 'file'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'file-info': {
|
||||||
|
'name': None,
|
||||||
|
'file-size': None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.send_iter_request(
|
||||||
|
'file-list-directory-iter', api_args, max_page_length=100)
|
||||||
|
|
||||||
|
if not self._has_records(result):
|
||||||
|
return []
|
||||||
|
|
||||||
|
attributes_list = result.get_child_by_name('attributes-list')
|
||||||
|
|
||||||
|
files = []
|
||||||
|
for file_info in attributes_list.get_children():
|
||||||
|
files.append({
|
||||||
|
'name': file_info.get_child_content('name'),
|
||||||
|
'file-size': float(file_info.get_child_content('file-size'))
|
||||||
|
})
|
||||||
|
return files
|
||||||
|
|
||||||
def get_lun_list(self):
|
def get_lun_list(self):
|
||||||
"""Gets the list of LUNs on filer.
|
"""Gets the list of LUNs on filer.
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import uuid
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
from oslo_utils import units
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
@ -390,6 +391,14 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
|||||||
nfs_share = ssc_vol_info['pool_name']
|
nfs_share = ssc_vol_info['pool_name']
|
||||||
capacity = self._get_share_capacity_info(nfs_share)
|
capacity = self._get_share_capacity_info(nfs_share)
|
||||||
pool.update(capacity)
|
pool.update(capacity)
|
||||||
|
if self.configuration.netapp_driver_reports_provisioned_capacity:
|
||||||
|
files = self.zapi_client.get_file_sizes_by_dir(ssc_vol_name)
|
||||||
|
provisioned_cap = 0
|
||||||
|
for f in files:
|
||||||
|
if volume_utils.extract_id_from_volume_name(f['name']):
|
||||||
|
provisioned_cap = provisioned_cap + f['file-size']
|
||||||
|
pool['provisioned_capacity_gb'] = na_utils.round_down(
|
||||||
|
float(provisioned_cap) / units.Gi)
|
||||||
|
|
||||||
if self.using_cluster_credentials and not is_flexgroup:
|
if self.using_cluster_credentials and not is_flexgroup:
|
||||||
dedupe_used = self.zapi_client.get_flexvol_dedupe_used_percent(
|
dedupe_used = self.zapi_client.get_flexvol_dedupe_used_percent(
|
||||||
|
@ -83,7 +83,17 @@ netapp_provisioning_opts = [
|
|||||||
help=('This option determines if storage space is reserved '
|
help=('This option determines if storage space is reserved '
|
||||||
'for LUN allocation. If enabled, LUNs are thick '
|
'for LUN allocation. If enabled, LUNs are thick '
|
||||||
'provisioned. If space reservation is disabled, '
|
'provisioned. If space reservation is disabled, '
|
||||||
'storage space is allocated on demand.')), ]
|
'storage space is allocated on demand.')),
|
||||||
|
cfg.BoolOpt('netapp_driver_reports_provisioned_capacity',
|
||||||
|
default=False,
|
||||||
|
help=('Set to True for Cinder to query the storage system in '
|
||||||
|
'order to calculate volumes provisioned size, otherwise '
|
||||||
|
'provisioned_capacity_gb will corresponds to the '
|
||||||
|
'value of allocated_capacity_gb (calculated by Cinder '
|
||||||
|
'Core code). Enabling this feature increases '
|
||||||
|
'the number of API calls to the storage and '
|
||||||
|
'requires more processing on host, which may impact '
|
||||||
|
'volume report overall performance.')), ]
|
||||||
|
|
||||||
netapp_cluster_opts = [
|
netapp_cluster_opts = [
|
||||||
cfg.StrOpt('netapp_vserver',
|
cfg.StrOpt('netapp_vserver',
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
NetApp ONTAP driver: added option
|
||||||
|
´netapp_driver_reports_provisioned_capacity´, which enables the driver
|
||||||
|
to calculate and report provisioned capacity to Cinder Scheduler based
|
||||||
|
on volumes sizes in the storage system.
|
Loading…
Reference in New Issue
Block a user