From 170ab5825ec24e7cd181147efc331e08029a7354 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 23 Jan 2015 15:41:40 -0500 Subject: [PATCH] RemoteFS: Use nas_ip and nas_share_path options This replaces the 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 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 | 3 ++ cinder/tests/test_hds_nfs.py | 3 ++ cinder/tests/test_netapp_nfs.py | 1 + cinder/tests/test_nexenta.py | 1 + cinder/tests/test_nfs.py | 27 +++++++++- cinder/volume/drivers/nfs.py | 8 +++ cinder/volume/drivers/remotefs.py | 87 ++++++++++++++++++++++--------- 7 files changed, 103 insertions(+), 27 deletions(-) diff --git a/cinder/tests/test_glusterfs.py b/cinder/tests/test_glusterfs.py index 4e70bd4e8..0f9db292f 100644 --- a/cinder/tests/test_glusterfs.py +++ b/cinder/tests/test_glusterfs.py @@ -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, diff --git a/cinder/tests/test_hds_nfs.py b/cinder/tests/test_hds_nfs.py index 168dc8b6d..22dc68d47 100644 --- a/cinder/tests/test_hds_nfs.py +++ b/cinder/tests/test_hds_nfs.py @@ -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("") diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py index 7bb0dbb96..a2e03d905 100644 --- a/cinder/tests/test_netapp_nfs.py +++ b/cinder/tests/test_netapp_nfs.py @@ -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'] diff --git a/cinder/tests/test_nexenta.py b/cinder/tests/test_nexenta.py index 7d39c79ce..27251bf01 100644 --- a/cinder/tests/test_nexenta.py +++ b/cinder/tests/test_nexenta.py @@ -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', diff --git a/cinder/tests/test_nfs.py b/cinder/tests/test_nfs.py index 45851e0d2..d7471beb0 100644 --- a/cinder/tests/test_nfs.py +++ b/cinder/tests/test_nfs.py @@ -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 diff --git a/cinder/volume/drivers/nfs.py b/cinder/volume/drivers/nfs.py index 6acce9261..43880fd0f 100644 --- a/cinder/volume/drivers/nfs.py +++ b/cinder/volume/drivers/nfs.py @@ -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, diff --git a/cinder/volume/drivers/remotefs.py b/cinder/volume/drivers/remotefs.py index f51956df2..d8ff5207e 100644 --- a/cinder/volume/drivers/remotefs.py +++ b/cinder/volume/drivers/remotefs.py @@ -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) -- 2.45.2