Add RemoteFSPoolMixin

This change adds a mixin class to the remotefs module so that
any related volume driver can easily start reporting each share
as a storage pool.

Partially Implements: blueprint remotefs-pools-support

Change-Id: I52da65b1987f4ebdcb5c513f2a316bba9de8e892
This commit is contained in:
Lucian Petrut 2016-12-14 18:52:50 +02:00
parent 4287bfa9d4
commit daeab949a7
2 changed files with 160 additions and 0 deletions

View File

@ -481,3 +481,96 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
'of=/path', 'bs=1M',
'count=1024',
run_as_root=True)
class RemoteFSPoolMixinTestCase(test.TestCase):
def setUp(self):
super(RemoteFSPoolMixinTestCase, self).setUp()
# We'll instantiate this directly for now.
self._driver = remotefs.RemoteFSPoolMixin()
self.context = context.get_admin_context()
@mock.patch.object(remotefs.RemoteFSPoolMixin,
'_get_pool_name_from_volume')
@mock.patch.object(remotefs.RemoteFSPoolMixin,
'_get_share_from_pool_name')
def test_find_share(self, mock_get_share_from_pool,
mock_get_pool_from_volume):
share = self._driver._find_share(mock.sentinel.volume)
self.assertEqual(mock_get_share_from_pool.return_value, share)
mock_get_pool_from_volume.assert_called_once_with(
mock.sentinel.volume)
mock_get_share_from_pool.assert_called_once_with(
mock_get_pool_from_volume.return_value)
def test_get_pool_name_from_volume(self):
fake_pool = 'fake_pool'
fake_host = 'fake_host@fake_backend#%s' % fake_pool
fake_vol = fake_volume.fake_volume_obj(
self.context, provider_location='fake_share',
host=fake_host)
pool_name = self._driver._get_pool_name_from_volume(fake_vol)
self.assertEqual(fake_pool, pool_name)
def test_update_volume_stats(self):
share_total_gb = 3
share_free_gb = 2
share_used_gb = 4 # provisioned space
expected_allocated_gb = share_total_gb - share_free_gb
self._driver._mounted_shares = [mock.sentinel.share]
self._driver.configuration = mock.Mock()
self._driver.configuration.safe_get.return_value = (
mock.sentinel.backend_name)
self._driver.vendor_name = mock.sentinel.vendor_name
self._driver.driver_volume_type = mock.sentinel.driver_volume_type
self._driver._thin_provisioning_support = (
mock.sentinel.thin_prov_support)
self._driver.get_version = mock.Mock(
return_value=mock.sentinel.driver_version)
self._driver._ensure_shares_mounted = mock.Mock()
self._driver._get_capacity_info = mock.Mock(
return_value=(share_total_gb << 30,
share_free_gb << 30,
share_used_gb << 30))
self._driver._get_pool_name_from_share = mock.Mock(
return_value=mock.sentinel.pool_name)
expected_pool = {
'pool_name': mock.sentinel.pool_name,
'total_capacity_gb': float(share_total_gb),
'free_capacity_gb': float(share_free_gb),
'provisioned_capacity_gb': float(share_used_gb),
'allocated_capacity_gb': float(expected_allocated_gb),
'reserved_percentage': (
self._driver.configuration.reserved_percentage),
'max_over_subscription_ratio': (
self._driver.configuration.max_over_subscription_ratio),
'thin_provisioning_support': (
mock.sentinel.thin_prov_support),
'QoS_support': False,
}
expected_stats = {
'volume_backend_name': mock.sentinel.backend_name,
'vendor_name': mock.sentinel.vendor_name,
'driver_version': mock.sentinel.driver_version,
'storage_protocol': mock.sentinel.driver_volume_type,
'total_capacity_gb': 0,
'free_capacity_gb': 0,
'pools': [expected_pool],
}
self._driver._update_volume_stats()
self.assertDictEqual(expected_stats, self._driver._stats)
self._driver._get_capacity_info.assert_called_once_with(
mock.sentinel.share)
self._driver.configuration.safe_get.assert_called_once_with(
'volume_backend_name')

View File

@ -37,6 +37,7 @@ from cinder.image import image_utils
from cinder.objects import fields
from cinder import utils
from cinder.volume import driver
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
@ -139,6 +140,7 @@ class RemoteFSDriver(driver.BaseVD):
driver_volume_type = None
driver_prefix = 'remotefs'
volume_backend_name = None
vendor_name = 'Open Source'
SHARE_FORMAT_REGEX = r'.+:/.+'
def __init__(self, *args, **kwargs):
@ -149,6 +151,10 @@ class RemoteFSDriver(driver.BaseVD):
self._is_voldb_empty_at_startup = kwargs.pop('is_vol_db_empty', None)
self._supports_encryption = False
# We let the drivers inheriting this specify
# whether thin provisioning is supported or not.
self._thin_provisioning_support = False
if self.configuration:
self.configuration.append_config_values(nas_opts)
self.configuration.append_config_values(volume_opts)
@ -1579,3 +1585,64 @@ class RemoteFSSnapDriverDistributed(RemoteFSSnapDriverBase):
return self._copy_volume_to_image(context, volume, image_service,
image_meta)
class RemoteFSPoolMixin(object):
"""Drivers inheriting this will report each share as a pool."""
def _find_share(self, volume):
# We let the scheduler choose a pool for us.
pool_name = self._get_pool_name_from_volume(volume)
share = self._get_share_from_pool_name(pool_name)
return share
def _get_pool_name_from_volume(self, volume):
pool_name = volume_utils.extract_host(volume['host'],
level='pool')
return pool_name
def _get_pool_name_from_share(self, share):
raise NotImplementedError()
def _get_share_from_pool_name(self, pool_name):
# To be implemented by drivers using pools.
raise NotImplementedError()
def _update_volume_stats(self):
data = {}
pools = []
backend_name = self.configuration.safe_get('volume_backend_name')
data['volume_backend_name'] = backend_name or self.volume_backend_name
data['vendor_name'] = self.vendor_name
data['driver_version'] = self.get_version()
data['storage_protocol'] = self.driver_volume_type
self._ensure_shares_mounted()
for share in self._mounted_shares:
(share_capacity,
share_free,
share_used) = self._get_capacity_info(share)
pool = {'pool_name': self._get_pool_name_from_share(share),
'total_capacity_gb': share_capacity / float(units.Gi),
'free_capacity_gb': share_free / float(units.Gi),
'provisioned_capacity_gb': share_used / float(units.Gi),
'allocated_capacity_gb': (
share_capacity - share_free) / float(units.Gi),
'reserved_percentage': (
self.configuration.reserved_percentage),
'max_over_subscription_ratio': (
self.configuration.max_over_subscription_ratio),
'thin_provisioning_support': (
self._thin_provisioning_support),
'QoS_support': False,
}
pools.append(pool)
data['total_capacity_gb'] = 0
data['free_capacity_gb'] = 0
data['pools'] = pools
self._stats = data