HP3PAR_SAN_SSH_PORT = 999
HP3PAR_SAN_SSH_CON_TIMEOUT = 44
HP3PAR_SAN_SSH_PRIVATE = 'foobar'
+GOODNESS_FUNCTION = \
+ "stats.capacity_utilization < 0.6? 100:25"
+FILTER_FUNCTION = \
+ "stats.total_volumes < 400 && stats.capacity_utilization < 0.8"
CHAP_USER_KEY = "HPQ-cinder-CHAP-name"
CHAP_PASS_KEY = "HPQ-cinder-CHAP-secret"
'name': HP3PAR_CPG,
'numFPVVs': 2,
'numTPVVs': 0,
+ 'numTDVVs': 1,
'state': 1,
'uuid': '29c214aa-62b9-41c8-b198-543f6cf24edf'}]
configuration.hp3par_snapshot_retention = ""
configuration.hp3par_iscsi_ips = []
configuration.hp3par_iscsi_chap_enabled = False
+ configuration.goodness_function = GOODNESS_FUNCTION
+ configuration.filter_function = FILTER_FUNCTION
return configuration
@mock.patch(
self.assertNotIn('initiator_target_map', conn_info['data'])
def test_get_volume_stats(self):
- # setup_mock_client drive with default configuration
+ # setup_mock_client drive with the configuration
# and return the mock HTTP 3PAR client
- mock_client = self.setup_driver()
+ config = self.setup_configuration()
+ config.filter_function = FILTER_FUNCTION
+ config.goodness_function = GOODNESS_FUNCTION
+ mock_client = self.setup_driver(config=config)
mock_client.getCPG.return_value = self.cpgs[0]
mock_client.getStorageSystemInfo.return_value = {
'serialNumber': '1234'
self.assertEqual(stats['free_capacity_gb'], 0)
self.assertEqual(stats['pools'][0]['total_capacity_gb'], 24.0)
self.assertEqual(stats['pools'][0]['free_capacity_gb'], 3.0)
+ self.assertEqual(stats['pools'][0]['capacity_utilization'], 87.5)
+ self.assertEqual(stats['pools'][0]['total_volumes'], 3)
+ self.assertEqual(stats['pools'][0]['goodness_function'],
+ GOODNESS_FUNCTION)
+ self.assertEqual(stats['pools'][0]['filter_function'],
+ FILTER_FUNCTION)
expected = [
mock.call.getStorageSystemInfo(),
self.assertEqual(stats['free_capacity_gb'], 0)
self.assertEqual(stats['pools'][0]['total_capacity_gb'], 24.0)
self.assertEqual(stats['pools'][0]['free_capacity_gb'], 3.0)
+ self.assertEqual(stats['pools'][0]['capacity_utilization'], 87.5)
+ self.assertEqual(stats['pools'][0]['total_volumes'], 3)
+ self.assertEqual(stats['pools'][0]['goodness_function'],
+ GOODNESS_FUNCTION)
+ self.assertEqual(stats['pools'][0]['filter_function'],
+ FILTER_FUNCTION)
cpg2 = self.cpgs[0].copy()
cpg2.update({'SDGrowth': {'limitMiB': 8192}})
self.assertEqual(stats['pools'][0]['total_capacity_gb'],
total_capacity_gb)
free_capacity_gb = int(
- (8192 - self.cpgs[0]['UsrUsage']['usedMiB']) * const)
+ (8192 - (self.cpgs[0]['UsrUsage']['usedMiB'] +
+ self.cpgs[0]['SDUsage']['usedMiB'])) * const)
self.assertEqual(stats['free_capacity_gb'], 0)
self.assertEqual(stats['pools'][0]['free_capacity_gb'],
free_capacity_gb)
+ cap_util = (float(total_capacity_gb - free_capacity_gb) /
+ float(total_capacity_gb)) * 100
+ self.assertEqual(stats['pools'][0]['capacity_utilization'],
+ cap_util)
+ self.assertEqual(stats['pools'][0]['total_volumes'], 3)
+ self.assertEqual(stats['pools'][0]['goodness_function'],
+ GOODNESS_FUNCTION)
+ self.assertEqual(stats['pools'][0]['filter_function'],
+ FILTER_FUNCTION)
common.client.deleteCPG(HP3PAR_CPG)
common.client.createCPG(HP3PAR_CPG, {})
self.assertDictMatch(result, self.properties)
def test_get_volume_stats(self):
- # setup_mock_client drive with default configuration
+ # setup_mock_client drive with the configuration
# and return the mock HTTP 3PAR client
- mock_client = self.setup_driver()
+ config = self.setup_configuration()
+ config.filter_function = FILTER_FUNCTION
+ config.goodness_function = GOODNESS_FUNCTION
+ mock_client = self.setup_driver(config=config)
mock_client.getCPG.return_value = self.cpgs[0]
mock_client.getStorageSystemInfo.return_value = {
'serialNumber': '1234'
self.assertEqual(stats['free_capacity_gb'], 0)
self.assertEqual(stats['pools'][0]['total_capacity_gb'], 24.0)
self.assertEqual(stats['pools'][0]['free_capacity_gb'], 3.0)
+ self.assertEqual(stats['pools'][0]['capacity_utilization'], 87.5)
+ self.assertEqual(stats['pools'][0]['total_volumes'], 3)
+ self.assertEqual(stats['pools'][0]['goodness_function'],
+ GOODNESS_FUNCTION)
+ self.assertEqual(stats['pools'][0]['filter_function'],
+ FILTER_FUNCTION)
expected = [
mock.call.getStorageSystemInfo(),
self.assertEqual(stats['pools'][0]['total_capacity_gb'],
total_capacity_gb)
free_capacity_gb = int(
- (8192 - self.cpgs[0]['UsrUsage']['usedMiB']) * const)
+ (8192 - (self.cpgs[0]['UsrUsage']['usedMiB'] +
+ self.cpgs[0]['SDUsage']['usedMiB'])) * const)
self.assertEqual(stats['free_capacity_gb'], 0)
self.assertEqual(stats['pools'][0]['free_capacity_gb'],
free_capacity_gb)
+ cap_util = (float(total_capacity_gb - free_capacity_gb) /
+ float(total_capacity_gb)) * 100
+ self.assertEqual(stats['pools'][0]['capacity_utilization'],
+ cap_util)
+ self.assertEqual(stats['pools'][0]['total_volumes'], 3)
+ self.assertEqual(stats['pools'][0]['goodness_function'],
+ GOODNESS_FUNCTION)
+ self.assertEqual(stats['pools'][0]['filter_function'],
+ FILTER_FUNCTION)
def test_create_host(self):
# setup_mock_client drive with default configuration
'lv_count': '2',
'uuid': 'vR1JU3-FAKE-C4A9-PQFh-Mctm-9FwA-Xwzc1m'}]
+ def _fake_get_volumes(obj, lv_name=None):
+ return [{'vg': 'fake_vg', 'name': 'fake_vol', 'size': '1000'}]
+
self.stubs.Set(brick_lvm.LVM,
'get_all_volume_groups',
_fake_get_all_volume_groups)
'get_all_physical_volumes',
_fake_get_all_physical_volumes)
+ self.stubs.Set(brick_lvm.LVM,
+ 'get_volumes',
+ _fake_get_volumes)
+
self.volume.driver.vg = brick_lvm.LVM('cinder-volumes', 'sudo')
self.volume.driver._update_volume_stats()
stats['pools'][0]['free_capacity_gb'], float('0.52'))
self.assertEqual(
stats['pools'][0]['provisioned_capacity_gb'], float('5.0'))
+ self.assertEqual(
+ stats['pools'][0]['total_volumes'], int('1'))
def test_validate_connector(self):
iscsi_driver =\
secret=True),
cfg.StrOpt('driver_data_namespace',
default=None,
- help='Namespace for driver private data values to be saved in.')
+ help='Namespace for driver private data values to be '
+ 'saved in.'),
+ cfg.StrOpt('filter_function',
+ default=None,
+ help='String representation for an equation that will be '
+ 'used to filter hosts. Only used when the driver '
+ 'filter is set to be used by the Cinder scheduler.'),
+ cfg.StrOpt('goodness_function',
+ default=None,
+ help='String representation for an equation that will be '
+ 'used to determine the goodness of a host. Only used '
+ 'when using the goodness weigher is set to be used by '
+ 'the Cinder scheduler.'),
]
# for backward compatibility
"""
return None
+ def _update_pools_and_stats(self, data):
+ """Updates data for pools and volume stats based on provided data."""
+ # provisioned_capacity_gb is set to None by default below, but
+ # None won't be used in calculation. It will be overridden by
+ # driver's provisioned_capacity_gb if reported, otherwise it
+ # defaults to allocated_capacity_gb in host_manager.py.
+ if self.pools:
+ for pool in self.pools:
+ new_pool = {}
+ new_pool.update(dict(
+ pool_name=pool,
+ total_capacity_gb=0,
+ free_capacity_gb=0,
+ provisioned_capacity_gb=None,
+ reserved_percentage=100,
+ QoS_support=False,
+ filter_function=self.get_filter_function(),
+ goodness_function=self.get_goodness_function()
+ ))
+ data["pools"].append(new_pool)
+ else:
+ # No pool configured, the whole backend will be treated as a pool
+ single_pool = {}
+ single_pool.update(dict(
+ pool_name=data["volume_backend_name"],
+ total_capacity_gb=0,
+ free_capacity_gb=0,
+ provisioned_capacity_gb=None,
+ reserved_percentage=100,
+ QoS_support=False,
+ filter_function=self.get_filter_function(),
+ goodness_function=self.get_goodness_function()
+ ))
+ data["pools"].append(single_pool)
+ self._stats = data
+
def copy_volume_data(self, context, src_vol, dest_vol, remote=None):
"""Copy data from src_vol to dest_vol."""
LOG.debug(('copy_data_between_volumes %(src)s -> %(dest)s.')
finally:
self._detach_volume(context, attach_info, volume, properties)
+ def get_filter_function(self):
+ """Get filter_function string.
+
+ Returns either the string from the driver instance or global section
+ in cinder.conf. If nothing is specified in cinder.conf, then try to
+ find the default filter_function. When None is returned the scheduler
+ will always pass the driver instance.
+
+ :return a filter_function string or None
+ """
+ ret_function = self.configuration.filter_function
+ if not ret_function:
+ ret_function = CONF.filter_function
+ if not ret_function:
+ ret_function = self.get_default_filter_function()
+ return ret_function
+
+ def get_goodness_function(self):
+ """Get good_function string.
+
+ Returns either the string from the driver instance or global section
+ in cinder.conf. If nothing is specified in cinder.conf, then try to
+ find the default goodness_function. When None is returned the scheduler
+ will give the lowest score to the driver instance.
+
+ :return a goodness_function string or None
+ """
+ ret_function = self.configuration.goodness_function
+ if not ret_function:
+ ret_function = CONF.goodness_function
+ if not ret_function:
+ ret_function = self.get_default_goodness_function()
+ return ret_function
+
+ def get_default_filter_function(self):
+ """Get the default filter_function string.
+
+ Each driver could overwrite the method to return a well-known
+ default string if it is available.
+
+ :return: None
+ """
+ return None
+
+ def get_default_goodness_function(self):
+ """Get the default goodness_function string.
+
+ Each driver could overwrite the method to return a well-known
+ default string if it is available.
+
+ :return: None
+ """
+ return None
+
def _attach_volume(self, context, volume, properties, remote=False):
"""Attach the volume."""
if remote:
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
- LOG.debug("Updating volume stats")
+ LOG.debug("Updating volume stats...")
data = {}
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = backend_name or 'Generic_iSCSI'
data["storage_protocol"] = 'iSCSI'
data["pools"] = []
- # provisioned_capacity_gb is set to None by default below, but
- # None won't be used in calculation. It will be overridden by
- # driver's provisioned_capacity_gb if reported, otherwise it
- # defaults to allocated_capacity_gb in host_manager.py.
- if self.pools:
- for pool in self.pools:
- new_pool = {}
- new_pool.update(dict(
- pool_name=pool,
- total_capacity_gb=0,
- free_capacity_gb=0,
- provisioned_capacity_gb=None,
- reserved_percentage=100,
- QoS_support=False
- ))
- data["pools"].append(new_pool)
- else:
- # No pool configured, the whole backend will be treated as a pool
- single_pool = {}
- single_pool.update(dict(
- pool_name=data["volume_backend_name"],
- total_capacity_gb=0,
- free_capacity_gb=0,
- provisioned_capacity_gb=None,
- reserved_percentage=100,
- QoS_support=False
- ))
- data["pools"].append(single_pool)
- self._stats = data
+ self._update_pools_and_stats(data)
class FakeISCSIDriver(ISCSIDriver):
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
- LOG.debug("Updating volume stats")
+ LOG.debug("Updating volume stats...")
data = {}
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = backend_name or 'Generic_iSER'
data["storage_protocol"] = 'iSER'
data["pools"] = []
- if self.pools:
- for pool in self.pools:
- new_pool = {}
- new_pool.update(dict(
- pool_name=pool,
- total_capacity_gb=0,
- free_capacity_gb=0,
- reserved_percentage=100,
- QoS_support=False
- ))
- data["pools"].append(new_pool)
- else:
- # No pool configured, the whole backend will be treated as a pool
- single_pool = {}
- single_pool.update(dict(
- pool_name=data["volume_backend_name"],
- total_capacity_gb=0,
- free_capacity_gb=0,
- reserved_percentage=100,
- QoS_support=False
- ))
- data["pools"].append(single_pool)
- self._stats = data
+ self._update_pools_and_stats(data)
class FakeISERDriver(FakeISCSIDriver):
{'setting': setting})
LOG.error(*msg)
raise exception.InvalidConnectorException(missing=setting)
+
+ def get_volume_stats(self, refresh=False):
+ """Get volume stats.
+
+ If 'refresh' is True, run update the stats first.
+ """
+ if refresh:
+ self._update_volume_stats()
+
+ return self._stats
+
+ def _update_volume_stats(self):
+ """Retrieve stats info from volume group."""
+
+ LOG.debug("Updating volume stats...")
+ data = {}
+ backend_name = self.configuration.safe_get('volume_backend_name')
+ data["volume_backend_name"] = backend_name or 'Generic_FC'
+ data["vendor_name"] = 'Open Source'
+ data["driver_version"] = '1.0'
+ data["storage_protocol"] = 'FC'
+ data["pools"] = []
+
+ self._update_pools_and_stats(data)
thin_enabled = self.configuration.lvm_type == 'thin'
+ # Calculate the total volumes used by the VG group.
+ # This includes volumes and snapshots.
+ total_volumes = len(self.vg.get_volumes())
+
# Skip enabled_pools setting, treat the whole backend as one pool
# XXX FIXME if multipool support is added to LVM driver.
single_pool = {}
self.configuration.max_over_subscription_ratio),
thin_provisioning_support=thin_enabled,
thick_provisioning_support=not thin_enabled,
+ total_volumes=total_volumes,
+ filter_function=self.get_filter_function(),
+ goodness_function=self.get_goodness_function()
))
data["pools"].append(single_pool)
2.0.35 - Fix default snapCPG for manage_existing bug #1393609
2.0.36 - Added support for dedup provisioning
2.0.37 - Added support for enabling Flash Cache
+ 2.0.38 - Add stats for hp3par goodness_function and filter_function
"""
- VERSION = "2.0.37"
+ VERSION = "2.0.38"
stats = {}
return iscsi_ports
- def get_volume_stats(self, refresh=False):
+ def get_volume_stats(self,
+ refresh,
+ filter_function=None,
+ goodness_function=None):
if refresh:
- self._update_volume_stats()
+ self._update_volume_stats(
+ filter_function=filter_function,
+ goodness_function=goodness_function)
return self.stats
- def _update_volume_stats(self):
+ def _update_volume_stats(self,
+ filter_function=None,
+ goodness_function=None):
# const to convert MiB to GB
const = 0.0009765625
pools = []
info = self.client.getStorageSystemInfo()
+
for cpg_name in self.config.hp3par_cpg:
try:
cpg = self.client.getCPG(cpg_name)
+ if 'numTDVVs' in cpg:
+ total_volumes = int(
+ cpg['numFPVVs'] + cpg['numTPVVs'] + cpg['numTDVVs']
+ )
+ else:
+ total_volumes = int(
+ cpg['numFPVVs'] + cpg['numTPVVs']
+ )
+
if 'limitMiB' not in cpg['SDGrowth']:
# cpg usable free space
- cpg_avail_space = \
- self.client.getCPGAvailableSpace(cpg_name)
+ cpg_avail_space = (
+ self.client.getCPGAvailableSpace(cpg_name))
free_capacity = int(
cpg_avail_space['usableFreeMiB'] * const)
# total_capacity is the best we can do for a limitless cpg
else:
total_capacity = int(cpg['SDGrowth']['limitMiB'] * const)
free_capacity = int((cpg['SDGrowth']['limitMiB'] -
- cpg['UsrUsage']['usedMiB']) * const)
+ (cpg['UsrUsage']['usedMiB'] +
+ cpg['SDUsage']['usedMiB'])) * const)
+ capacity_utilization = (
+ (float(total_capacity - free_capacity) /
+ float(total_capacity)) * 100)
except hpexceptions.HTTPNotFound:
err = (_("CPG (%s) doesn't exist on array")
'reserved_percentage': 0,
'location_info': ('HP3PARDriver:%(sys_id)s:%(dest_cpg)s' %
{'sys_id': info['serialNumber'],
- 'dest_cpg': cpg_name})
+ 'dest_cpg': cpg_name}),
+ 'total_volumes': total_volumes,
+ 'capacity_utilization': capacity_utilization,
+ 'filter_function': filter_function,
+ 'goodness_function': goodness_function
}
+
pools.append(pool)
self.stats = {'driver_version': '1.0',
def get_volume_stats(self, refresh=False):
common = self._login()
try:
- stats = common.get_volume_stats(refresh)
+ stats = common.get_volume_stats(
+ refresh,
+ self.get_filter_function(),
+ self.get_goodness_function())
stats['storage_protocol'] = 'FC'
stats['driver_version'] = self.VERSION
backend_name = self.configuration.safe_get('volume_backend_name')
def get_volume_stats(self, refresh=False):
common = self._login()
try:
- stats = common.get_volume_stats(refresh)
+ stats = common.get_volume_stats(
+ refresh,
+ self.get_filter_function(),
+ self.get_goodness_function())
stats['storage_protocol'] = 'iSCSI'
stats['driver_version'] = self.VERSION
backend_name = self.configuration.safe_get('volume_backend_name')