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:
parent
755013615c
commit
34d4d46c40
doc/source/devref
etc/neutron/plugins/ml2
neutron
agent/common
common
plugins/ml2/drivers/openvswitch
tests/unit/plugins/ml2/drivers/openvswitch/agent
62
doc/source/devref/ovs_vhostuser.rst
Normal file
62
doc/source/devref/ovs_vhostuser.rst
Normal 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.
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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.")),
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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')),
|
||||
|
Loading…
x
Reference in New Issue
Block a user