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.glusterfs_qcow2_volumes = False
|
||||||
self._configuration.nas_secure_file_permissions = 'false'
|
self._configuration.nas_secure_file_permissions = 'false'
|
||||||
self._configuration.nas_secure_file_operations = '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 =\
|
self._driver =\
|
||||||
glusterfs.GlusterfsDriver(configuration=self._configuration,
|
glusterfs.GlusterfsDriver(configuration=self._configuration,
|
||||||
|
|
|
@ -123,6 +123,9 @@ class HDSNFSDriverTest(test.TestCase):
|
||||||
self.configuration.nfs_shares_config = self.shares_file
|
self.configuration.nfs_shares_config = self.shares_file
|
||||||
self.configuration.nfs_mount_point_base = '/opt/stack/cinder/mnt'
|
self.configuration.nfs_mount_point_base = '/opt/stack/cinder/mnt'
|
||||||
self.configuration.nfs_mount_options = None
|
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 = nfs.HDSNFSDriver(configuration=self.configuration)
|
||||||
self.driver.do_setup("")
|
self.driver.do_setup("")
|
||||||
|
|
|
@ -64,6 +64,7 @@ def create_configuration():
|
||||||
configuration.append_config_values(mox.IgnoreArg())
|
configuration.append_config_values(mox.IgnoreArg())
|
||||||
configuration.nfs_mount_point_base = '/mnt/test'
|
configuration.nfs_mount_point_base = '/mnt/test'
|
||||||
configuration.nfs_mount_options = None
|
configuration.nfs_mount_options = None
|
||||||
|
configuration.nas_mount_options = None
|
||||||
configuration.netapp_server_hostname = CONNECTION_INFO['hostname']
|
configuration.netapp_server_hostname = CONNECTION_INFO['hostname']
|
||||||
configuration.netapp_transport_type = CONNECTION_INFO['transport_type']
|
configuration.netapp_transport_type = CONNECTION_INFO['transport_type']
|
||||||
configuration.netapp_server_port = CONNECTION_INFO['port']
|
configuration.netapp_server_port = CONNECTION_INFO['port']
|
||||||
|
|
|
@ -472,6 +472,7 @@ class TestNexentaNfsDriver(test.TestCase):
|
||||||
self.configuration.nexenta_volume_compression = 'on'
|
self.configuration.nexenta_volume_compression = 'on'
|
||||||
self.configuration.nfs_mount_point_base = '/mnt/test'
|
self.configuration.nfs_mount_point_base = '/mnt/test'
|
||||||
self.configuration.nfs_mount_options = None
|
self.configuration.nfs_mount_options = None
|
||||||
|
self.configuration.nas_mount_options = None
|
||||||
self.configuration.nexenta_nms_cache_volroot = False
|
self.configuration.nexenta_nms_cache_volroot = False
|
||||||
self.nms_mock = self.mox.CreateMockAnything()
|
self.nms_mock = self.mox.CreateMockAnything()
|
||||||
for mod in ('appliance', 'folder', 'server', 'volume', 'netstorsvc',
|
for mod in ('appliance', 'folder', 'server', 'volume', 'netstorsvc',
|
||||||
|
|
|
@ -342,7 +342,9 @@ class RemoteFsDriverTestCase(test.TestCase):
|
||||||
class NfsDriverTestCase(test.TestCase):
|
class NfsDriverTestCase(test.TestCase):
|
||||||
"""Test case for NFS driver."""
|
"""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 = 'nfs-host2:/export'
|
||||||
TEST_NFS_EXPORT2_OPTIONS = '-o intr'
|
TEST_NFS_EXPORT2_OPTIONS = '-o intr'
|
||||||
TEST_SIZE_IN_GB = 1
|
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_point_base = self.TEST_MNT_POINT_BASE
|
||||||
self.configuration.nfs_mount_options = None
|
self.configuration.nfs_mount_options = None
|
||||||
self.configuration.nfs_mount_attempts = 3
|
self.configuration.nfs_mount_attempts = 3
|
||||||
|
self.configuration.nfs_qcow2_volumes = False
|
||||||
self.configuration.nas_secure_file_permissions = 'false'
|
self.configuration.nas_secure_file_permissions = 'false'
|
||||||
self.configuration.nas_secure_file_operations = '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.configuration.volume_dd_blocksize = '1M'
|
||||||
self._driver = nfs.NfsDriver(configuration=self.configuration)
|
self._driver = nfs.NfsDriver(configuration=self.configuration)
|
||||||
self._driver.shares = {}
|
self._driver.shares = {}
|
||||||
|
@ -533,6 +539,25 @@ class NfsDriverTestCase(test.TestCase):
|
||||||
|
|
||||||
mox.VerifyAll()
|
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):
|
def test_ensure_shares_mounted_should_save_mounting_successfully(self):
|
||||||
"""_ensure_shares_mounted should save share if mounted with success."""
|
"""_ensure_shares_mounted should save share if mounted with success."""
|
||||||
mox = self._mox
|
mox = self._mox
|
||||||
|
|
|
@ -94,6 +94,14 @@ class NfsDriver(remotefs.RemoteFSDriver):
|
||||||
opts = getattr(self.configuration,
|
opts = getattr(self.configuration,
|
||||||
'nfs_mount_options',
|
'nfs_mount_options',
|
||||||
CONF.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(
|
self._remotefsclient = remotefs_brick.RemoteFsClient(
|
||||||
'nfs', root_helper, execute=execute,
|
'nfs', root_helper, execute=execute,
|
||||||
nfs_mount_point_base=self.base,
|
nfs_mount_point_base=self.base,
|
||||||
|
|
|
@ -36,6 +36,7 @@ from cinder.volume import driver
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
nas_opts = [
|
nas_opts = [
|
||||||
|
#TODO(eharney): deprecate nas_ip and change this to nas_host
|
||||||
cfg.StrOpt('nas_ip',
|
cfg.StrOpt('nas_ip',
|
||||||
default='',
|
default='',
|
||||||
help='IP address or Hostname of NAS system.'),
|
help='IP address or Hostname of NAS system.'),
|
||||||
|
@ -71,6 +72,15 @@ nas_opts = [
|
||||||
'set to auto, a check is done to determine if '
|
'set to auto, a check is done to determine if '
|
||||||
'this is a new installation: True is used if so, '
|
'this is a new installation: True is used if so, '
|
||||||
'otherwise False. Default is auto.')),
|
'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
|
CONF = cfg.CONF
|
||||||
|
@ -156,6 +166,7 @@ class RemoteFSDriver(driver.VolumeDriver):
|
||||||
"""Creates a volume.
|
"""Creates a volume.
|
||||||
|
|
||||||
:param volume: volume reference
|
:param volume: volume reference
|
||||||
|
:returns: provider_location update dict for database
|
||||||
"""
|
"""
|
||||||
self._ensure_shares_mounted()
|
self._ensure_shares_mounted()
|
||||||
|
|
||||||
|
@ -229,7 +240,9 @@ class RemoteFSDriver(driver.VolumeDriver):
|
||||||
self._ensure_share_mounted(volume['provider_location'])
|
self._ensure_share_mounted(volume['provider_location'])
|
||||||
|
|
||||||
def create_export(self, ctx, volume):
|
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.
|
to the volume object to be persisted.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
@ -250,14 +263,12 @@ class RemoteFSDriver(driver.VolumeDriver):
|
||||||
self._execute('rm', '-f', path, run_as_root=self._execute_as_root)
|
self._execute('rm', '-f', path, run_as_root=self._execute_as_root)
|
||||||
|
|
||||||
def _create_sparsed_file(self, path, size):
|
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,
|
self._execute('truncate', '-s', '%sG' % size,
|
||||||
path, run_as_root=self._execute_as_root)
|
path, run_as_root=self._execute_as_root)
|
||||||
|
|
||||||
def _create_regular_file(self, path, size):
|
def _create_regular_file(self, path, size):
|
||||||
"""Creates regular file of given size. Takes a lot of time for large
|
"""Creates a regular file of given size in GiB."""
|
||||||
files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
block_size_mb = 1
|
block_size_mb = 1
|
||||||
block_count = size * units.Gi / (block_size_mb * units.Mi)
|
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)
|
run_as_root=self._execute_as_root)
|
||||||
|
|
||||||
def _create_qcow2_file(self, path, size_gb):
|
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',
|
self._execute('qemu-img', 'create', '-f', 'qcow2',
|
||||||
'-o', 'preallocation=metadata',
|
'-o', 'preallocation=metadata',
|
||||||
|
@ -358,33 +369,57 @@ class RemoteFSDriver(driver.VolumeDriver):
|
||||||
with open(config_file) as f:
|
with open(config_file) as f:
|
||||||
return f.readlines()
|
return f.readlines()
|
||||||
|
|
||||||
def _load_shares_config(self, share_file):
|
def _load_shares_config(self, share_file=None):
|
||||||
self.shares = {}
|
self.shares = {}
|
||||||
|
|
||||||
for share in self._read_config_file(share_file):
|
if all((self.configuration.nas_ip,
|
||||||
# A configuration line may be either:
|
self.configuration.nas_share_path)):
|
||||||
# host:/vol_name
|
LOG.debug('Using nas_ip and nas_share_path configuration.')
|
||||||
# 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)
|
nas_ip = self.configuration.nas_ip
|
||||||
# results in share_info =
|
nas_share_path = self.configuration.nas_share_path
|
||||||
# [ 'address:/vol', '-o options=123,rw --other' ]
|
|
||||||
|
|
||||||
share_address = share_info[0].strip().decode('unicode_escape')
|
share_address = '%s:%s' % (nas_ip, nas_share_path)
|
||||||
share_opts = share_info[1].strip() if len(share_info) > 1 else None
|
|
||||||
|
|
||||||
if not re.match(self.SHARE_FORMAT_REGEX, share_address):
|
if not re.match(self.SHARE_FORMAT_REGEX, share_address):
|
||||||
LOG.error(_LE("Share %s ignored due to invalid format. Must "
|
msg = (_("Share %s ignored due to invalid format. Must "
|
||||||
"be of form address:/export.") % share_address)
|
"be of form address:/export. Please check the "
|
||||||
continue
|
"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)
|
LOG.debug("shares loaded: %s", self.shares)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue