]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Scheduler-based over-subscription for NFS drivers
authorTom Barron <tpb@dyncloud.net>
Mon, 17 Aug 2015 18:08:28 +0000 (14:08 -0400)
committerTom Barron <tpb@dyncloud.net>
Wed, 2 Sep 2015 00:54:11 +0000 (00:54 +0000)
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

13 files changed:
cinder/tests/unit/test_hitachi_hnas_nfs.py
cinder/tests/unit/test_netapp_nfs.py
cinder/tests/unit/test_nfs.py
cinder/tests/unit/test_tintri.py
cinder/tests/unit/test_zfssa.py
cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py
cinder/volume/drivers/netapp/dataontap/nfs_7mode.py
cinder/volume/drivers/netapp/dataontap/nfs_base.py
cinder/volume/drivers/netapp/dataontap/nfs_cmode.py
cinder/volume/drivers/nfs.py

index db1c3e75a3f166834938317a278024ba0cc382a3..0277477cb912578b07187e2ef86ac978f5ca07f7 100644 (file)
@@ -182,6 +182,8 @@ class HDSNFSDriverTest(test.TestCase):
         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'
@@ -189,6 +191,8 @@ class HDSNFSDriverTest(test.TestCase):
         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("")
index d08d6ae89a3a0179090fbf155d10687db5863f75..77861ab99347f8e42cc8633f116ccdd9663e6941 100644 (file)
@@ -85,9 +85,13 @@ FAKE_VSERVER = 'fake_vserver'
 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']
index 53b93271157841e3507342f1040374ab422f9856..4432c7b1bb73c146a8ac8b17c56fd74b4c0550b2 100644 (file)
@@ -14,6 +14,7 @@
 #    under the License.
 """Unit tests for the NFS driver module."""
 
+import ddt
 import errno
 import os
 
@@ -53,6 +54,8 @@ class RemoteFsDriverTestCase(test.TestCase):
         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)
 
@@ -334,6 +337,7 @@ class RemoteFsDriverTestCase(test.TestCase):
         self.assertFalse(ret_flag)
 
 
+@ddt.ddt
 class NfsDriverTestCase(test.TestCase):
     """Test case for NFS driver."""
 
@@ -358,10 +362,13 @@ class NfsDriverTestCase(test.TestCase):
         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
@@ -371,7 +378,6 @@ class NfsDriverTestCase(test.TestCase):
         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 = {}
@@ -620,7 +626,7 @@ class NfsDriverTestCase(test.TestCase):
 
         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
 
@@ -657,7 +663,7 @@ class NfsDriverTestCase(test.TestCase):
 
         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
@@ -849,7 +855,7 @@ class NfsDriverTestCase(test.TestCase):
         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()
@@ -857,8 +863,8 @@ class NfsDriverTestCase(test.TestCase):
     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]
 
@@ -883,6 +889,83 @@ class NfsDriverTestCase(test.TestCase):
         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')\
@@ -1132,118 +1215,61 @@ class NfsDriverDoSetupTestCase(test.TestCase):
         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."""
index 04449a9de9184f551ee3faaf63fccf4f8871c213..d7cf987654b9902d744bac58486afad001b3026f 100644 (file)
@@ -56,6 +56,7 @@ class TintriDriverTestCase(test.TestCase):
         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):
index 719bb80255a3d3a9fad36418b1a28630c936d5c7..3ce0a0d1e1423a4798f0d43354204c7207259260 100644 (file)
@@ -737,6 +737,8 @@ class TestZFSSANFSDriver(test.TestCase):
 
     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'
index 6192ae7c7e47c42609f64f476d28ecae05c77472..472f58e73e5ecd3a5e0405b165a6c65f8a305e29 100644 (file)
@@ -158,6 +158,7 @@ NFS_SHARE_IPV4 = IPV4_ADDRESS + ':' + EXPORT_PATH
 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)
index d15a09061a19e58238d77c178bdff2ed82cc6082..894ef21586867f6651e8967ccc4717a1b606782c 100644 (file)
@@ -15,6 +15,7 @@
 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
@@ -27,6 +28,7 @@ from cinder.volume.drivers.netapp.dataontap import nfs_7mode
 from cinder.volume.drivers.netapp import utils as na_utils
 
 
+@ddt.ddt
 class NetApp7modeNfsDriverTestCase(test.TestCase):
     def setUp(self):
         super(NetApp7modeNfsDriverTestCase, self).setUp()
@@ -52,28 +54,43 @@ class NetApp7modeNfsDriverTestCase(test.TestCase):
         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(
index 794bb4337eef7c7ffa4a2181fd12fcbaf2a49cb5..9c855de3b02fe041b48048bee76c4f63fade4274 100644 (file)
@@ -37,6 +37,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
     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
@@ -62,14 +63,10 @@ class NetAppNfsDriverTestCase(test.TestCase):
     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))
 
@@ -80,7 +77,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
         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
index 300cf9f626e6bad7f8b78da5bb89942a17e5be08..4d714fd9b64f81daba60a4b9e2bf3209bd675939 100644 (file)
@@ -16,6 +16,7 @@
 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
@@ -37,6 +38,7 @@ from cinder.volume.drivers import nfs
 from cinder.volume import utils as volume_utils
 
 
+@ddt.ddt
 class NetAppCmodeNfsDriverTestCase(test.TestCase):
     def setUp(self):
         super(NetAppCmodeNfsDriverTestCase, self).setUp()
@@ -73,32 +75,90 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
         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(
index e2984a06af4658c6a3c163bb424192456106d334..1960b6b7f0ce2568e8f868dabfc0d37d9f874965 100644 (file)
@@ -118,6 +118,10 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver):
             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
index 2b0e0c092e085f4aa48afb47710f5fb11663930f..55f35078ce9dbdee6be7f66bf13edf3c658f0eb0 100644 (file)
@@ -752,28 +752,16 @@ class NetAppNfsDriver(driver.ManageableVD,
     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
 
index 154eccd73da29b13103080855d3e9ab2aa1504d9..0dcb6d9d9858feb26e1e1db5f844df14e36cd1ca 100644 (file)
@@ -197,10 +197,16 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
                 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)
 
index 3f59f598948540041a9128ba58611e9d7b6af265..52d7a448c067e1191994f9c6f6212c5278d19468 100644 (file)
@@ -21,6 +21,7 @@ from os_brick.remotefs import remotefs as remotefs_brick
 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
 
@@ -35,6 +36,9 @@ VERSION = '1.3.0'
 
 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',
@@ -44,16 +48,23 @@ nfs_opts = [
                 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.')),
@@ -110,6 +121,10 @@ class NfsDriver(driver.ExtendVD, remotefs.RemoteFSDriver):
             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:
@@ -130,18 +145,6 @@ class NfsDriver(driver.ExtendVD, remotefs.RemoteFSDriver):
                    {'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
 
@@ -232,17 +235,30 @@ class NfsDriver(driver.ExtendVD, remotefs.RemoteFSDriver):
         :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
@@ -372,3 +388,64 @@ class NfsDriver(driver.ExtendVD, remotefs.RemoteFSDriver):
 
         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