Merge "add IPv6 support for CephFS/NFS back end"

This commit is contained in:
Zuul 2019-07-25 18:59:20 +00:00 committed by Gerrit Code Review
commit cec13b8057
8 changed files with 282 additions and 38 deletions

View File

@ -307,10 +307,13 @@
- openstack/devstack-plugin-ceph
- openstack/manila
- openstack/manila-tempest-plugin
- openstack/neutron-dynamic-routing
# TODO(gouthamr): Remove the line below when neutron-dynamic-routing
# separates its tempest plugin from its tree
- openstack/neutron-tempest-plugin
- openstack/python-manilaclient
- openstack/tempest
- job:
name: manila-tempest-minimal-dsvm-dummy
parent: manila-tempest-base

View File

@ -1061,6 +1061,13 @@ function setup_ipv6 {
iniset $MANILA_CONF DEFAULT data_node_access_ip $public_gateway_ipv4
fi
if [ "$SHARE_DRIVER" == "manila.share.drivers.cephfs.driver.CephFSDriver" ]; then
for backend_name in ${MANILA_ENABLED_BACKENDS//,/ }; do
iniset $MANILA_CONF $backend_name cephfs_ganesha_export_ips $public_gateway_ipv4,$public_gateway_ipv6
done
iniset $MANILA_CONF DEFAULT data_node_access_ip $public_gateway_ipv4
fi
# install Quagga for setting up the host routes dynamically
install_package quagga

View File

@ -146,7 +146,7 @@ Mapping of share drivers and share access rules support
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| CephFS | NFS (P) | \- | \- | \- | CEPHFS (M) | NFS (P) | \- | \- | \- | CEPHFS (N) |
| CephFS | NFS (P) | NFS (T) | \- | \- | CEPHFS (M) | NFS (P) | NFS (T) | \- | \- | CEPHFS (N) |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
| Tegile | NFS (M) | \- |NFS (M),CIFS (M)| \- | \- | NFS (M) | \- |NFS (M),CIFS (M)| \- | \- |
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+

View File

@ -14,12 +14,15 @@
# under the License.
import ipaddress
import socket
import sys
from oslo_config import cfg
from oslo_config import types
from oslo_log import log
from oslo_utils import units
import six
from manila.common import constants
from manila import exception
@ -27,6 +30,7 @@ from manila.i18n import _
from manila.share import driver
from manila.share.drivers import ganesha
from manila.share.drivers.ganesha import utils as ganesha_utils
from manila.share.drivers import helpers as driver_helpers
from manila.share import share_types
try:
@ -78,8 +82,8 @@ cephfs_opts = [
default=False,
help="Whether the NFS-Ganesha server is remote to the driver."
),
cfg.StrOpt('cephfs_ganesha_server_ip',
help="The IP address of the NFS-Ganesha server."),
cfg.HostAddressOpt('cephfs_ganesha_server_ip',
help="The IP address of the NFS-Ganesha server."),
cfg.StrOpt('cephfs_ganesha_server_username',
default='root',
help="The username to authenticate as in the remote "
@ -91,6 +95,11 @@ cephfs_opts = [
help="The password to authenticate as the user in the remote "
"Ganesha server host. This is not required if "
"'cephfs_ganesha_path_to_private_key' is configured."),
cfg.ListOpt('cephfs_ganesha_export_ips',
default='',
help="List of IPs to export shares. If not supplied, "
"then the value of 'cephfs_ganesha_server_ip' "
"will be used to construct share export locations."),
cfg.StrOpt('cephfs_volume_mode',
default=DEFAULT_VOLUME_MODE,
help="The read/write/execute permissions mode for CephFS "
@ -110,7 +119,7 @@ def cephfs_share_path(share):
class CephFSDriver(driver.ExecuteMixin, driver.GaneshaMixin,
driver.ShareDriver,):
driver.ShareDriver):
"""Driver for the Ceph Filesystem."""
def __init__(self, *args, **kwargs):
@ -130,6 +139,8 @@ class CephFSDriver(driver.ExecuteMixin, driver.GaneshaMixin,
raise exception.BadConfigurationException(
msg % self.configuration.cephfs_volume_mode)
self.ipv6_implemented = True
def do_setup(self, context):
if self.configuration.cephfs_protocol_helper_type.upper() == "CEPHFS":
protocol_helper_class = getattr(
@ -145,6 +156,10 @@ class CephFSDriver(driver.ExecuteMixin, driver.GaneshaMixin,
self.protocol_helper.init_helper()
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self.protocol_helper.check_for_setup_error()
def _update_share_stats(self):
stats = self.volume_client.rados.get_cluster_stats()
@ -174,7 +189,8 @@ class CephFSDriver(driver.ExecuteMixin, driver.GaneshaMixin,
'snapshot_support': self.configuration.safe_get(
'cephfs_enable_snapshots'),
}
super(CephFSDriver, self)._update_share_stats(data)
super( # pylint: disable=no-member
CephFSDriver, self)._update_share_stats(data)
def _to_bytes(self, gigs):
"""Convert a Manila size into bytes.
@ -337,6 +353,9 @@ class CephFSDriver(driver.ExecuteMixin, driver.GaneshaMixin,
self._volume_client.disconnect()
self._volume_client = None
def get_configured_ip_versions(self):
return self.protocol_helper.get_configured_ip_versions()
class NativeProtocolHelper(ganesha.NASHelperBase):
"""Helper class for native CephFS protocol"""
@ -353,6 +372,10 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def _init_helper(self):
pass
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
return
def get_export_locations(self, share, cephfs_volume):
# To mount this you need to know the mon IPs and the path to the volume
mon_addrs = self.volume_client.get_mon_addrs()
@ -460,6 +483,9 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
return access_keys
def get_configured_ip_versions(self):
return [4]
class NFSProtocolHelper(ganesha.GaneshaNASHelper2):
@ -487,20 +513,40 @@ class NFSProtocolHelper(ganesha.GaneshaNASHelper2):
if not hasattr(self, 'ceph_vol_client'):
self.ceph_vol_client = kwargs.pop('ceph_vol_client')
self.export_ips = config_object.cephfs_ganesha_export_ips
if not self.export_ips:
self.export_ips = [self.ganesha_host]
self.configured_ip_versions = set()
self.config = config_object
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
host_address_obj = types.HostAddress()
for export_ip in self.config.cephfs_ganesha_export_ips:
try:
host_address_obj(export_ip)
except ValueError:
msg = (_("Invalid list member of 'cephfs_ganesha_export_ips' "
"option supplied %s -- not a valid IP address or "
"hostname.") % export_ip)
raise exception.InvalidParameterValue(err=msg)
def get_export_locations(self, share, cephfs_volume):
export_location = "{server_address}:{path}".format(
server_address=self.ganesha_host,
path=cephfs_volume['mount_path'])
export_locations = []
for export_ip in self.export_ips:
export_path = "{server_address}:{mount_path}".format(
server_address=driver_helpers.escaped_address(export_ip),
mount_path=cephfs_volume['mount_path'])
LOG.info("Calculated export location for share %(id)s: %(loc)s",
{"id": share['id'], "loc": export_location})
return {
'path': export_location,
'is_admin_only': False,
'metadata': {},
}
LOG.info("Calculated export path for share %(id)s: %(epath)s",
{"id": share['id'], "epath": export_path})
export_location = {
'path': export_path,
'is_admin_only': False,
'metadata': {},
}
export_locations.append(export_location)
return export_locations
def _default_config_hook(self):
"""Callback to provide default export block."""
@ -540,3 +586,19 @@ class NFSProtocolHelper(ganesha.GaneshaNASHelper2):
"""Callback to provide pseudo path."""
volume_path = cephfs_share_path(share)
return self.ceph_vol_client._get_path(volume_path)
def get_configured_ip_versions(self):
if not self.configured_ip_versions:
try:
for export_ip in self.export_ips:
self.configured_ip_versions.add(
ipaddress.ip_address(six.text_type(export_ip)).version)
except Exception:
# export_ips contained a hostname, safest thing is to
# claim support for IPv4 and IPv6 address families
LOG.warning("Setting configured IP versions to [4, 6] since "
"a hostname (rather than IP address) was supplied "
"in 'cephfs_ganesha_server_ip' or "
"in 'cephfs_ganesha_export_ips'.")
return [4, 6]
return list(self.configured_ip_versions)

View File

@ -181,6 +181,14 @@ def nfs_synchronized(f):
return wrapped_func
def escaped_address(address):
addr = ipaddress.ip_address(six.text_type(address))
if addr.version == 4:
return six.text_type(addr)
else:
return '[%s]' % six.text_type(addr)
class NFSHelper(NASHelperBase):
"""Interface to work with share."""
@ -191,24 +199,16 @@ class NFSHelper(NASHelperBase):
if 'public_addresses' in server_copy:
for address in server_copy['public_addresses']:
public_addresses.append(
self._escaped_address(address))
escaped_address(address))
server_copy['public_addresses'] = public_addresses
for t in ['public_address', 'admin_ip', 'ip']:
address = server_copy.get(t)
if address is not None:
server_copy[t] = self._escaped_address(address)
server_copy[t] = escaped_address(address)
return self.get_exports_for_share(server_copy, path)
@staticmethod
def _escaped_address(address):
addr = ipaddress.ip_address(six.text_type(address))
if addr.version == 4:
return six.text_type(addr)
else:
return '[%s]' % six.text_type(addr)
def init_helper(self, server):
try:
self._ssh_exec(server, ['sudo', 'exportfs'])

View File

@ -136,6 +136,16 @@ class CephFSDriverTestCase(test.TestCase):
self.assertEqual(DEFAULT_VOLUME_MODE, self._driver._cephfs_volume_mode)
@ddt.data('cephfs', 'nfs')
def test_check_for_setup_error(self, protocol_helper):
self._driver.configuration.cephfs_protocol_helper_type = (
protocol_helper)
self._driver.check_for_setup_error()
(self._driver.protocol_helper.check_for_setup_error.
assert_called_once_with())
def test_create_share(self):
cephfs_volume = {"mount_path": "/foo/bar"}
@ -342,6 +352,7 @@ class CephFSDriverTestCase(test.TestCase):
vc.connect.assert_called_once_with(premount_evict=None)
def test_update_share_stats(self):
self._driver.get_configured_ip_versions = mock.Mock(return_value=[4])
self._driver._volume_client
self._driver._update_share_stats()
result = self._driver._stats
@ -359,6 +370,16 @@ class CephFSDriverTestCase(test.TestCase):
self._context,
self._share)
@ddt.data('cephfs', 'nfs')
def test_get_configured_ip_versions(self, protocol_helper):
self._driver.configuration.cephfs_protocol_helper_type = (
protocol_helper)
self._driver.get_configured_ip_versions()
(self._driver.protocol_helper.get_configured_ip_versions.
assert_called_once_with())
@ddt.ddt
class NativeProtocolHelperTestCase(test.TestCase):
@ -379,6 +400,13 @@ class NativeProtocolHelperTestCase(test.TestCase):
ceph_vol_client=MockVolumeClientModule.CephFSVolumeClient()
)
def test_check_for_setup_error(self):
expected = None
result = self._native_protocol_helper.check_for_setup_error()
self.assertEqual(expected, result)
def test_get_export_locations(self):
vc = self._native_protocol_helper.volume_client
fake_cephfs_volume = {'mount_path': '/foo/bar'}
@ -534,6 +562,13 @@ class NativeProtocolHelperTestCase(test.TestCase):
vc.authorize.assert_called_once_with(
driver.cephfs_share_path(self._share), "alice")
def test_get_configured_ip_versions(self):
expected = [4]
result = self._native_protocol_helper.get_configured_ip_versions()
self.assertEqual(expected, result)
@ddt.ddt
class NFSProtocolHelperTestCase(test.TestCase):
@ -558,6 +593,38 @@ class NFSProtocolHelperTestCase(test.TestCase):
self.fake_conf,
ceph_vol_client=self._volume_client)
@ddt.data(
(['fakehost', 'some.host.name', 'some.host.name.', '1.1.1.0'], False),
(['fakehost', 'some.host.name', 'some.host.name.', '1.1..1.0'], True),
(['fakehost', 'some.host.name', 'some.host.name', '1.1.1.256'], True),
(['fakehost..', 'some.host.name', 'some.host.name', '1.1.1.0'], True),
(['fakehost', 'some.host.name..', 'some.host.name', '1.1.1.0'], True),
(['fakehost', 'some.host.name', 'some.host.name.', '1.1..1.0'], True),
(['fakehost', 'some.host.name', '1.1.1.0/24'], True),
(['fakehost', 'some.host.name', '1.1.1.0', '1001::1001'], False),
(['fakehost', 'some.host.name', '1.1.1.0', '1001:1001'], True),
(['fakehost', 'some.host.name', '1.1.1.0', '1001::1001:'], True),
(['fakehost', 'some.host.name', '1.1.1.0', '1001::1001.'], True),
(['fakehost', 'some.host.name', '1.1.1.0', '1001::1001/129.'], True),
)
@ddt.unpack
def test_check_for_setup_error(self, cephfs_ganesha_export_ips, raises):
fake_conf = configuration.Configuration(None)
fake_conf.set_default('cephfs_ganesha_export_ips',
cephfs_ganesha_export_ips)
helper = driver.NFSProtocolHelper(
self._execute,
fake_conf,
ceph_vol_client=MockVolumeClientModule.CephFSVolumeClient()
)
if raises:
self.assertRaises(exception.InvalidParameterValue,
helper.check_for_setup_error)
else:
self.assertIsNone(helper.check_for_setup_error())
@ddt.data(False, True)
def test_init_executor_type(self, ganesha_server_is_remote):
fake_conf = configuration.Configuration(None)
@ -612,17 +679,104 @@ class NFSProtocolHelperTestCase(test.TestCase):
driver.socket.gethostname.assert_called_once_with()
driver.LOG.info.assert_called_once()
def test_get_export_locations(self):
def test_get_export_locations_no_export_ips_configured(self):
cephfs_volume = {"mount_path": "/foo/bar"}
fake_conf = configuration.Configuration(None)
fake_conf.set_default('cephfs_ganesha_server_ip', '1.2.3.4')
ret = self._nfs_helper.get_export_locations(self._share,
cephfs_volume)
helper = driver.NFSProtocolHelper(
self._execute,
fake_conf,
ceph_vol_client=MockVolumeClientModule.CephFSVolumeClient()
)
ret = helper.get_export_locations(self._share,
cephfs_volume)
self.assertEqual(
{
'path': 'fakeip:/foo/bar',
[{
'path': '1.2.3.4:/foo/bar',
'is_admin_only': False,
'metadata': {}
}, ret)
}], ret)
def test_get_export_locations_with_export_ips_configured(self):
fake_conf = configuration.Configuration(None)
conf_args_list = [
('cephfs_ganesha_server_ip', '1.2.3.4'),
('cephfs_ganesha_export_ips', '127.0.0.1,fd3f:c057:1192:1::1,::1')]
for args in conf_args_list:
fake_conf.set_default(*args)
helper = driver.NFSProtocolHelper(
self._execute,
fake_conf,
ceph_vol_client=MockVolumeClientModule.CephFSVolumeClient()
)
cephfs_volume = {"mount_path": "/foo/bar"}
ret = helper.get_export_locations(self._share, cephfs_volume)
self.assertEqual(
[
{
'path': '127.0.0.1:/foo/bar',
'is_admin_only': False,
'metadata': {},
},
{
'path': '[fd3f:c057:1192:1::1]:/foo/bar',
'is_admin_only': False,
'metadata': {},
},
{
'path': '[::1]:/foo/bar',
'is_admin_only': False,
'metadata': {},
},
], ret)
@ddt.data(('some.host.name', None, [4, 6]), ('host.', None, [4, 6]),
('1001::1001', None, [6]), ('1.1.1.0', None, [4]),
(None, ['1001::1001', '1.1.1.0'], [6, 4]),
(None, ['1001::1001'], [6]), (None, ['1.1.1.0'], [4]),
(None, ['1001::1001/129', '1.1.1.0'], [4, 6]))
@ddt.unpack
def test_get_configured_ip_versions(
self, cephfs_ganesha_server_ip, cephfs_ganesha_export_ips,
configured_ip_version):
fake_conf = configuration.Configuration(None)
conf_args_list = [
('cephfs_ganesha_server_ip', cephfs_ganesha_server_ip),
('cephfs_ganesha_export_ips', cephfs_ganesha_export_ips)]
for args in conf_args_list:
fake_conf.set_default(*args)
helper = driver.NFSProtocolHelper(
self._execute,
fake_conf,
ceph_vol_client=MockVolumeClientModule.CephFSVolumeClient()
)
self.assertEqual(set(configured_ip_version),
set(helper.get_configured_ip_versions()))
def test_get_configured_ip_versions_already_set(self):
fake_conf = configuration.Configuration(None)
helper = driver.NFSProtocolHelper(
self._execute,
fake_conf,
ceph_vol_client=MockVolumeClientModule.CephFSVolumeClient()
)
ip_versions = ['foo', 'bar']
helper.configured_ip_versions = ip_versions
result = helper.get_configured_ip_versions()
self.assertEqual(ip_versions, result)
def test_default_config_hook(self):
fake_conf_dict = {'key': 'value1'}

View File

@ -30,7 +30,9 @@
cat << 'EOF' >>"/tmp/dg-local.conf"
[[local|localrc]]
enable_plugin manila https://opendev.org/openstack/manila
enable_plugin manila-tempest-plugin https://opendev.org/openstack/manila-tempest-plugin
enable_plugin neutron-dynamic-routing https://opendev.org/openstack/neutron-dynamic-routing
enable_plugin neutron-tempest-plugin https://opendev.org/openstack/neutron-tempest-plugin
enable_plugin devstack-plugin-ceph https://opendev.org/openstack/devstack-plugin-ceph
# Enable CephFS as the backend for Manila.
@ -62,12 +64,12 @@
set -x
export PYTHONUNBUFFERED=true
export DEVSTACK_GATE_NEUTRON=1
export ENABLED_SERVICES=tempest
export PROJECTS="openstack/devstack-plugin-ceph $PROJECTS"
export DEVSTACK_PROJECT_FROM_GIT="python-manilaclient"
export KEEP_LOCALRC=1
export PROJECTS="openstack/manila-tempest-plugin $PROJECTS"
export MANILA_SETUP_IPV6=True
export RUN_MANILA_IPV6_TESTS=True
# Basic services needed for minimal job
OVERRIDE_ENABLED_SERVICES=key,mysql,rabbit,tempest
# Enable glance for scenario tests
OVERRIDE_ENABLED_SERVICES+=,g-api,g-reg
@ -75,6 +77,18 @@
OVERRIDE_ENABLED_SERVICES+=,n-api,n-cpu,n-cond,n-sch,n-crt,n-cauth,n-obj
# Enable neutron for scenario tests
OVERRIDE_ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-l3,q-agt
# Enable tls-proxy
OVERRIDE_ENABLED_SERVICES+=,tls-proxy
OVERRIDE_ENABLED_SERVICES+=,placement-api,placement-client
export OVERRIDE_ENABLED_SERVICES
# Keep localrc to be able to set some vars in pre_test_hook
export KEEP_LOCALRC=1
PROJECTS="openstack/devstack-plugin-ceph $PROJECTS"
PROJECTS="openstack/manila-tempest-plugin $PROJECTS"
PROJECTS="openstack/neutron-dynamic-routing $PROJECTS"
PROJECTS="openstack/neutron-tempest-plugin $PROJECTS"
export PROJECTS
export DEVSTACK_GATE_USE_PYTHON3=True

View File

@ -0,0 +1,4 @@
---
features:
- IPv6 support for CephFS Manila driver with NFS gateway.