]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implement Mellanox ML2 MechanismDriver
authorIrena Berezovsky <irenab@mellanox.com>
Sun, 9 Feb 2014 06:06:45 +0000 (08:06 +0200)
committerThomas Goirand <thomas@goirand.fr>
Thu, 13 Mar 2014 07:20:42 +0000 (15:20 +0800)
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

13 files changed:
etc/neutron/plugins/ml2/ml2_conf.ini
etc/neutron/plugins/ml2/ml2_conf_mlnx.ini [new file with mode: 0644]
neutron/extensions/portbindings.py
neutron/plugins/ml2/db.py
neutron/plugins/ml2/drivers/mlnx/__init__.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/mlnx/config.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/mlnx/mech_mlnx.py [new file with mode: 0644]
neutron/plugins/ml2/rpc.py
neutron/plugins/mlnx/agent/eswitch_neutron_agent.py
neutron/tests/unit/ml2/_test_mech_agent.py
neutron/tests/unit/ml2/drivers/test_mech_mlnx.py [new file with mode: 0644]
neutron/tests/unit/mlnx/test_mlnx_neutron_agent.py
setup.cfg

index a9b0d508b674c8c04193a4f5e6783d9bf8fe55ec..55af11f7bb53343070e4ca7cebf058c87d48d7dd 100644 (file)
@@ -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 (file)
index 0000000..46139ae
--- /dev/null
@@ -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
index 6c1ed7ed407ea3a0974d72cb41458a26390a4983..acf0efcd25917a68bc28ecf76bb51fb861683416 100644 (file)
@@ -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'
index 861042fea40dbe9e4af434914d6db950e70924db..4cf8eed328bb3facedec580d24c5b21c81d8e8b8 100644 (file)
@@ -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 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/ml2/drivers/mlnx/config.py b/neutron/plugins/ml2/drivers/mlnx/config.py
new file mode 100644 (file)
index 0000000..eac221b
--- /dev/null
@@ -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 (file)
index 0000000..13d7b50
--- /dev/null
@@ -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
index f44a3eb1e329adb93d4daeedc03ffcd5bc0fd718..b77ddd1372f39f46a679138cdaf98105ee765a0e 100644 (file)
@@ -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):
index d546dd5d538dc867ee7d98b253d96dee43267673..611290b990b7462419ac2a2f1bcc942a2851da62 100644 (file)
@@ -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})
index 0714b58907dac00f4ecd37a433a8d4c493c27439..4339ac2ea2d5f2294a915cd49023a18d6f485c60 100644 (file)
@@ -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 (file)
index 0000000..382e54e
--- /dev/null
@@ -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)
index 79b4f56b464515fd752f684cf66ae045e21cb8f5..a78cc42e4bad3da70d50fae850dbbc9281377452 100644 (file)
@@ -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',
index bc93250d5fc6488d0781dba3ad5a8a8c065f14a0..1131453439bf23d3b43181b3f3f2684e2bcdb7d2 100644 (file)
--- 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