Add vhost-user support via ovs capabilities/datapath_type

Adds the ovs 'config' property which returns the contents of the
single row of the Open_vSwitch table. This gives access to certain
OVS capabilities such as datapath_types and iface_types.

Using this information in concert with the datapath_type config
option, vif details are calculated by the OVS mech driver. If
datapath_type == 'netdev' and OVS on the agent host is capable of
supporting dpdkvhostuser, then it is used.

Authored-By: Terry Wilson <twilson@redhat.com>
Co-Authored-By: Sean Mooney <sean.k.mooney@intel.com>

Closes-Bug: #1506127
Change-Id: I5047f1d1276e2f52ff02a0cba136e222779d059c
This commit is contained in:
Terry Wilson 2015-10-15 18:50:40 -05:00
parent 755013615c
commit 34d4d46c40
10 changed files with 165 additions and 2 deletions

View File

@ -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.

View File

@ -82,6 +82,10 @@
# To enable the userspace datapath set this value to 'netdev' # To enable the userspace datapath set this value to 'netdev'
# datapath_type = system # datapath_type = system
# (StrOpt) OVS vhost-user socket directory.
# '/var/run/openvswitch' is the default value
# vhostuser_socket_dir = /var/run/openvswitch
[agent] [agent]
# Log agent heartbeats from this OVS agent # Log agent heartbeats from this OVS agent
# log_agent_heartbeats = False # log_agent_heartbeats = False

View File

@ -53,6 +53,11 @@ cfg.CONF.register_opts(OPTS)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
OVS_DEFAULT_CAPS = {
'datapath_types': [],
'iface_types': [],
}
def _ofport_result_pending(result): def _ofport_result_pending(result):
"""Return True if ovs-vsctl indicates the result is still pending.""" """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( return self.ovsdb.db_get(table, record, column).execute(
check_error=check_error, log_errors=log_errors) 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): class OVSBridge(BaseOVS):
def __init__(self, br_name, datapath_type=constants.OVS_DATAPATH_SYSTEM): def __init__(self, br_name, datapath_type=constants.OVS_DATAPATH_SYSTEM):

View File

@ -155,6 +155,8 @@ LLA_TASK_TIMEOUT = 40
# Linux interface max length # Linux interface max length
DEVICE_NAME_MAX_LEN = 15 DEVICE_NAME_MAX_LEN = 15
# vhost-user device names start with "vhu"
VHOST_USER_DEVICE_PREFIX = 'vhu'
# Device names start with "tap" # Device names start with "tap"
TAP_DEVICE_PREFIX = 'tap' TAP_DEVICE_PREFIX = 'tap'
# The vswitch side of a veth pair for a nova iptables filter setup # The vswitch side of a veth pair for a nova iptables filter setup

View File

@ -53,6 +53,8 @@ ovs_opts = [
choices=[constants.OVS_DATAPATH_SYSTEM, choices=[constants.OVS_DATAPATH_SYSTEM,
constants.OVS_DATAPATH_NETDEV], constants.OVS_DATAPATH_NETDEV],
help=_("OVS datapath to use.")), 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', cfg.IPOpt('of_listen_address', default='127.0.0.1',
help=_("Address to listen on for OpenFlow connections. " help=_("Address to listen on for OpenFlow connections. "
"Used only for 'native' driver.")), "Used only for 'native' driver.")),

View File

@ -100,5 +100,9 @@ EXTENSION_DRIVER_TYPE = 'ovs'
# ovs datapath types # ovs datapath types
OVS_DATAPATH_SYSTEM = 'system' OVS_DATAPATH_SYSTEM = 'system'
OVS_DATAPATH_NETDEV = 'netdev' 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 MAX_DEVICE_RETRIES = 5

View File

@ -181,6 +181,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
''' '''
super(OVSNeutronAgent, self).__init__() super(OVSNeutronAgent, self).__init__()
self.conf = conf or cfg.CONF self.conf = conf or cfg.CONF
self.ovs = ovs_lib.BaseOVS()
# init bridge classes with configured datapath type. # init bridge classes with configured datapath type.
self.br_int_cls, self.br_phys_cls, self.br_tun_cls = ( 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, self.enable_distributed_routing,
'log_agent_heartbeats': 'log_agent_heartbeats':
self.conf.AGENT.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, 'agent_type': self.conf.AGENT.agent_type,
'start_flag': True} 'start_flag': True}

View File

@ -13,13 +13,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import os
from oslo_log import log from oslo_log import log
from neutron.agent import securitygroups_rpc from neutron.agent import securitygroups_rpc
from neutron.common import constants from neutron.common import constants
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron.plugins.common import constants as p_constants 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 import mech_agent
from neutron.plugins.ml2.drivers.openvswitch.agent.common \
import constants as a_const
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -57,3 +62,44 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
def check_vlan_transparency(self, context): def check_vlan_transparency(self, context):
"""Currently Openvswitch driver doesn't support vlan transparency.""" """Currently Openvswitch driver doesn't support vlan transparency."""
return False 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)

View File

@ -129,6 +129,9 @@ class TestOvsNeutronAgent(object):
'neutron.agent.common.ovs_lib.OVSBridge.get_ports_attributes', 'neutron.agent.common.ovs_lib.OVSBridge.get_ports_attributes',
return_value=[]).start() 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, with mock.patch.object(self.mod_agent.OVSNeutronAgent,
'setup_integration_br'),\ 'setup_integration_br'),\
mock.patch.object(self.mod_agent.OVSNeutronAgent, mock.patch.object(self.mod_agent.OVSNeutronAgent,
@ -199,7 +202,10 @@ class TestOvsNeutronAgent(object):
new=MockFixedIntervalLoopingCall), \ new=MockFixedIntervalLoopingCall), \
mock.patch( mock.patch(
'neutron.agent.common.ovs_lib.OVSBridge.' 'get_vif_ports', '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 # validate setting non default datapath
expected = constants.OVS_DATAPATH_NETDEV expected = constants.OVS_DATAPATH_NETDEV
cfg.CONF.set_override('datapath_type', cfg.CONF.set_override('datapath_type',
@ -1610,6 +1616,9 @@ class AncillaryBridgesTest(object):
group='SECURITYGROUP') group='SECURITYGROUP')
cfg.CONF.set_override('report_interval', 0, 'AGENT') cfg.CONF.set_override('report_interval', 0, 'AGENT')
self.kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) 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): def _test_ancillary_bridges(self, bridges, ancillary):
device_ids = ancillary[:] device_ids = ancillary[:]
@ -1727,6 +1736,9 @@ class TestOvsDvrNeutronAgent(object):
group='SECURITYGROUP') group='SECURITYGROUP')
kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) 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, with mock.patch.object(self.mod_agent.OVSNeutronAgent,
'setup_integration_br'),\ 'setup_integration_br'),\
mock.patch.object(self.mod_agent.OVSNeutronAgent, mock.patch.object(self.mod_agent.OVSNeutronAgent,

View File

@ -97,6 +97,10 @@ class TunnelTest(object):
self.inta = mock.Mock() self.inta = mock.Mock()
self.intb = mock.Mock() self.intb = mock.Mock()
mock.patch.object(ovs_lib.BaseOVS, 'config',
new_callable=mock.PropertyMock,
return_value={}).start()
self.ovs_bridges = { self.ovs_bridges = {
self.INT_BRIDGE: mock.create_autospec( self.INT_BRIDGE: mock.create_autospec(
self.br_int_cls('br-int')), self.br_int_cls('br-int')),