]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
RemoteFS: Use nas_ip and nas_share_path options
authorEric Harney <eharney@redhat.com>
Fri, 23 Jan 2015 20:41:40 +0000 (15:41 -0500)
committerEric Harney <eharney@redhat.com>
Tue, 3 Feb 2015 16:31:38 +0000 (17:31 +0100)
This replaces the <x>fs_shares_config file configuration
options with the existing nas_ip option and new
nas_share_path and nas_mount_options configuration
options.

This means that RemoteFS drivers will manage a single
export rather than a handful of unrelated exports.

If the nas_ip and nas_share_path options are set, they
are used.  If not, the previous configuration mechanism,
based on loading a set of shares from a file configured
by <x>fs_shares_config will be used.

Also use nas_mount_options to replace
nfs_mount_options for consistency.  If nas_mount_options
is not set, nfs_mount_options will be used for
compatibility.

Implements blueprint: remotefs-share-cfg-improvements
DocImpact: new configuration options

Change-Id: Ic72ae6c7fdd2c6a7fea27152f27c6521a561977b

cinder/tests/test_glusterfs.py
cinder/tests/test_hds_nfs.py
cinder/tests/test_netapp_nfs.py
cinder/tests/test_nexenta.py
cinder/tests/test_nfs.py
cinder/volume/drivers/nfs.py
cinder/volume/drivers/remotefs.py

index 4e70bd4e8b0e0c7a04b2d921adb520e8479ddbc4..0f9db292f219fbcf2a1688924bceffb8122fdbbb 100644 (file)
@@ -92,6 +92,9 @@ class GlusterFsDriverTestCase(test.TestCase):
         self._configuration.glusterfs_qcow2_volumes = False
         self._configuration.nas_secure_file_permissions = 'false'
         self._configuration.nas_secure_file_operations = 'false'
+        self._configuration.nas_ip = None
+        self._configuration.nas_share_path = None
+        self._configuration.nas_mount_options = None
 
         self._driver =\
             glusterfs.GlusterfsDriver(configuration=self._configuration,
index 168dc8b6d1556fd36d3251a590ae406ff6d593e9..22dc68d4746ba8c2bb7ffe2782c2eb4ec0ec9a87 100644 (file)
@@ -123,6 +123,9 @@ class HDSNFSDriverTest(test.TestCase):
         self.configuration.nfs_shares_config = self.shares_file
         self.configuration.nfs_mount_point_base = '/opt/stack/cinder/mnt'
         self.configuration.nfs_mount_options = None
+        self.configuration.nas_ip = None
+        self.configuration.nas_share_path = None
+        self.configuration.nas_mount_options = None
 
         self.driver = nfs.HDSNFSDriver(configuration=self.configuration)
         self.driver.do_setup("")
index 7bb0dbb960b378274dddee2155a2743e51688c8a..a2e03d9058f09cebeceae0ece5328b9e6ee6eb82 100644 (file)
@@ -64,6 +64,7 @@ def create_configuration():
     configuration.append_config_values(mox.IgnoreArg())
     configuration.nfs_mount_point_base = '/mnt/test'
     configuration.nfs_mount_options = None
+    configuration.nas_mount_options = None
     configuration.netapp_server_hostname = CONNECTION_INFO['hostname']
     configuration.netapp_transport_type = CONNECTION_INFO['transport_type']
     configuration.netapp_server_port = CONNECTION_INFO['port']
index 7d39c79cef7330542ba484aaaba1cd0534b22293..27251bf0196f578bfd3612a2ffeb5020d03c9ab9 100644 (file)
@@ -472,6 +472,7 @@ class TestNexentaNfsDriver(test.TestCase):
         self.configuration.nexenta_volume_compression = 'on'
         self.configuration.nfs_mount_point_base = '/mnt/test'
         self.configuration.nfs_mount_options = None
+        self.configuration.nas_mount_options = None
         self.configuration.nexenta_nms_cache_volroot = False
         self.nms_mock = self.mox.CreateMockAnything()
         for mod in ('appliance', 'folder', 'server', 'volume', 'netstorsvc',
index 45851e0d288309748765de4bf71fd689a615505e..d7471beb0a5dd54ff612b031c44ef6ebd6e10369 100644 (file)
@@ -342,7 +342,9 @@ class RemoteFsDriverTestCase(test.TestCase):
 class NfsDriverTestCase(test.TestCase):
     """Test case for NFS driver."""
 
-    TEST_NFS_EXPORT1 = 'nfs-host1:/export'
+    TEST_NFS_HOST = 'nfs-host1'
+    TEST_NFS_SHARE_PATH = '/export'
+    TEST_NFS_EXPORT1 = '%s:%s' % (TEST_NFS_HOST, TEST_NFS_SHARE_PATH)
     TEST_NFS_EXPORT2 = 'nfs-host2:/export'
     TEST_NFS_EXPORT2_OPTIONS = '-o intr'
     TEST_SIZE_IN_GB = 1
@@ -367,8 +369,12 @@ class NfsDriverTestCase(test.TestCase):
         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.nfs_qcow2_volumes = False
         self.configuration.nas_secure_file_permissions = 'false'
         self.configuration.nas_secure_file_operations = 'false'
+        self.configuration.nas_ip = None
+        self.configuration.nas_share_path = None
+        self.configuration.nas_mount_options = None
         self.configuration.volume_dd_blocksize = '1M'
         self._driver = nfs.NfsDriver(configuration=self.configuration)
         self._driver.shares = {}
@@ -533,6 +539,25 @@ class NfsDriverTestCase(test.TestCase):
 
         mox.VerifyAll()
 
+    def test_load_shares_config_nas_opts(self):
+        mox = self._mox
+        drv = self._driver
+
+        mox.StubOutWithMock(drv, '_read_config_file')  # ensure not called
+
+        drv.configuration.nas_ip = self.TEST_NFS_HOST
+        drv.configuration.nas_share_path = self.TEST_NFS_SHARE_PATH
+        drv.configuration.nfs_shares_config = self.TEST_SHARES_CONFIG_FILE
+
+        mox.ReplayAll()
+
+        drv._load_shares_config(drv.configuration.nfs_shares_config)
+
+        self.assertIn(self.TEST_NFS_EXPORT1, drv.shares)
+        self.assertEqual(len(drv.shares), 1)
+
+        mox.VerifyAll()
+
     def test_ensure_shares_mounted_should_save_mounting_successfully(self):
         """_ensure_shares_mounted should save share if mounted with success."""
         mox = self._mox
index 6acce926115e0eb34c6b24aba5a8edb446f1ef9d..43880fd0f7a3c04128a564f9a5607e2319341dbe 100644 (file)
@@ -94,6 +94,14 @@ class NfsDriver(remotefs.RemoteFSDriver):
         opts = getattr(self.configuration,
                        'nfs_mount_options',
                        CONF.nfs_mount_options)
+
+        nas_mount_options = getattr(self.configuration,
+                                    'nas_mount_options',
+                                    None)
+        if nas_mount_options is not None:
+            LOG.debug('overriding nfs_mount_options with nas_mount_options')
+            opts = nas_mount_options
+
         self._remotefsclient = remotefs_brick.RemoteFsClient(
             'nfs', root_helper, execute=execute,
             nfs_mount_point_base=self.base,
index f51956df2ecb8903add7bb9bbee381665ce8ac76..d8ff5207eb0ded723c02cd9164ec842e716fea4a 100644 (file)
@@ -36,6 +36,7 @@ from cinder.volume import driver
 LOG = logging.getLogger(__name__)
 
 nas_opts = [
+    #TODO(eharney): deprecate nas_ip and change this to nas_host
     cfg.StrOpt('nas_ip',
                default='',
                help='IP address or Hostname of NAS system.'),
@@ -71,6 +72,15 @@ nas_opts = [
                      'set to auto, a check is done to determine if '
                      'this is a new installation: True is used if so, '
                      'otherwise False. Default is auto.')),
+    cfg.StrOpt('nas_share_path',
+               default='',
+               help=('Path to the share to use for storing Cinder volumes. ',
+                     'For example:  "/srv/export1" for an NFS server export '
+                     'available at 10.0.5.10:/srv/export1 .')),
+    cfg.StrOpt('nas_mount_options',
+               default=None,
+               help=('Options used to mount the storage backend file system '
+                     'where Cinder volumes are stored.'))
 ]
 
 CONF = cfg.CONF
@@ -156,6 +166,7 @@ class RemoteFSDriver(driver.VolumeDriver):
         """Creates a volume.
 
         :param volume: volume reference
+        :returns: provider_location update dict for database
         """
         self._ensure_shares_mounted()
 
@@ -229,7 +240,9 @@ class RemoteFSDriver(driver.VolumeDriver):
         self._ensure_share_mounted(volume['provider_location'])
 
     def create_export(self, ctx, volume):
-        """Exports the volume. Can optionally return a Dictionary of changes
+        """Exports the volume.
+
+        Can optionally return a dictionary of changes
         to the volume object to be persisted.
         """
         pass
@@ -250,14 +263,12 @@ class RemoteFSDriver(driver.VolumeDriver):
         self._execute('rm', '-f', path, run_as_root=self._execute_as_root)
 
     def _create_sparsed_file(self, path, size):
-        """Creates file with 0 disk usage."""
+        """Creates a sparse file of a given size in GiB."""
         self._execute('truncate', '-s', '%sG' % size,
                       path, run_as_root=self._execute_as_root)
 
     def _create_regular_file(self, path, size):
-        """Creates regular file of given size. Takes a lot of time for large
-        files.
-        """
+        """Creates a regular file of given size in GiB."""
 
         block_size_mb = 1
         block_count = size * units.Gi / (block_size_mb * units.Mi)
@@ -268,7 +279,7 @@ class RemoteFSDriver(driver.VolumeDriver):
                       run_as_root=self._execute_as_root)
 
     def _create_qcow2_file(self, path, size_gb):
-        """Creates a QCOW2 file of a given size."""
+        """Creates a QCOW2 file of a given size in GiB."""
 
         self._execute('qemu-img', 'create', '-f', 'qcow2',
                       '-o', 'preallocation=metadata',
@@ -358,33 +369,57 @@ class RemoteFSDriver(driver.VolumeDriver):
         with open(config_file) as f:
             return f.readlines()
 
-    def _load_shares_config(self, share_file):
+    def _load_shares_config(self, share_file=None):
         self.shares = {}
 
-        for share in self._read_config_file(share_file):
-            # A configuration line may be either:
-            #  host:/vol_name
-            # or
-            #  host:/vol_name -o options=123,rw --other
-            if not share.strip():
-                # Skip blank or whitespace-only lines
-                continue
-            if share.startswith('#'):
-                continue
+        if all((self.configuration.nas_ip,
+                self.configuration.nas_share_path)):
+            LOG.debug('Using nas_ip and nas_share_path configuration.')
 
-            share_info = share.split(' ', 1)
-            # results in share_info =
-            #  [ 'address:/vol', '-o options=123,rw --other' ]
+            nas_ip = self.configuration.nas_ip
+            nas_share_path = self.configuration.nas_share_path
 
-            share_address = share_info[0].strip().decode('unicode_escape')
-            share_opts = share_info[1].strip() if len(share_info) > 1 else None
+            share_address = '%s:%s' % (nas_ip, nas_share_path)
 
             if not re.match(self.SHARE_FORMAT_REGEX, share_address):
-                LOG.error(_LE("Share %s ignored due to invalid format. Must "
-                              "be of form address:/export.") % share_address)
-                continue
+                msg = (_("Share %s ignored due to invalid format. Must "
+                         "be of form address:/export. Please check the "
+                         "nas_ip and nas_share_path settings."),
+                       share_address)
+                raise exception.InvalidConfigurationValue(msg)
 
-            self.shares[share_address] = share_opts
+            self.shares[share_address] = self.configuration.nas_mount_options
+
+        elif share_file is not None:
+            LOG.debug('Loading shares from %s.' % share_file)
+
+            for share in self._read_config_file(share_file):
+                # A configuration line may be either:
+                #  host:/vol_name
+                # or
+                #  host:/vol_name -o options=123,rw --other
+                if not share.strip():
+                    # Skip blank or whitespace-only lines
+                    continue
+                if share.startswith('#'):
+                    continue
+
+                share_info = share.split(' ', 1)
+                # results in share_info =
+                #  [ 'address:/vol', '-o options=123,rw --other' ]
+
+                share_address = share_info[0].strip().decode('unicode_escape')
+                share_opts = None
+                if len(share_info) > 1:
+                    share_opts = share_info[1].strip()
+
+                if not re.match(self.SHARE_FORMAT_REGEX, share_address):
+                    LOG.error(_LE("Share %s ignored due to invalid format. "
+                                  "Must be of form address:/export."),
+                              share_address)
+                    continue
+
+                self.shares[share_address] = share_opts
 
         LOG.debug("shares loaded: %s", self.shares)