--- /dev/null
+..
+ 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.
# 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
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."""
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):
# 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
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.")),
# 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
'''
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 = (
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}
# 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__)
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)
'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,
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',
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[:]
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,
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')),