From f36c985e294d165b392b6eede8623908e4706f4c Mon Sep 17 00:00:00 2001 From: Irena Berezovsky Date: Sun, 9 Feb 2014 08:06:45 +0200 Subject: [PATCH] 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 --- etc/neutron/plugins/ml2/ml2_conf.ini | 1 + etc/neutron/plugins/ml2/ml2_conf_mlnx.ini | 4 + neutron/extensions/portbindings.py | 4 +- neutron/plugins/ml2/db.py | 7 ++ neutron/plugins/ml2/drivers/mlnx/__init__.py | 0 neutron/plugins/ml2/drivers/mlnx/config.py | 29 ++++++ neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py | 79 ++++++++++++++++ neutron/plugins/ml2/rpc.py | 9 +- .../mlnx/agent/eswitch_neutron_agent.py | 16 ++-- neutron/tests/unit/ml2/_test_mech_agent.py | 7 +- .../tests/unit/ml2/drivers/test_mech_mlnx.py | 89 +++++++++++++++++++ .../unit/mlnx/test_mlnx_neutron_agent.py | 2 +- setup.cfg | 1 + 13 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 etc/neutron/plugins/ml2/ml2_conf_mlnx.ini create mode 100644 neutron/plugins/ml2/drivers/mlnx/__init__.py create mode 100644 neutron/plugins/ml2/drivers/mlnx/config.py create mode 100644 neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py create mode 100644 neutron/tests/unit/ml2/drivers/test_mech_mlnx.py diff --git a/etc/neutron/plugins/ml2/ml2_conf.ini b/etc/neutron/plugins/ml2/ml2_conf.ini index a9b0d508b6..55af11f7bb 100644 --- a/etc/neutron/plugins/ml2/ml2_conf.ini +++ b/etc/neutron/plugins/ml2/ml2_conf.ini @@ -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 diff --git a/etc/neutron/plugins/ml2/ml2_conf_mlnx.ini b/etc/neutron/plugins/ml2/ml2_conf_mlnx.ini new file mode 100644 index 0000000000..46139aed85 --- /dev/null +++ b/etc/neutron/plugins/ml2/ml2_conf_mlnx.ini @@ -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 diff --git a/neutron/extensions/portbindings.py b/neutron/extensions/portbindings.py index 6c1ed7ed40..acf0efcd25 100644 --- a/neutron/extensions/portbindings.py +++ b/neutron/extensions/portbindings.py @@ -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' diff --git a/neutron/plugins/ml2/db.py b/neutron/plugins/ml2/db.py index 861042fea4..4cf8eed328 100644 --- a/neutron/plugins/ml2/db.py +++ b/neutron/plugins/ml2/db.py @@ -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.""" diff --git a/neutron/plugins/ml2/drivers/mlnx/__init__.py b/neutron/plugins/ml2/drivers/mlnx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/neutron/plugins/ml2/drivers/mlnx/config.py b/neutron/plugins/ml2/drivers/mlnx/config.py new file mode 100644 index 0000000000..eac221b761 --- /dev/null +++ b/neutron/plugins/ml2/drivers/mlnx/config.py @@ -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") diff --git a/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py b/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py new file mode 100644 index 0000000000..13d7b50516 --- /dev/null +++ b/neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py @@ -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 diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index f44a3eb1e3..b77ddd1372 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -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): diff --git a/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py b/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py index d546dd5d53..611290b990 100644 --- a/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py +++ b/neutron/plugins/mlnx/agent/eswitch_neutron_agent.py @@ -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}) diff --git a/neutron/tests/unit/ml2/_test_mech_agent.py b/neutron/tests/unit/ml2/_test_mech_agent.py index 0714b58907..4339ac2ea2 100644 --- a/neutron/tests/unit/ml2/_test_mech_agent.py +++ b/neutron/tests/unit/ml2/_test_mech_agent.py @@ -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): diff --git a/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py b/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py new file mode 100644 index 0000000000..382e54e6ea --- /dev/null +++ b/neutron/tests/unit/ml2/drivers/test_mech_mlnx.py @@ -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) diff --git a/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py b/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py index 79b4f56b46..a78cc42e4b 100644 --- a/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py +++ b/neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py @@ -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', diff --git a/setup.cfg b/setup.cfg index bc93250d5f..1131453439 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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