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:
parent
17779327c6
commit
170ab5825e
|
@ -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,
|
||||
|
|
|
@ -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("")
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue