RemoteFS: Use nas_ip and nas_share_path options

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
This commit is contained in:
Eric Harney 2015-01-23 15:41:40 -05:00
parent 17779327c6
commit 170ab5825e
7 changed files with 103 additions and 27 deletions

View 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,

View 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("")

View 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']

View 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',

View 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

View 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,

View 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)