Implement Mellanox ML2 MechanismDriver
This commit adds support for currently provided Mellanox Plugin embedded switch functionality as part of the VPI (Ethernet/InfiniBand) HCA as an ML2 MechanismDriver. MechanismDriver adds support for VNIC_DIRECT and VNIC_MACVTAP vnic types. MechanismDriver provides configurable default vif_type for neutron port created with default VNIC_NORMAL vnic type till nova api support for vnic_type is available. Implements blueprint mlnx-ml2-support Change-Id: I16ad318f095b7af879e1b99dcc7f5f9e92facd2b
This commit is contained in:
parent
2d2d9b85b5
commit
029057a870
@ -15,6 +15,7 @@
|
||||
# (ListOpt) Ordered list of networking mechanism driver entrypoints
|
||||
# to be loaded from the neutron.ml2.mechanism_drivers namespace.
|
||||
# mechanism_drivers =
|
||||
# Example: mechanism drivers = openvswitch,mlnx
|
||||
# Example: mechanism_drivers = arista
|
||||
# Example: mechanism_drivers = cisco,logger
|
||||
|
||||
|
4
etc/neutron/plugins/ml2/ml2_conf_mlnx.ini
Normal file
4
etc/neutron/plugins/ml2/ml2_conf_mlnx.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[eswitch]
|
||||
# (StrOpt) Type of Network Interface to allocate for VM:
|
||||
# mlnx_direct or hostdev according to libvirt terminology
|
||||
# vnic_type = mlnx_direct
|
@ -55,11 +55,13 @@ VIF_TYPE_802_QBG = '802.1qbg'
|
||||
VIF_TYPE_802_QBH = '802.1qbh'
|
||||
VIF_TYPE_HYPERV = 'hyperv'
|
||||
VIF_TYPE_MIDONET = 'midonet'
|
||||
VIF_TYPE_MLNX_DIRECT = 'mlnx_direct'
|
||||
VIF_TYPE_MLNX_HOSTDEV = 'hostdev'
|
||||
VIF_TYPE_OTHER = 'other'
|
||||
VIF_TYPES = [VIF_TYPE_UNBOUND, VIF_TYPE_BINDING_FAILED, VIF_TYPE_OVS,
|
||||
VIF_TYPE_IVS, VIF_TYPE_BRIDGE, VIF_TYPE_802_QBG,
|
||||
VIF_TYPE_802_QBH, VIF_TYPE_HYPERV, VIF_TYPE_MIDONET,
|
||||
VIF_TYPE_OTHER]
|
||||
VIF_TYPE_MLNX_DIRECT, VIF_TYPE_MLNX_HOSTDEV, VIF_TYPE_OTHER]
|
||||
|
||||
VNIC_NORMAL = 'normal'
|
||||
VNIC_DIRECT = 'direct'
|
||||
|
@ -87,6 +87,13 @@ def get_port(session, port_id):
|
||||
return
|
||||
|
||||
|
||||
def get_port_from_device_mac(device_mac):
|
||||
LOG.debug(_("get_port_from_device_mac() called for mac %s"), device_mac)
|
||||
session = db_api.get_session()
|
||||
qry = session.query(models_v2.Port).filter_by(mac_address=device_mac)
|
||||
return qry.first()
|
||||
|
||||
|
||||
def get_port_and_sgs(port_id):
|
||||
"""Get port from database with security group info."""
|
||||
|
||||
|
0
neutron/plugins/ml2/drivers/mlnx/__init__.py
Normal file
0
neutron/plugins/ml2/drivers/mlnx/__init__.py
Normal file
29
neutron/plugins/ml2/drivers/mlnx/config.py
Normal file
29
neutron/plugins/ml2/drivers/mlnx/config.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.extensions import portbindings
|
||||
|
||||
eswitch_opts = [
|
||||
cfg.StrOpt('vnic_type',
|
||||
default=portbindings.VIF_TYPE_MLNX_DIRECT,
|
||||
help=_("Type of VM network interface: mlnx_direct or "
|
||||
"hostdev")),
|
||||
]
|
||||
|
||||
|
||||
cfg.CONF.register_opts(eswitch_opts, "ESWITCH")
|
79
neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py
Normal file
79
neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.common import constants
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.openstack.common import log
|
||||
from neutron.plugins.ml2 import driver_api as api
|
||||
from neutron.plugins.ml2.drivers import mech_agent
|
||||
from neutron.plugins.ml2.drivers.mlnx import config # noqa
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class MlnxMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
|
||||
"""Attach to networks using Mellanox eSwitch L2 agent.
|
||||
|
||||
The MellanoxMechanismDriver integrates the ml2 plugin with the
|
||||
Mellanox eswitch L2 agent. Port binding with this driver requires the
|
||||
Mellanox eswitch agent to be running on the port's host, and that agent
|
||||
to have connectivity to at least one segment of the port's
|
||||
network.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# REVISIT(irenab): update supported_vnic_types to contain
|
||||
# only VNIC_DIRECT and VNIC_MACVTAP once its possible to specify
|
||||
# vnic_type via nova API/GUI. Currently VNIC_NORMAL is included
|
||||
# to enable VM creation via GUI. It should be noted, that if
|
||||
# several MDs are capable to bing bind port on chosen host, the
|
||||
# first listed MD will bind the port for VNIC_NORMAL.
|
||||
super(MlnxMechanismDriver, self).__init__(
|
||||
constants.AGENT_TYPE_MLNX,
|
||||
cfg.CONF.ESWITCH.vnic_type,
|
||||
{portbindings.CAP_PORT_FILTER: False},
|
||||
portbindings.VNIC_TYPES)
|
||||
|
||||
def check_segment_for_agent(self, segment, agent):
|
||||
mappings = agent['configurations'].get('interface_mappings', {})
|
||||
LOG.debug(_("Checking segment: %(segment)s "
|
||||
"for mappings: %(mappings)s "),
|
||||
{'segment': segment, 'mappings': mappings})
|
||||
|
||||
network_type = segment[api.NETWORK_TYPE]
|
||||
if network_type == 'local':
|
||||
return True
|
||||
elif network_type in ['flat', 'vlan']:
|
||||
return segment[api.PHYSICAL_NETWORK] in mappings
|
||||
else:
|
||||
return False
|
||||
|
||||
def try_to_bind_segment_for_agent(self, context, segment, agent):
|
||||
if self.check_segment_for_agent(segment, agent):
|
||||
vif_type = self._get_vif_type(
|
||||
context.current[portbindings.VNIC_TYPE])
|
||||
context.set_binding(segment[api.ID],
|
||||
vif_type,
|
||||
self.vif_details)
|
||||
|
||||
def _get_vif_type(self, requested_vnic_type):
|
||||
if requested_vnic_type == portbindings.VNIC_MACVTAP:
|
||||
return portbindings.VIF_TYPE_MLNX_DIRECT
|
||||
elif requested_vnic_type == portbindings.VNIC_DIRECT:
|
||||
return portbindings.VIF_TYPE_MLNX_HOSTDEV
|
||||
return self.vif_type
|
@ -24,6 +24,7 @@ from neutron.db import securitygroups_rpc_base as sg_db_rpc
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log
|
||||
from neutron.openstack.common.rpc import proxy
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.ml2 import db
|
||||
from neutron.plugins.ml2 import driver_api as api
|
||||
from neutron.plugins.ml2.drivers import type_tunnel
|
||||
@ -69,7 +70,13 @@ class RpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
|
||||
if device.startswith(TAP_DEVICE_PREFIX):
|
||||
return device[TAP_DEVICE_PREFIX_LENGTH:]
|
||||
else:
|
||||
return device
|
||||
# REVISIT(irenab): Consider calling into bound MD to
|
||||
# handle the get_device_details RPC, then remove the 'else' clause
|
||||
if not uuidutils.is_uuid_like(device):
|
||||
port = db.get_port_from_device_mac(device)
|
||||
if port:
|
||||
return port.id
|
||||
return device
|
||||
|
||||
@classmethod
|
||||
def get_port_from_device(cls, device):
|
||||
|
@ -190,7 +190,8 @@ class MlnxEswitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
||||
# update plugin about port status
|
||||
self.agent.plugin_rpc.update_device_up(self.context,
|
||||
port['mac_address'],
|
||||
self.agent.agent_id)
|
||||
self.agent.agent_id,
|
||||
cfg.CONF.host)
|
||||
else:
|
||||
self.eswitch.port_down(net_id,
|
||||
physical_network,
|
||||
@ -199,7 +200,8 @@ class MlnxEswitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
||||
self.agent.plugin_rpc.update_device_down(
|
||||
self.context,
|
||||
port['mac_address'],
|
||||
self.agent.agent_id)
|
||||
self.agent.agent_id,
|
||||
cfg.CONF.host)
|
||||
except rpc_common.Timeout:
|
||||
LOG.error(_("RPC timeout while updating port %s"), port['id'])
|
||||
else:
|
||||
@ -227,11 +229,12 @@ class MlnxEswitchNeutronAgent(sg_rpc.SecurityGroupAgentRpcMixin):
|
||||
def __init__(self, interface_mapping):
|
||||
self._polling_interval = cfg.CONF.AGENT.polling_interval
|
||||
self._setup_eswitches(interface_mapping)
|
||||
configurations = {'interface_mappings': interface_mapping}
|
||||
self.agent_state = {
|
||||
'binary': 'neutron-mlnx-agent',
|
||||
'host': cfg.CONF.host,
|
||||
'topic': q_constants.L2_AGENT_TOPIC,
|
||||
'configurations': interface_mapping,
|
||||
'configurations': configurations,
|
||||
'agent_type': q_constants.AGENT_TYPE_MLNX,
|
||||
'start_flag': True}
|
||||
self._setup_rpc()
|
||||
@ -245,7 +248,7 @@ class MlnxEswitchNeutronAgent(sg_rpc.SecurityGroupAgentRpcMixin):
|
||||
def _report_state(self):
|
||||
try:
|
||||
devices = len(self.eswitch.get_vnics_mac())
|
||||
self.agent_state['configurations']['devices'] = devices
|
||||
self.agent_state.get('configurations')['devices'] = devices
|
||||
self.state_rpc.report_state(self.context,
|
||||
self.agent_state)
|
||||
self.agent_state.pop('start_flag', None)
|
||||
@ -336,7 +339,7 @@ class MlnxEswitchNeutronAgent(sg_rpc.SecurityGroupAgentRpcMixin):
|
||||
LOG.info(_("Port %s updated"), device)
|
||||
LOG.debug(_("Device details %s"), str(dev_details))
|
||||
self.treat_vif_port(dev_details['port_id'],
|
||||
dev_details['port_mac'],
|
||||
dev_details['device'],
|
||||
dev_details['network_id'],
|
||||
dev_details['network_type'],
|
||||
dev_details['physical_network'],
|
||||
@ -359,7 +362,8 @@ class MlnxEswitchNeutronAgent(sg_rpc.SecurityGroupAgentRpcMixin):
|
||||
port_id = self.eswitch.get_port_id_by_mac(device)
|
||||
dev_details = self.plugin_rpc.update_device_down(self.context,
|
||||
port_id,
|
||||
self.agent_id)
|
||||
self.agent_id,
|
||||
cfg.CONF.host)
|
||||
except Exception as e:
|
||||
LOG.debug(_("Removing port failed for device %(device)s "
|
||||
"due to %(exc)s"), {'device': device, 'exc': e})
|
||||
|
@ -40,17 +40,20 @@ class FakeNetworkContext(api.NetworkContext):
|
||||
|
||||
|
||||
class FakePortContext(api.PortContext):
|
||||
def __init__(self, agent_type, agents, segments):
|
||||
def __init__(self, agent_type, agents, segments,
|
||||
vnic_type=portbindings.VNIC_NORMAL):
|
||||
self._agent_type = agent_type
|
||||
self._agents = agents
|
||||
self._network_context = FakeNetworkContext(segments)
|
||||
self._bound_vnic_type = vnic_type
|
||||
self._bound_segment_id = None
|
||||
self._bound_vif_type = None
|
||||
self._bound_vif_details = None
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
return {'id': PORT_ID}
|
||||
return {'id': PORT_ID,
|
||||
'binding:vnic_type': self._bound_vnic_type}
|
||||
|
||||
@property
|
||||
def original(self):
|
||||
|
89
neutron/tests/unit/ml2/drivers/test_mech_mlnx.py
Normal file
89
neutron/tests/unit/ml2/drivers/test_mech_mlnx.py
Normal file
@ -0,0 +1,89 @@
|
||||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from neutron.common import constants
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.plugins.ml2.drivers.mlnx import mech_mlnx
|
||||
from neutron.tests.unit.ml2 import _test_mech_agent as base
|
||||
|
||||
|
||||
class MlnxMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
|
||||
VIF_TYPE = portbindings.VIF_TYPE_MLNX_DIRECT
|
||||
CAP_PORT_FILTER = False
|
||||
AGENT_TYPE = constants.AGENT_TYPE_MLNX
|
||||
|
||||
GOOD_MAPPINGS = {'fake_physical_network': 'fake_bridge'}
|
||||
GOOD_CONFIGS = {'interface_mappings': GOOD_MAPPINGS}
|
||||
|
||||
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_bridge'}
|
||||
BAD_CONFIGS = {'interface_mappings': BAD_MAPPINGS}
|
||||
|
||||
AGENTS = [{'alive': True,
|
||||
'configurations': GOOD_CONFIGS}]
|
||||
AGENTS_DEAD = [{'alive': False,
|
||||
'configurations': GOOD_CONFIGS}]
|
||||
AGENTS_BAD = [{'alive': False,
|
||||
'configurations': GOOD_CONFIGS},
|
||||
{'alive': True,
|
||||
'configurations': BAD_CONFIGS}]
|
||||
|
||||
def setUp(self):
|
||||
super(MlnxMechanismBaseTestCase, self).setUp()
|
||||
self.driver = mech_mlnx.MlnxMechanismDriver()
|
||||
self.driver.initialize()
|
||||
|
||||
|
||||
class MlnxMechanismGenericTestCase(MlnxMechanismBaseTestCase,
|
||||
base.AgentMechanismGenericTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class MlnxMechanismLocalTestCase(MlnxMechanismBaseTestCase,
|
||||
base.AgentMechanismLocalTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class MlnxMechanismFlatTestCase(MlnxMechanismBaseTestCase,
|
||||
base.AgentMechanismFlatTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class MlnxMechanismVlanTestCase(MlnxMechanismBaseTestCase,
|
||||
base.AgentMechanismVlanTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class MlnxMechanismVnicTypeTestCase(MlnxMechanismBaseTestCase,
|
||||
base.AgentMechanismVlanTestCase):
|
||||
def _check_vif_type_for_vnic_type(self, vnic_type,
|
||||
expected_vif_type):
|
||||
context = base.FakePortContext(self.AGENT_TYPE,
|
||||
self.AGENTS,
|
||||
self.VLAN_SEGMENTS,
|
||||
vnic_type)
|
||||
self.driver.bind_port(context)
|
||||
self.assertEqual(expected_vif_type, context._bound_vif_type)
|
||||
|
||||
def test_vnic_type_direct(self):
|
||||
self._check_vif_type_for_vnic_type(portbindings.VNIC_DIRECT,
|
||||
portbindings.VIF_TYPE_MLNX_HOSTDEV)
|
||||
|
||||
def test_vnic_type_macvtap(self):
|
||||
self._check_vif_type_for_vnic_type(portbindings.VNIC_MACVTAP,
|
||||
portbindings.VIF_TYPE_MLNX_DIRECT)
|
||||
|
||||
def test_vnic_type_normal(self):
|
||||
self._check_vif_type_for_vnic_type(portbindings.VNIC_NORMAL,
|
||||
self.VIF_TYPE)
|
@ -91,7 +91,7 @@ class TestEswitchAgent(base.BaseTestCase):
|
||||
|
||||
def test_treat_devices_added_updates_known_port_admin_down(self):
|
||||
details = {'port_id': '1234567890',
|
||||
'port_mac': '01:02:03:04:05:06',
|
||||
'device': '01:02:03:04:05:06',
|
||||
'network_id': '123456789',
|
||||
'network_type': 'vlan',
|
||||
'physical_network': 'default',
|
||||
|
@ -170,6 +170,7 @@ neutron.ml2.mechanism_drivers =
|
||||
l2population = neutron.plugins.ml2.drivers.l2pop.mech_driver:L2populationMechanismDriver
|
||||
bigswitch = neutron.plugins.ml2.drivers.mech_bigswitch.driver:BigSwitchMechanismDriver
|
||||
ofagent = neutron.plugins.ml2.drivers.mech_ofagent:OfagentMechanismDriver
|
||||
mlnx = neutron.plugins.ml2.drivers.mlnx.mech_mlnx:MlnxMechanismDriver
|
||||
neutron.openstack.common.cache.backends =
|
||||
memory = neutron.openstack.common.cache._backends.memory:MemoryBackend
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user