]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Detect if iproute2 support SR-IOV commands
authorIrena Berezovsky <irenab@mellanox.com>
Mon, 18 Aug 2014 10:52:36 +0000 (13:52 +0300)
committerIrena Berezovsky <irenab@mellanox.com>
Wed, 29 Oct 2014 09:34:52 +0000 (11:34 +0200)
Add sanity_check that 'ip link' command supports VF management and
link state changes of VF by parsing 'ip link help' command output.

Co-authored-by: Samer Deeb <samerd@mellanox.com>
Closes bug: 1349302

Change-Id: I5a72a9061a76c39960076f078905371f07069431

neutron/agent/linux/ip_link_support.py [new file with mode: 0644]
neutron/cmd/sanity/checks.py
neutron/cmd/sanity_check.py
neutron/tests/functional/sanity/test_sanity.py
neutron/tests/unit/agent/linux/test_ip_link_support.py [new file with mode: 0644]

diff --git a/neutron/agent/linux/ip_link_support.py b/neutron/agent/linux/ip_link_support.py
new file mode 100644 (file)
index 0000000..c7222d0
--- /dev/null
@@ -0,0 +1,109 @@
+# Copyright 2014 Mellanox Technologies, Ltd
+#
+# 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.
+
+import re
+
+from neutron.agent.linux import utils
+from neutron.common import exceptions as n_exc
+from neutron.openstack.common.gettextutils import _LE
+from neutron.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+class IpLinkSupportError(n_exc.NeutronException):
+    pass
+
+
+class UnsupportedIpLinkCommand(IpLinkSupportError):
+    message = _("ip link command is not supported: %(reason)s")
+
+
+class InvalidIpLinkCapability(IpLinkSupportError):
+    message = _("ip link capability %(capability)s is not supported")
+
+
+class IpLinkConstants(object):
+    IP_LINK_CAPABILITY_STATE = "state"
+    IP_LINK_CAPABILITY_VLAN = "vlan"
+    IP_LINK_CAPABILITY_RATE = "rate"
+    IP_LINK_CAPABILITY_SPOOFCHK = "spoofchk"
+    IP_LINK_SUB_CAPABILITY_QOS = "qos"
+
+
+class IpLinkSupport(object):
+    VF_BLOCK_REGEX = "\[ vf NUM(?P<vf_block>.*) \] \]"
+
+    CAPABILITY_REGEX = "\[ %s (.*)"
+    SUB_CAPABILITY_REGEX = "\[ %(cap)s (.*) \[ %(subcap)s (.*)"
+
+    @classmethod
+    def get_vf_mgmt_section(cls, root_helper=None):
+        """Parses ip link help output, and gets vf block
+
+        :param root_helper: root permission helper
+
+        """
+        output = cls._get_ip_link_output(root_helper)
+        vf_block_pattern = re.search(cls.VF_BLOCK_REGEX,
+                                     output,
+                                     re.DOTALL | re.MULTILINE)
+        if vf_block_pattern:
+            return vf_block_pattern.group("vf_block")
+
+    @classmethod
+    def vf_mgmt_capability_supported(cls, vf_section, capability,
+                                     subcapability=None):
+        """Validate vf capability support
+
+        Checks if given vf capability (and sub capability
+        if given) supported
+        :param vf_section: vf Num block content
+        :param capability: for example: vlan, rate, spoofchk, state
+        :param subcapability: for example: qos
+        """
+        if not vf_section:
+            return False
+        if subcapability:
+            regex = cls.SUB_CAPABILITY_REGEX % {"cap": capability,
+                                                "subcap": subcapability}
+        else:
+            regex = cls.CAPABILITY_REGEX % capability
+        pattern_match = re.search(regex, vf_section,
+                                  re.DOTALL | re.MULTILINE)
+        return pattern_match is not None
+
+    @classmethod
+    def _get_ip_link_output(cls, root_helper):
+        """Gets the output of the ip link help command
+
+        Runs ip link help command and stores its output
+        Note: ip link help return error and writes its output to stderr
+                so we get the output from there. however, if this issue
+                will be solved and the command will write to stdout, we
+                will get the output from there too.
+        """
+        try:
+            ip_cmd = ['ip', 'link', 'help']
+            _stdout, _stderr = utils.execute(
+                ip_cmd, root_helper,
+                check_exit_code=False,
+                return_stderr=True,
+                log_fail_as_error=False)
+        except Exception as e:
+            LOG.exception(_LE("Failed executing ip command"))
+            raise UnsupportedIpLinkCommand(reason=e)
+        return _stdout or _stderr
index da3766355915b63b866c26384b74b662b6654f4d..3fbd6fae0dd4aa340b09baecd9b12f4d342dba0f 100644 (file)
@@ -15,6 +15,7 @@
 
 import netaddr
 
+from neutron.agent.linux import ip_link_support
 from neutron.agent.linux import ovs_lib
 from neutron.agent.linux import utils as agent_utils
 from neutron.common import utils
@@ -89,3 +90,19 @@ def arp_responder_supported(root_helper):
                                dl_vlan=42,
                                nw_dst='%s' % ip,
                                actions=actions)
+
+
+def vf_management_supported(root_helper):
+    try:
+        vf_section = ip_link_support.IpLinkSupport.get_vf_mgmt_section(
+                        root_helper)
+        if not ip_link_support.IpLinkSupport.vf_mgmt_capability_supported(
+                vf_section,
+                ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_STATE):
+            LOG.debug("ip link command does not support vf capability")
+            return False
+    except ip_link_support.UnsupportedIpLinkCommand:
+        LOG.exception(_("Unexpected exception while checking supported "
+                        "ip link command"))
+        return False
+    return True
index e2d1e640dd68ba7dc7208773234aea9cf84969df..b6a5b601b89901ca4868f86d24e0e838e51dd3c1 100644 (file)
@@ -25,6 +25,8 @@ from oslo.config import cfg
 LOG = logging.getLogger(__name__)
 cfg.CONF.import_group('AGENT', 'neutron.plugins.openvswitch.common.config')
 cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config')
+cfg.CONF.import_group('ml2_sriov',
+                      'neutron.plugins.ml2.drivers.mech_sriov.mech_driver')
 
 
 class BoolOptCallback(cfg.BoolOpt):
@@ -71,6 +73,16 @@ def check_arp_responder():
     return result
 
 
+def check_vf_management():
+    result = checks.vf_management_supported(
+        root_helper=cfg.CONF.AGENT.root_helper)
+    if not result:
+        LOG.error(_LE('Check for VF management support failed. '
+                      'Please ensure that the version of ip link '
+                      'being used has VF support.'))
+    return result
+
+
 # Define CLI opts to test specific features, with a calback for the test
 OPTS = [
     BoolOptCallback('ovs_vxlan', check_ovs_vxlan, default=False,
@@ -81,6 +93,9 @@ OPTS = [
                     help=_('Check for nova notification support')),
     BoolOptCallback('arp_responder', check_arp_responder, default=False,
                     help=_('Check for ARP responder support')),
+    BoolOptCallback('vf_management', check_vf_management, default=False,
+                    help=_('Check for VF management support')),
+
 ]
 
 
@@ -101,6 +116,8 @@ def enable_tests_from_config():
         cfg.CONF.set_override('nova_notify', True)
     if cfg.CONF.AGENT.arp_responder:
         cfg.CONF.set_override('arp_responder', True)
+    if cfg.CONF.ml2_sriov.agent_required:
+        cfg.CONF.set_override('vf_management', True)
 
 
 def all_tests_passed():
index bbe8911e534a4006e002048cb336a0eba5aa6d51..4ee357d426729bfef79b4f2dec13054a937fee35 100644 (file)
@@ -52,3 +52,6 @@ class SanityTestCaseRoot(functional_base.BaseSudoTestCase):
 
     def test_arp_responder_runs(self):
         checks.arp_responder_supported(self.root_helper)
+
+    def test_vf_management_runs(self):
+        checks.vf_management_supported(self.root_helper)
diff --git a/neutron/tests/unit/agent/linux/test_ip_link_support.py b/neutron/tests/unit/agent/linux/test_ip_link_support.py
new file mode 100644 (file)
index 0000000..af6c8b4
--- /dev/null
@@ -0,0 +1,175 @@
+# Copyright 2014 Mellanox Technologies, Ltd
+#
+# 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.
+
+
+import mock
+
+from neutron.agent.linux import ip_link_support as ip_link
+from neutron.tests import base
+
+
+class TestIpLinkSupport(base.BaseTestCase):
+    IP_LINK_HELP = """Usage: ip link add [link DEV] [ name ] NAME
+                   [ txqueuelen PACKETS ]
+                   [ address LLADDR ]
+                   [ broadcast LLADDR ]
+                   [ mtu MTU ] [index IDX ]
+                   [ numtxqueues QUEUE_COUNT ]
+                   [ numrxqueues QUEUE_COUNT ]
+                   type TYPE [ ARGS ]
+       ip link delete DEV type TYPE [ ARGS ]
+
+       ip link set { dev DEVICE | group DEVGROUP } [ { up | down } ]
+                          [ arp { on | off } ]
+                          [ dynamic { on | off } ]
+                          [ multicast { on | off } ]
+                          [ allmulticast { on | off } ]
+                          [ promisc { on | off } ]
+                          [ trailers { on | off } ]
+                          [ txqueuelen PACKETS ]
+                          [ name NEWNAME ]
+                          [ address LLADDR ]
+                          [ broadcast LLADDR ]
+                          [ mtu MTU ]
+                          [ netns PID ]
+                          [ netns NAME ]
+                          [ alias NAME ]
+                          [ vf NUM [ mac LLADDR ]
+                                   [ vlan VLANID [ qos VLAN-QOS ] ]
+                                   [ rate TXRATE ] ]
+                                   [ spoofchk { on | off} ] ]
+                                   [ state { auto | enable | disable} ] ]
+                          [ master DEVICE ]
+                          [ nomaster ]
+       ip link show [ DEVICE | group GROUP ] [up]
+
+TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | macvtap |
+          can | bridge | bond | ipoib | ip6tnl | ipip | sit |
+          vxlan | gre | gretap | ip6gre | ip6gretap | vti }
+    """
+
+    IP_LINK_HELP_NO_STATE = """Usage: ip link add link DEV [ name ] NAME
+                   [ txqueuelen PACKETS ]
+                   [ address LLADDR ]
+                   [ broadcast LLADDR ]
+                   [ mtu MTU ]
+                   type TYPE [ ARGS ]
+       ip link delete DEV type TYPE [ ARGS ]
+
+       ip link set DEVICE [ { up | down } ]
+                          [ arp { on | off } ]
+                          [ dynamic { on | off } ]
+                          [ multicast { on | off } ]
+                          [ allmulticast { on | off } ]
+                          [ promisc { on | off } ]
+                          [ trailers { on | off } ]
+                          [ txqueuelen PACKETS ]
+                          [ name NEWNAME ]
+                          [ address LLADDR ]
+                          [ broadcast LLADDR ]
+                          [ mtu MTU ]
+                          [ netns PID ]
+                          [ alias NAME ]
+                          [ vf NUM [ mac LLADDR ]
+                                   [ vlan VLANID [ qos VLAN-QOS ] ]
+                                   [ rate TXRATE ] ]
+       ip link show [ DEVICE ]
+
+TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | can }
+    """
+
+    IP_LINK_HELP_NO_VF = """Usage: ip link set DEVICE { up | down |
+                             arp { on | off } |
+                             dynamic { on | off } |
+                             multicast { on | off } |
+                             allmulticast { on | off } |
+                             promisc { on | off } |
+                             trailers { on | off } |
+                             txqueuelen PACKETS |
+                             name NEWNAME |
+                             address LLADDR | broadcast LLADDR |
+                             mtu MTU }
+       ip link show [ DEVICE ]
+
+    """
+
+    def _test_capability(self, capability, subcapability=None,
+                         expected=True, stdout="", stderr=""):
+        with mock.patch("neutron.agent.linux.utils.execute") as mock_exec:
+            mock_exec.return_value = (stdout, stderr)
+            vf_section = ip_link.IpLinkSupport.get_vf_mgmt_section()
+            capable = ip_link.IpLinkSupport.vf_mgmt_capability_supported(
+                vf_section, capability, subcapability)
+            self.assertEqual(expected, capable)
+            mock_exec.assert_called_once_with(['ip', 'link', 'help'],
+                                              None,
+                                              check_exit_code=False,
+                                              return_stderr=True,
+                                              log_fail_as_error=False)
+
+    def test_vf_mgmt(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE,
+            stderr=self.IP_LINK_HELP)
+
+    def test_execute_with_stdout(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE,
+            stdout=self.IP_LINK_HELP)
+
+    def test_vf_mgmt_no_state(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE,
+            expected=False,
+            stderr=self.IP_LINK_HELP_NO_STATE)
+
+    def test_vf_mgmt_no_vf(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE,
+            expected=False,
+            stderr=self.IP_LINK_HELP_NO_VF)
+
+    def test_vf_mgmt_unknown_capability(self):
+        self._test_capability(
+            "state1",
+            expected=False,
+            stderr=self.IP_LINK_HELP)
+
+    def test_vf_mgmt_sub_capability(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_VLAN,
+            ip_link.IpLinkConstants.IP_LINK_SUB_CAPABILITY_QOS,
+            stderr=self.IP_LINK_HELP)
+
+    def test_vf_mgmt_sub_capability_mismatch(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_STATE,
+            ip_link.IpLinkConstants.IP_LINK_SUB_CAPABILITY_QOS,
+            expected=False,
+            stderr=self.IP_LINK_HELP)
+
+    def test_vf_mgmt_sub_capability_invalid(self):
+        self._test_capability(
+            ip_link.IpLinkConstants.IP_LINK_CAPABILITY_VLAN,
+            "qos1",
+            expected=False,
+            stderr=self.IP_LINK_HELP)
+
+    def test_vf_mgmt_error(self):
+        with mock.patch("neutron.agent.linux.utils.execute") as mock_exec:
+            mock_exec.side_effect = Exception()
+            self.assertRaises(
+                ip_link.UnsupportedIpLinkCommand,
+                ip_link.IpLinkSupport.get_vf_mgmt_section)