diff --git a/doc/source/devref/ovs_vhostuser.rst b/doc/source/devref/ovs_vhostuser.rst new file mode 100644 index 00000000000..775828bcae9 --- /dev/null +++ b/doc/source/devref/ovs_vhostuser.rst @@ -0,0 +1,62 @@ +.. + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + + + Convention for heading levels in Neutron devref: + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + (Avoid deeper levels because they do not render well.) + + +Neutron Open vSwitch vhost-user support +======================================= + +Neutron supports using Open vSwitch + DPDK vhost-user interfaces directly in +the OVS ML2 driver and agent. The current implementation relies on a multiple +configuration values and includes runtime verification of Open vSwitch's +capability to provide these interfaces. + +The OVS agent detects the capability of the underlying Open vSwitch +installation and passes that information over RPC via the agent +'configurations' dictionary. The ML2 driver uses this information to select +the proper VIF type and binding details. + +Neutron+OVS+DPDK platform requirements +-------------------------------------- +OVS 2.4.0+ +DPDK 2.0+ + +Neutron OVS+DPDK vhost-user config +---------------------------------- + +[OVS] +datapath_type=netdev +vhostuser_socket_dir=/var/run/openvswitch + +When OVS is running with DPDK support enabled, and the datapath_type is set to +"netdev", then the OVS ML2 driver will use the vhost-user VIF type and pass +the necessary binding details to use OVS+DPDK and vhost-user sockets. This +includes the vhoustuser_socket_dir setting, which must match the directory +passed to ovs-vswitchd on startup. + +What about the networking-ovs-dpdk repo? +---------------------------------------- + +The networking-ovs-dpdk repo will continue to exist and undergo active +development. This feature just removes the necessity for a separate ML2 driver +and OVS agent in the networking-ovs-dpdk repo. The networking-ovs-dpdk project +also provides a devstack plugin which also allows automated CI, a puppet +module, and an OpenFlow-based security group implementation. diff --git a/etc/neutron/plugins/ml2/openvswitch_agent.ini b/etc/neutron/plugins/ml2/openvswitch_agent.ini index ad096871f67..441079c7865 100644 --- a/etc/neutron/plugins/ml2/openvswitch_agent.ini +++ b/etc/neutron/plugins/ml2/openvswitch_agent.ini @@ -82,6 +82,10 @@ # To enable the userspace datapath set this value to 'netdev' # datapath_type = system +# (StrOpt) OVS vhost-user socket directory. +# '/var/run/openvswitch' is the default value +# vhostuser_socket_dir = /var/run/openvswitch + [agent] # Log agent heartbeats from this OVS agent # log_agent_heartbeats = False diff --git a/neutron/agent/common/ovs_lib.py b/neutron/agent/common/ovs_lib.py index 406e5e35c69..41ce3993bf1 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -53,6 +53,11 @@ cfg.CONF.register_opts(OPTS) LOG = logging.getLogger(__name__) +OVS_DEFAULT_CAPS = { + 'datapath_types': [], + 'iface_types': [], +} + def _ofport_result_pending(result): """Return True if ovs-vsctl indicates the result is still pending.""" @@ -146,6 +151,23 @@ class BaseOVS(object): return self.ovsdb.db_get(table, record, column).execute( check_error=check_error, log_errors=log_errors) + @property + def config(self): + """A dict containing the only row from the root Open_vSwitch table + + This row contains several columns describing the Open vSwitch install + and the system on which it is installed. Useful keys include: + datapath_types: a list of supported datapath types + iface_types: a list of supported interface types + ovs_version: the OVS version + """ + return self.ovsdb.db_list("Open_vSwitch").execute()[0] + + @property + def capabilities(self): + _cfg = self.config + return {k: _cfg.get(k, OVS_DEFAULT_CAPS[k]) for k in OVS_DEFAULT_CAPS} + class OVSBridge(BaseOVS): def __init__(self, br_name, datapath_type=constants.OVS_DATAPATH_SYSTEM): diff --git a/neutron/common/constants.py b/neutron/common/constants.py index a5f34ee9d2d..70072534361 100644 --- a/neutron/common/constants.py +++ b/neutron/common/constants.py @@ -155,6 +155,8 @@ LLA_TASK_TIMEOUT = 40 # Linux interface max length DEVICE_NAME_MAX_LEN = 15 +# vhost-user device names start with "vhu" +VHOST_USER_DEVICE_PREFIX = 'vhu' # Device names start with "tap" TAP_DEVICE_PREFIX = 'tap' # The vswitch side of a veth pair for a nova iptables filter setup diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py index 5fcdb56234b..d4248254462 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py @@ -53,6 +53,8 @@ ovs_opts = [ choices=[constants.OVS_DATAPATH_SYSTEM, constants.OVS_DATAPATH_NETDEV], help=_("OVS datapath to use.")), + cfg.StrOpt('vhostuser_socket_dir', default=constants.VHOST_USER_SOCKET_DIR, + help=_("OVS vhost-user socket directory.")), cfg.IPOpt('of_listen_address', default='127.0.0.1', help=_("Address to listen on for OpenFlow connections. " "Used only for 'native' driver.")), diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py index 095553d5c31..ba256c1f826 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py @@ -100,5 +100,9 @@ EXTENSION_DRIVER_TYPE = 'ovs' # ovs datapath types OVS_DATAPATH_SYSTEM = 'system' OVS_DATAPATH_NETDEV = 'netdev' +OVS_DPDK_VHOST_USER = 'dpdkvhostuser' + +# default ovs vhost-user socket location +VHOST_USER_SOCKET_DIR = '/var/run/openvswitch' MAX_DEVICE_RETRIES = 5 diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index 085c093ed97..3a5476d7275 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -181,6 +181,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, ''' super(OVSNeutronAgent, self).__init__() self.conf = conf or cfg.CONF + self.ovs = ovs_lib.BaseOVS() # init bridge classes with configured datapath type. self.br_int_cls, self.br_phys_cls, self.br_tun_cls = ( @@ -282,7 +283,11 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, self.enable_distributed_routing, 'log_agent_heartbeats': self.conf.AGENT.log_agent_heartbeats, - 'extensions': self.ext_manager.names()}, + 'extensions': self.ext_manager.names(), + 'datapath_type': self.conf.OVS.datapath_type, + 'ovs_capabilities': self.ovs.capabilities, + 'vhostuser_socket_dir': + self.conf.OVS.vhostuser_socket_dir}, 'agent_type': self.conf.AGENT.agent_type, 'start_flag': True} diff --git a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py index 2ad29dd00b3..cc7007ac397 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py +++ b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py @@ -13,13 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from oslo_log import log from neutron.agent import securitygroups_rpc from neutron.common import constants from neutron.extensions import portbindings from neutron.plugins.common import constants as p_constants +from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2.drivers import mech_agent +from neutron.plugins.ml2.drivers.openvswitch.agent.common \ + import constants as a_const from neutron.services.qos import qos_consts LOG = log.getLogger(__name__) @@ -57,3 +62,44 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): def check_vlan_transparency(self, context): """Currently Openvswitch driver doesn't support vlan transparency.""" return False + + def try_to_bind_segment_for_agent(self, context, segment, agent): + if self.check_segment_for_agent(segment, agent): + context.set_binding(segment[api.ID], + self.get_vif_type(agent, context), + self.get_vif_details(agent, context)) + return True + else: + return False + + def get_vif_type(self, agent, context): + caps = agent['configurations'].get('ovs_capabilities', {}) + if (a_const.OVS_DPDK_VHOST_USER in caps.get('iface_types', []) and + agent['configurations'].get('datapath_type') == + a_const.OVS_DATAPATH_NETDEV): + return portbindings.VIF_TYPE_VHOST_USER + return self.vif_type + + def get_vif_details(self, agent, context): + if (agent['configurations'].get('datapath_type') != + a_const.OVS_DATAPATH_NETDEV): + return self.vif_details + caps = agent['configurations'].get('ovs_capabilities', {}) + if a_const.OVS_DPDK_VHOST_USER in caps.get('iface_types', []): + sock_path = self.agent_vhu_sockpath(agent, context.current['id']) + return { + portbindings.CAP_PORT_FILTER: False, + portbindings.VHOST_USER_MODE: + portbindings.VHOST_USER_MODE_CLIENT, + portbindings.VHOST_USER_OVS_PLUG: True, + portbindings.VHOST_USER_SOCKET: sock_path + } + return self.vif_details + + @staticmethod + def agent_vhu_sockpath(agent, port_id): + """Return the agent's vhost-user socket path for a given port""" + sockdir = agent['configurations'].get('vhostuser_socket_dir', + a_const.VHOST_USER_SOCKET_DIR) + sock_name = (constants.VHOST_USER_DEVICE_PREFIX + port_id)[:14] + return os.path.join(sockdir, sock_name) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index 7838be8a88c..cd85720c906 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -129,6 +129,9 @@ class TestOvsNeutronAgent(object): 'neutron.agent.common.ovs_lib.OVSBridge.get_ports_attributes', return_value=[]).start() + mock.patch('neutron.agent.common.ovs_lib.BaseOVS.config', + new_callable=mock.PropertyMock, + return_value={}).start() with mock.patch.object(self.mod_agent.OVSNeutronAgent, 'setup_integration_br'),\ mock.patch.object(self.mod_agent.OVSNeutronAgent, @@ -199,7 +202,10 @@ class TestOvsNeutronAgent(object): new=MockFixedIntervalLoopingCall), \ mock.patch( 'neutron.agent.common.ovs_lib.OVSBridge.' 'get_vif_ports', - return_value=[]): + return_value=[]), \ + mock.patch('neutron.agent.common.ovs_lib.BaseOVS.config', + new_callable=mock.PropertyMock, + return_value={'datapath_types': ['netdev']}): # validate setting non default datapath expected = constants.OVS_DATAPATH_NETDEV cfg.CONF.set_override('datapath_type', @@ -1610,6 +1616,9 @@ class AncillaryBridgesTest(object): group='SECURITYGROUP') cfg.CONF.set_override('report_interval', 0, 'AGENT') self.kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) + mock.patch('neutron.agent.common.ovs_lib.BaseOVS.config', + new_callable=mock.PropertyMock, + return_value={}).start() def _test_ancillary_bridges(self, bridges, ancillary): device_ids = ancillary[:] @@ -1727,6 +1736,9 @@ class TestOvsDvrNeutronAgent(object): group='SECURITYGROUP') kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) + mock.patch('neutron.agent.common.ovs_lib.BaseOVS.config', + new_callable=mock.PropertyMock, + return_value={}).start() with mock.patch.object(self.mod_agent.OVSNeutronAgent, 'setup_integration_br'),\ mock.patch.object(self.mod_agent.OVSNeutronAgent, diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index 079d2dea083..e0a38e79efe 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -97,6 +97,10 @@ class TunnelTest(object): self.inta = mock.Mock() self.intb = mock.Mock() + mock.patch.object(ovs_lib.BaseOVS, 'config', + new_callable=mock.PropertyMock, + return_value={}).start() + self.ovs_bridges = { self.INT_BRIDGE: mock.create_autospec( self.br_int_cls('br-int')),