Add support to the NFS drivers for over-subscription/overprovisioning.
This allows the Cinder scheduler to more accurately represent the
remaining space on a pool when thin-provisioned Cinder Volumes are in
use on an NFS backend.
DocImpact
Co-Authored-By: Clinton Knight <cknight@netapp.com>
Co-Authored-By: Mike Rooney <rooneym@netapp.com>
Implements: blueprint nfs-over-subscription-thin-provisioning
Change-Id: Ib94d5131554466709e168c18d1b8690dddd55ff0
self.shares_file.flush()
self.configuration = mock.Mock(spec=conf.Configuration)
+ self.configuration.max_over_subscription_ratio = 20.0
+ self.configuration.reserved_percentage = 0
self.configuration.hds_hnas_nfs_config_file = self.config_file.name
self.configuration.nfs_shares_config = self.shares_file.name
self.configuration.nfs_mount_point_base = '/opt/stack/cinder/mnt'
self.configuration.nas_ip = None
self.configuration.nas_share_path = None
self.configuration.nas_mount_options = None
+ self.configuration.nfs_used_ratio = .95
+ self.configuration.nfs_oversub_ratio = 1.0
self.driver = nfs.HDSNFSDriver(configuration=self.configuration)
self.driver.do_setup("")
def create_configuration():
configuration = mox_lib.MockObject(conf.Configuration)
configuration.append_config_values(mox_lib.IgnoreArg())
+ configuration.max_over_subscription_ratio = 20.0
+ configuration.reserved_percentage = 0
configuration.nfs_mount_point_base = '/mnt/test'
configuration.nfs_mount_options = None
configuration.nas_mount_options = None
+ configuration.nfs_used_ratio = .95
+ configuration.nfs_oversub_ratio = 1.0
configuration.netapp_server_hostname = CONNECTION_INFO['hostname']
configuration.netapp_transport_type = CONNECTION_INFO['transport_type']
configuration.netapp_server_port = CONNECTION_INFO['port']
# under the License.
"""Unit tests for the NFS driver module."""
+import ddt
import errno
import os
self.configuration.append_config_values(mox_lib.IgnoreArg())
self.configuration.nas_secure_file_permissions = 'false'
self.configuration.nas_secure_file_operations = 'false'
+ self.configuration.max_over_subscription_ratio = 1.0
+ self.configuration.reserved_percentage = 5
self._driver = remotefs.RemoteFSDriver(
configuration=self.configuration)
self.assertFalse(ret_flag)
+@ddt.ddt
class NfsDriverTestCase(test.TestCase):
"""Test case for NFS driver."""
self.stubs = stubout.StubOutForTesting()
self.configuration = mox_lib.MockObject(conf.Configuration)
self.configuration.append_config_values(mox_lib.IgnoreArg())
+ self.configuration.max_over_subscription_ratio = 1.0
+ self.configuration.reserved_percentage = 5
self.configuration.nfs_shares_config = None
self.configuration.nfs_sparsed_volumes = True
self.configuration.nfs_used_ratio = 0.95
self.configuration.nfs_oversub_ratio = 1.0
+ self.configuration.nfs_reserved_percentage = 5.0
self.configuration.nfs_mount_point_base = self.TEST_MNT_POINT_BASE
self.configuration.nfs_mount_options = None
self.configuration.nfs_mount_attempts = 3
self.configuration.nas_ip = None
self.configuration.nas_share_path = None
self.configuration.nas_mount_options = None
- self.configuration.reserved_percentage = 0
self.configuration.volume_dd_blocksize = '1M'
self._driver = nfs.NfsDriver(configuration=self.configuration)
self._driver.shares = {}
self.assertEqual(1, remotefs.LOG.error.call_count)
- def test_find_share_should_throw_error_if_there_is_no_mounted_shares(self):
+ def test_find_share_should_throw_error_if_there_is_no_mounted_share(self):
"""_find_share should throw error if there is no mounted shares."""
drv = self._driver
mox.VerifyAll()
- def test_find_share_should_throw_error_if_there_is_no_enough_place(self):
+ def test_find_share_should_throw_error_if_there_is_not_enough_space(self):
"""_find_share should throw error if there is no share to host vol."""
mox = self.mox
drv = self._driver
drv.get_volume_stats()
self.assertEqual(30.0, drv._stats['total_capacity_gb'])
self.assertEqual(5.0, drv._stats['free_capacity_gb'])
- self.assertEqual(0, drv._stats['reserved_percentage'])
+ self.assertEqual(5, drv._stats['reserved_percentage'])
self.assertTrue(drv._stats['sparse_copy_volume'])
mox.VerifyAll()
def test_get_volume_stats_with_non_zero_reserved_percentage(self):
"""get_volume_stats must fill the correct values."""
mox = self.mox
- drv = self._driver
self.configuration.reserved_percentage = 10.0
+ drv = nfs.NfsDriver(configuration=self.configuration)
drv._mounted_shares = [self.TEST_NFS_EXPORT1, self.TEST_NFS_EXPORT2]
self.assertEqual(10.0, drv._stats['reserved_percentage'])
mox.VerifyAll()
+ @ddt.data(True, False)
+ def test_update_volume_stats(self, thin):
+
+ self._driver.over_subscription_ratio = 20.0
+ self._driver.reserved_percentage = 5.0
+ self._driver.configuration.nfs_sparsed_volumes = thin
+
+ remotefs_volume_stats = {
+ 'volume_backend_name': 'fake_backend_name',
+ 'vendor_name': 'fake_vendor',
+ 'driver_version': 'fake_version',
+ 'storage_protocol': 'NFS',
+ 'total_capacity_gb': 100.0,
+ 'free_capacity_gb': 20.0,
+ 'reserved_percentage': 5.0,
+ 'QoS_support': False,
+ }
+ self.mock_object(remotefs.RemoteFSDriver, '_update_volume_stats')
+ self._driver._stats = remotefs_volume_stats
+
+ mock_get_provisioned_capacity = self.mock_object(
+ self._driver, '_get_provisioned_capacity',
+ mock.Mock(return_value=25.0))
+
+ self._driver._update_volume_stats()
+
+ nfs_added_volume_stats = {
+ 'provisioned_capacity_gb': 25.0 if thin else 80.0,
+ 'max_over_subscription_ratio': 20.0,
+ 'reserved_percentage': 5.0,
+ 'thin_provisioning_support': thin,
+ 'thick_provisioning_support': not thin,
+ }
+ expected = remotefs_volume_stats
+ expected.update(nfs_added_volume_stats)
+
+ self.assertEqual(expected, self._driver._stats)
+ self.assertEqual(thin, mock_get_provisioned_capacity.called)
+
+ @ddt.data({'nfs_oversub_ratio': 1.0},
+ {'nfs_oversub_ratio': 1.5})
+ @ddt.unpack
+ def test_get_over_subscription_ratio(self, nfs_oversub_ratio):
+ self.configuration.nfs_oversub_ratio = nfs_oversub_ratio
+ self._driver.configuration = self.configuration
+ self.mock_object(nfs, 'LOG')
+
+ oversub_ratio = self._driver._get_over_subscription_ratio()
+
+ if nfs_oversub_ratio == 1.0:
+ self.assertEqual(1.0, oversub_ratio)
+ self.assertFalse(nfs.LOG.warn.called)
+ else:
+ self.assertEqual(nfs_oversub_ratio, oversub_ratio)
+ self.assertTrue(nfs.LOG.warn.called)
+
+ @ddt.data({'nfs_used_ratio': 0.95, 'nfs_reserved_percentage': 0.05},
+ {'nfs_used_ratio': 0.80, 'nfs_reserved_percentage': 0.20})
+ @ddt.unpack
+ def test_get_reserved_percentage(self, nfs_used_ratio,
+ nfs_reserved_percentage):
+ self.configuration.nfs_used_ratio = nfs_used_ratio
+ self.configuration.nfs_reserved_percentage = nfs_reserved_percentage
+ self._driver.configuration = self.configuration
+ self.mock_object(nfs, 'LOG')
+
+ reserved_percentage = self._driver._get_reserved_percentage()
+
+ if nfs_used_ratio == 0.95:
+ self.assertEqual(self._driver.configuration.reserved_percentage,
+ reserved_percentage)
+ self.assertFalse(nfs.LOG.warn.called)
+ else:
+ expected = (1 - self._driver.configuration.nfs_used_ratio) * 100
+ self.assertEqual(expected, reserved_percentage)
+ self.assertTrue(nfs.LOG.warn.called)
+
def _check_is_share_eligible(self, total_size, total_available,
total_allocated, requested_volume_size):
with mock.patch.object(self._driver, '_get_capacity_info')\
config.append_config_values(nfs.nfs_opts)
self.configuration = config
- def test_setup_should_throw_error_if_shares_config_not_configured(self):
- """do_setup should throw error if shares config is not configured."""
-
- self.override_config('nfs_shares_config', None)
- drv = nfs.NfsDriver(configuration=self.configuration)
-
- mock_os_path_exists = self.mock_object(os.path, 'exists')
-
- with self.assertRaisesRegex(exception.NfsException,
- ".*no NFS config file configured.*"):
- drv.do_setup(self.context)
-
- self.assertEqual(0, mock_os_path_exists.call_count)
-
- def test_setup_should_throw_error_if_shares_file_does_not_exist(self):
- """do_setup should throw error if shares file does not exist."""
-
- drv = nfs.NfsDriver(configuration=self.configuration)
-
- mock_os_path_exists = self.mock_object(os.path, 'exists')
- mock_os_path_exists.return_value = False
-
- with self.assertRaisesRegex(exception.NfsException,
- "NFS config file.*doesn't exist"):
- drv.do_setup(self.context)
-
- mock_os_path_exists.assert_has_calls(
- [mock.call(self.configuration.nfs_shares_config)])
-
- def test_setup_should_throw_error_if_oversub_ratio_less_than_zero(self):
- """do_setup should throw error if nfs_oversub_ratio is less than 0."""
+ def test_init_should_throw_error_if_oversub_ratio_less_than_zero(self):
+ """__init__ should throw error if nfs_oversub_ratio is less than 0."""
self.override_config('nfs_oversub_ratio', -1)
- drv = nfs.NfsDriver(configuration=self.configuration)
-
- mock_os_path_exists = self.mock_object(os.path, 'exists')
- mock_os_path_exists.return_value = True
with self.assertRaisesRegex(exception.InvalidConfigurationValue,
".*'nfs_oversub_ratio' invalid.*"):
- drv.do_setup(self.context)
+ nfs.NfsDriver(configuration=self.configuration)
- mock_os_path_exists.assert_has_calls(
- [mock.call(self.configuration.nfs_shares_config)])
+ def test_init_should_throw_error_if_used_ratio_less_than_zero(self):
+ """__init__ should throw error if nfs_used_ratio is less than 0."""
- def test_setup_oversub_ratio_default_value(self):
- """do_setup should work with default value for nfs_oversub_ratio."""
-
- drv = nfs.NfsDriver(configuration=self.configuration)
-
- mock_os_path_exists = self.mock_object(os.path, 'exists')
- mock_os_path_exists.return_value = True
- mock_execute = self.mock_object(drv, '_execute')
- mock_set_security = self.mock_object(drv, 'set_nas_security_options')
-
- drv.do_setup(self.context)
-
- mock_os_path_exists.assert_has_calls(
- [mock.call(self.configuration.nfs_shares_config)])
- self.assertEqual(1, mock_execute.call_count)
- self.assertEqual(1, mock_set_security.call_count)
- self.assertEqual(1.0, self.configuration.nfs_oversub_ratio)
+ self.override_config('nfs_used_ratio', -1)
- def test_setup_should_throw_error_if_used_ratio_less_than_zero(self):
- """do_setup should throw error if nfs_used_ratio is less than 0."""
+ with self.assertRaisesRegex(exception.InvalidConfigurationValue,
+ ".*'nfs_used_ratio' invalid.*"):
+ nfs.NfsDriver(configuration=self.configuration)
- self.override_config('nfs_used_ratio', -1)
- drv = nfs.NfsDriver(configuration=self.configuration)
+ def test_init_should_throw_error_if_used_ratio_greater_than_one(self):
+ """__init__ should throw error if nfs_used_ratio is greater than 1."""
- mock_os_path_exists = self.mock_object(os.path, 'exists')
- mock_os_path_exists.return_value = True
+ self.override_config('nfs_used_ratio', 2)
with self.assertRaisesRegex(exception.InvalidConfigurationValue,
".*'nfs_used_ratio' invalid.*"):
- drv.do_setup(self.context)
-
- mock_os_path_exists.assert_has_calls(
- [mock.call(self.configuration.nfs_shares_config)])
+ nfs.NfsDriver(configuration=self.configuration)
- def test_setup_should_throw_error_if_used_ratio_greater_than_one(self):
- """do_setup should throw error if nfs_used_ratio is greater than 1."""
+ def test_setup_should_throw_error_if_shares_config_not_configured(self):
+ """do_setup should throw error if shares config is not configured."""
- self.override_config('nfs_used_ratio', 2)
+ self.override_config('nfs_shares_config', None)
drv = nfs.NfsDriver(configuration=self.configuration)
mock_os_path_exists = self.mock_object(os.path, 'exists')
- mock_os_path_exists.return_value = True
- with self.assertRaisesRegex(exception.InvalidConfigurationValue,
- ".*'nfs_used_ratio' invalid.*"):
+ with self.assertRaisesRegex(exception.NfsException,
+ ".*no NFS config file configured.*"):
drv.do_setup(self.context)
- mock_os_path_exists.assert_has_calls(
- [mock.call(self.configuration.nfs_shares_config)])
+ self.assertEqual(0, mock_os_path_exists.call_count)
- def test_setup_used_ratio_default_value(self):
- """do_setup should work with default value for nfs_used_ratio."""
+ def test_setup_should_throw_error_if_shares_file_does_not_exist(self):
+ """do_setup should throw error if shares file does not exist."""
drv = nfs.NfsDriver(configuration=self.configuration)
mock_os_path_exists = self.mock_object(os.path, 'exists')
- mock_os_path_exists.return_value = True
- mock_execute = self.mock_object(drv, '_execute')
- mock_set_security = self.mock_object(drv, 'set_nas_security_options')
+ mock_os_path_exists.return_value = False
- drv.do_setup(self.context)
+ with self.assertRaisesRegex(exception.NfsException,
+ "NFS config file.*doesn't exist"):
+ drv.do_setup(self.context)
mock_os_path_exists.assert_has_calls(
[mock.call(self.configuration.nfs_shares_config)])
- self.assertEqual(1, mock_execute.call_count)
- self.assertEqual(1, mock_set_security.call_count)
- self.assertEqual(0.95, self.configuration.nfs_used_ratio)
def test_setup_should_throw_exception_if_nfs_client_is_not_installed(self):
"""do_setup should throw error if nfs client is not installed."""
configuration.nfs_mount_point_base = '/mnt/test'
configuration.nfs_mount_options = None
configuration.nas_mount_options = None
+ configuration.nfs_used_ratio = 0.95
return configuration
def fake_stubs(self):
def _create_fake_config(self):
self.configuration = mock.Mock(spec=conf.Configuration)
+ self.configuration.reserved_percentage = 0
+ self.configuration.max_over_subscription_ratio = 20.0
self.configuration.san_ip = '1.1.1.1'
self.configuration.san_login = 'user'
self.configuration.san_password = 'passwd'
NFS_SHARE_IPV6 = IPV6_ADDRESS + ':' + EXPORT_PATH
RESERVED_PERCENTAGE = 7
+MAX_OVER_SUBSCRIPTION_RATIO = 19.0
TOTAL_BYTES = 4797892092432
AVAILABLE_BYTES = 13479932478
CAPACITY_VALUES = (TOTAL_BYTES, AVAILABLE_BYTES)
Unit tests for the NetApp 7mode NFS storage driver
"""
+import ddt
import mock
from os_brick.remotefs import remotefs as remotefs_brick
from oslo_utils import units
from cinder.volume.drivers.netapp import utils as na_utils
+@ddt.ddt
class NetApp7modeNfsDriverTestCase(test.TestCase):
def setUp(self):
super(NetApp7modeNfsDriverTestCase, self).setUp()
config.netapp_server_port = '80'
return config
- def test_get_pool_stats(self):
+ @ddt.data({'nfs_sparsed_volumes': True},
+ {'nfs_sparsed_volumes': False})
+ @ddt.unpack
+ def test_get_pool_stats(self, nfs_sparsed_volumes):
+
+ self.driver.configuration.nfs_sparsed_volumes = nfs_sparsed_volumes
+ thick = not nfs_sparsed_volumes
total_capacity_gb = na_utils.round_down(
fake.TOTAL_BYTES / units.Gi, '0.01')
free_capacity_gb = na_utils.round_down(
fake.AVAILABLE_BYTES / units.Gi, '0.01')
- capacity = dict(
- reserved_percentage = fake.RESERVED_PERCENTAGE,
- total_capacity_gb = total_capacity_gb,
- free_capacity_gb = free_capacity_gb,
- )
-
- mock_get_capacity = self.mock_object(
- self.driver, '_get_share_capacity_info')
- mock_get_capacity.return_value = capacity
+ provisioned_capacity_gb = total_capacity_gb - free_capacity_gb
+ capacity = {
+ 'reserved_percentage': fake.RESERVED_PERCENTAGE,
+ 'max_over_subscription_ratio': fake.MAX_OVER_SUBSCRIPTION_RATIO,
+ 'total_capacity_gb': total_capacity_gb,
+ 'free_capacity_gb': free_capacity_gb,
+ 'provisioned_capacity_gb': provisioned_capacity_gb,
+ }
+ self.mock_object(self.driver,
+ '_get_share_capacity_info',
+ mock.Mock(return_value=capacity))
result = self.driver._get_pool_stats()
- self.assertEqual(fake.RESERVED_PERCENTAGE,
- result[0]['reserved_percentage'])
- self.assertEqual(total_capacity_gb, result[0]['total_capacity_gb'])
- self.assertEqual(free_capacity_gb, result[0]['free_capacity_gb'])
+ expected = [{'pool_name': '192.168.99.24:/fake/export/path',
+ 'QoS_support': False,
+ 'thick_provisioning_support': thick,
+ 'thin_provisioning_support': not thick,
+ 'free_capacity_gb': 12.0,
+ 'total_capacity_gb': 4468.0,
+ 'reserved_percentage': 7,
+ 'max_over_subscription_ratio': 19.0,
+ 'provisioned_capacity_gb': 4456.0}]
+
+ self.assertEqual(expected, result)
def test_shortlist_del_eligible_files(self):
mock_get_path_for_export = self.mock_object(
def setUp(self):
super(NetAppNfsDriverTestCase, self).setUp()
configuration = mock.Mock()
+ configuration.reserved_percentage = 0
configuration.nfs_mount_point_base = '/mnt/test'
configuration.nfs_used_ratio = 0.89
configuration.nfs_oversub_ratio = 3.0
def test_get_share_capacity_info(self):
mock_get_capacity = self.mock_object(self.driver, '_get_capacity_info')
mock_get_capacity.return_value = fake.CAPACITY_VALUES
- expected_total_capacity_gb = (na_utils.round_down(
- (fake.TOTAL_BYTES *
- self.driver.configuration.nfs_oversub_ratio) /
- units.Gi, '0.01'))
+ expected_total_capacity_gb = na_utils.round_down(
+ fake.TOTAL_BYTES / units.Gi, '0.01')
expected_free_capacity_gb = (na_utils.round_down(
- (fake.AVAILABLE_BYTES *
- self.driver.configuration.nfs_oversub_ratio) /
- units.Gi, '0.01'))
+ fake.AVAILABLE_BYTES / units.Gi, '0.01'))
expected_reserved_percentage = round(
100 * (1 - self.driver.configuration.nfs_used_ratio))
self.assertEqual(expected_free_capacity_gb,
result['free_capacity_gb'])
self.assertEqual(expected_reserved_percentage,
- result['reserved_percentage'])
+ round(result['reserved_percentage']))
def test_get_capacity_info_ipv4_share(self):
expected = fake.CAPACITY_VALUES
Mock unit tests for the NetApp cmode nfs storage driver
"""
+import ddt
import mock
from os_brick.remotefs import remotefs as remotefs_brick
from oslo_service import loopingcall
from cinder.volume import utils as volume_utils
+@ddt.ddt
class NetAppCmodeNfsDriverTestCase(test.TestCase):
def setUp(self):
super(NetAppCmodeNfsDriverTestCase, self).setUp()
self.assertTrue(mock_check_flags.called)
self.assertTrue(mock_super_do_setup.called)
- def test_get_pool_stats(self):
+ @ddt.data({'thin': True, 'nfs_sparsed_volumes': True},
+ {'thin': True, 'nfs_sparsed_volumes': False},
+ {'thin': False, 'nfs_sparsed_volumes': True},
+ {'thin': False, 'nfs_sparsed_volumes': False})
+ @ddt.unpack
+ def test_get_pool_stats(self, thin, nfs_sparsed_volumes):
+
+ class test_volume(object):
+ pass
+
+ test_volume = test_volume()
+ test_volume.id = {'vserver': 'openstack', 'name': 'vola'}
+ test_volume.aggr = {
+ 'disk_type': 'SSD',
+ 'ha_policy': 'cfo',
+ 'junction': '/vola',
+ 'name': 'aggr1',
+ 'raid_type': 'raiddp',
+ }
+ test_volume.export = {'path': fake.NFS_SHARE}
+ test_volume.sis = {'dedup': False, 'compression': False}
+ test_volume.state = {
+ 'status': 'online',
+ 'vserver_root': False,
+ 'junction_active': True,
+ }
+ test_volume.qos = {'qos_policy_group': None}
+
+ ssc_map = {
+ 'mirrored': {},
+ 'dedup': {},
+ 'compression': {},
+ 'thin': {test_volume if thin else None},
+ 'all': [test_volume],
+ }
+ self.driver.ssc_vols = ssc_map
+
+ self.driver.configuration.nfs_sparsed_volumes = nfs_sparsed_volumes
+
+ netapp_thin = 'true' if thin else 'false'
+ netapp_thick = 'false' if thin else 'true'
+
+ thick = not thin and not nfs_sparsed_volumes
total_capacity_gb = na_utils.round_down(
fake.TOTAL_BYTES / units.Gi, '0.01')
free_capacity_gb = na_utils.round_down(
fake.AVAILABLE_BYTES / units.Gi, '0.01')
- capacity = dict(
- reserved_percentage = fake.RESERVED_PERCENTAGE,
- total_capacity_gb = total_capacity_gb,
- free_capacity_gb = free_capacity_gb,
- )
-
- mock_get_capacity = self.mock_object(
- self.driver, '_get_share_capacity_info')
- mock_get_capacity.return_value = capacity
-
- mock_get_vol_for_share = self.mock_object(
- self.driver, '_get_vol_for_share')
- mock_get_vol_for_share.return_value = None
+ provisioned_capacity_gb = total_capacity_gb - free_capacity_gb
+ capacity = {
+ 'reserved_percentage': fake.RESERVED_PERCENTAGE,
+ 'max_over_subscription_ratio': fake.MAX_OVER_SUBSCRIPTION_RATIO,
+ 'total_capacity_gb': total_capacity_gb,
+ 'free_capacity_gb': free_capacity_gb,
+ 'provisioned_capacity_gb': provisioned_capacity_gb,
+ }
+ self.mock_object(self.driver,
+ '_get_share_capacity_info',
+ mock.Mock(return_value=capacity))
result = self.driver._get_pool_stats()
- self.assertEqual(fake.RESERVED_PERCENTAGE,
- result[0]['reserved_percentage'])
- self.assertEqual(total_capacity_gb, result[0]['total_capacity_gb'])
- self.assertEqual(free_capacity_gb, result[0]['free_capacity_gb'])
+ expected = [{'pool_name': '192.168.99.24:/fake/export/path',
+ 'netapp_unmirrored': 'true',
+ 'QoS_support': True,
+ 'thick_provisioning_support': thick,
+ 'netapp_thick_provisioned': netapp_thick,
+ 'netapp_nocompression': 'true',
+ 'thin_provisioning_support': not thick,
+ 'free_capacity_gb': 12.0,
+ 'netapp_thin_provisioned': netapp_thin,
+ 'total_capacity_gb': 4468.0,
+ 'netapp_compression': 'false',
+ 'netapp_mirrored': 'false',
+ 'netapp_dedup': 'false',
+ 'reserved_percentage': 7,
+ 'netapp_raid_type': 'raiddp',
+ 'netapp_disk_type': 'SSD',
+ 'netapp_nodedup': 'true',
+ 'reserved_percentage': 7,
+ 'max_over_subscription_ratio': 19.0,
+ 'provisioned_capacity_gb': 4456.0}]
+
+ self.assertEqual(expected, result)
def test_check_for_setup_error(self):
super_check_for_setup_error = self.mock_object(
pool['QoS_support'] = False
pool.update(capacity)
+ thick = not self.configuration.nfs_sparsed_volumes
+ pool['thick_provisioning_support'] = thick
+ pool['thin_provisioning_support'] = not thick
+
pools.append(pool)
return pools
def _get_share_capacity_info(self, nfs_share):
"""Returns the share capacity metrics needed by the scheduler."""
- used_ratio = self.configuration.nfs_used_ratio
- oversub_ratio = self.configuration.nfs_oversub_ratio
-
- # The scheduler's capacity filter will reduce the amount of
- # free space that we report to it by the reserved percentage.
- reserved_ratio = 1 - used_ratio
- reserved_percentage = round(100 * reserved_ratio)
-
- total_size, total_available = self._get_capacity_info(nfs_share)
-
- apparent_size = total_size * oversub_ratio
- apparent_size_gb = na_utils.round_down(
- apparent_size / units.Gi, '0.01')
-
- apparent_free_size = total_available * oversub_ratio
- apparent_free_gb = na_utils.round_down(
- float(apparent_free_size) / units.Gi, '0.01')
-
capacity = dict()
- capacity['reserved_percentage'] = reserved_percentage
- capacity['total_capacity_gb'] = apparent_size_gb
- capacity['free_capacity_gb'] = apparent_free_gb
+ capacity['reserved_percentage'] = self.reserved_percentage
+ capacity['max_over_subscription_ratio'] = self.over_subscription_ratio
+ total_size, total_available = self._get_capacity_info(nfs_share)
+ capacity['total_capacity_gb'] = na_utils.round_down(
+ total_size / units.Gi, '0.01')
+ capacity['free_capacity_gb'] = na_utils.round_down(
+ total_available / units.Gi, '0.01')
+ capacity['provisioned_capacity_gb'] = (round(
+ capacity['total_capacity_gb'] - capacity['free_capacity_gb'], 2))
return capacity
pool['netapp_nocompression'] = six.text_type(
not compression).lower()
- thin = vol in self.ssc_vols['thin']
- pool['netapp_thin_provisioned'] = six.text_type(thin).lower()
+ flexvol_thin = vol in self.ssc_vols['thin']
+ pool['netapp_thin_provisioned'] = six.text_type(
+ flexvol_thin).lower()
pool['netapp_thick_provisioned'] = six.text_type(
- not thin).lower()
+ not flexvol_thin).lower()
+
+ thick = (not flexvol_thin and
+ not self.configuration.nfs_sparsed_volumes)
+ pool['thick_provisioning_support'] = thick
+ pool['thin_provisioning_support'] = not thick
pools.append(pool)
from oslo_concurrency import processutils as putils
from oslo_config import cfg
from oslo_log import log as logging
+from oslo_log import versionutils
from oslo_utils import units
import six
LOG = logging.getLogger(__name__)
+NFS_USED_RATIO_DEFAULT = 0.95
+NFS_OVERSUB_RATIO_DEFAULT = 1.0
+
nfs_opts = [
cfg.StrOpt('nfs_shares_config',
default='/etc/cinder/nfs_shares',
help=('Create volumes as sparsed files which take no space.'
'If set to False volume is created as regular file.'
'In such case volume creation takes a lot of time.')),
+ # TODO(tbarron): remove nfs_used_ratio in the Mitaka release.
cfg.FloatOpt('nfs_used_ratio',
- default=0.95,
+ default=NFS_USED_RATIO_DEFAULT,
help=('Percent of ACTUAL usage of the underlying volume '
'before no new volumes can be allocated to the volume '
- 'destination.')),
+ 'destination. Note that this option is deprecated '
+ 'in favor of "reserved_percentage" and will be removed '
+ 'in the Mitaka release.')),
+ # TODO(tbarron): remove nfs_oversub_ratio in the Mitaka release.
cfg.FloatOpt('nfs_oversub_ratio',
- default=1.0,
+ default=NFS_OVERSUB_RATIO_DEFAULT,
help=('This will compare the allocated to available space on '
'the volume destination. If the ratio exceeds this '
- 'number, the destination will no longer be valid.')),
+ 'number, the destination will no longer be valid. '
+ 'Note that this option is deprecated in favor of '
+ '"max_oversubscription_ratio" and will be removed '
+ 'in the Mitaka release.')),
cfg.StrOpt('nfs_mount_point_base',
default='$state_path/mnt',
help=('Base dir containing mount points for nfs shares.')),
nfs_mount_point_base=self.base,
nfs_mount_options=opts)
+ self._sparse_copy_volume_data = True
+ self.reserved_percentage = self._get_reserved_percentage()
+ self.over_subscription_ratio = self._get_over_subscription_ratio()
+
def set_execute(self, execute):
super(NfsDriver, self).set_execute(execute)
if self._remotefsclient:
{'config': config})
LOG.warning(msg)
raise exception.NfsException(msg)
- if not self.configuration.nfs_oversub_ratio > 0:
- msg = _("NFS config 'nfs_oversub_ratio' invalid. Must be > 0: "
- "%s") % self.configuration.nfs_oversub_ratio
-
- LOG.error(msg)
- raise exception.InvalidConfigurationValue(msg)
- if not ((self.configuration.nfs_used_ratio > 0) and
- (self.configuration.nfs_used_ratio <= 1)):
- msg = _("NFS config 'nfs_used_ratio' invalid. Must be > 0 "
- "and <= 1.0: %s") % self.configuration.nfs_used_ratio
- LOG.error(msg)
- raise exception.InvalidConfigurationValue(msg)
self.shares = {} # address : options
:param nfs_share: nfs share
:param volume_size_in_gib: int size in GB
"""
-
- used_ratio = self.configuration.nfs_used_ratio
- oversub_ratio = self.configuration.nfs_oversub_ratio
+ # Because the generic NFS driver aggregates over all shares
+ # when reporting capacity and usage stats to the scheduler,
+ # we still have to perform some scheduler-like capacity
+ # checks here, and these have to take into account
+ # configuration for reserved space and oversubscription.
+ # It would be better to do all this in the scheduler, but
+ # this requires either pool support for the generic NFS
+ # driver or limiting each NFS backend driver to a single share.
+
+ # 'nfs_used_ratio' is deprecated, so derive used_ratio from
+ # reserved_percentage.
+ used_percentage = 100 - self.reserved_percentage
+ used_ratio = used_percentage / 100.0
+
+ oversub_ratio = self.over_subscription_ratio
requested_volume_size = volume_size_in_gib * units.Gi
total_size, total_available, total_allocated = \
self._get_capacity_info(nfs_share)
apparent_size = max(0, total_size * oversub_ratio)
apparent_available = max(0, apparent_size - total_allocated)
- used = (total_size - total_available) / total_size
- if used > used_ratio:
+
+ actual_used_ratio = (total_size - total_available) / float(total_size)
+ if actual_used_ratio > used_ratio:
# NOTE(morganfainberg): We check the used_ratio first since
# with oversubscription it is possible to not have the actual
# available space but be within our oversubscription limit
super(NfsDriver, self)._update_volume_stats()
self._stats['sparse_copy_volume'] = True
+ data = self._stats
+
+ global_capacity = data['total_capacity_gb']
+ global_free = data['free_capacity_gb']
+
+ thin_enabled = self.configuration.nfs_sparsed_volumes
+ if thin_enabled:
+ provisioned_capacity = self._get_provisioned_capacity()
+ else:
+ provisioned_capacity = round(global_capacity - global_free, 2)
+
+ data['provisioned_capacity_gb'] = provisioned_capacity
+ data['max_over_subscription_ratio'] = self.over_subscription_ratio
+ data['reserved_percentage'] = self.reserved_percentage
+ data['thin_provisioning_support'] = thin_enabled
+ data['thick_provisioning_support'] = not thin_enabled
+
+ self._stats = data
+
+ def _get_over_subscription_ratio(self):
+ legacy_oversub_ratio = self.configuration.nfs_oversub_ratio
+ if legacy_oversub_ratio == NFS_OVERSUB_RATIO_DEFAULT:
+ return self.configuration.max_over_subscription_ratio
+
+ # Honor legacy option if its value is not the default.
+ msg = _LW("The option 'nfs_oversub_ratio' is deprecated and will "
+ "be removed in the Mitaka release. Please set "
+ "'max_over_subscription_ratio = %s' instead.") % (
+ self.configuration.nfs_oversub_ratio)
+ versionutils.report_deprecated_feature(LOG, msg)
+
+ if not self.configuration.nfs_oversub_ratio > 0:
+ msg = _("NFS config 'nfs_oversub_ratio' invalid. Must be > 0: "
+ "%s.") % self.configuration.nfs_oversub_ratio
+ LOG.error(msg)
+ raise exception.InvalidConfigurationValue(msg)
+
+ return legacy_oversub_ratio
+
+ def _get_reserved_percentage(self):
+ legacy_used_ratio = self.configuration.nfs_used_ratio
+ legacy_reserved_ratio = 1 - legacy_used_ratio
+ legacy_percentage = legacy_reserved_ratio * 100
+ if legacy_used_ratio == NFS_USED_RATIO_DEFAULT:
+ return self.configuration.reserved_percentage
+
+ # Honor legacy option if its value is not the default.
+ msg = _LW("The option 'nfs_used_ratio' is deprecated and will "
+ "be removed in the Mitaka release. Please set "
+ "'reserved_percentage = %d' instead.") % (
+ legacy_percentage)
+ versionutils.report_deprecated_feature(LOG, msg)
+
+ if not ((self.configuration.nfs_used_ratio > 0) and
+ (self.configuration.nfs_used_ratio <= 1)):
+ msg = _("NFS config 'nfs_used_ratio' invalid. Must be > 0 "
+ "and <= 1.0: %s.") % self.configuration.nfs_used_ratio
+ LOG.error(msg)
+ raise exception.InvalidConfigurationValue(msg)
+
+ return legacy_percentage