diff --git a/cinder/tests/unit/volume/drivers/test_remotefs.py b/cinder/tests/unit/volume/drivers/test_remotefs.py index 0cd2ad85578..2e1caec4500 100644 --- a/cinder/tests/unit/volume/drivers/test_remotefs.py +++ b/cinder/tests/unit/volume/drivers/test_remotefs.py @@ -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') diff --git a/cinder/volume/drivers/remotefs.py b/cinder/volume/drivers/remotefs.py index ff6d79f3eef..99481bd3480 100644 --- a/cinder/volume/drivers/remotefs.py +++ b/cinder/volume/drivers/remotefs.py @@ -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