From 18b683a844e878b8b1dfbd656a22f4d5613f70c3 Mon Sep 17 00:00:00 2001 From: Irena Berezovsky Date: Mon, 18 Aug 2014 13:52:36 +0300 Subject: [PATCH] Detect if iproute2 support SR-IOV commands 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 Closes bug: 1349302 Change-Id: I5a72a9061a76c39960076f078905371f07069431 --- neutron/agent/linux/ip_link_support.py | 109 +++++++++++ neutron/cmd/sanity/checks.py | 17 ++ neutron/cmd/sanity_check.py | 17 ++ .../tests/functional/sanity/test_sanity.py | 3 + .../unit/agent/linux/test_ip_link_support.py | 175 ++++++++++++++++++ 5 files changed, 321 insertions(+) create mode 100644 neutron/agent/linux/ip_link_support.py create mode 100644 neutron/tests/unit/agent/linux/test_ip_link_support.py diff --git a/neutron/agent/linux/ip_link_support.py b/neutron/agent/linux/ip_link_support.py new file mode 100644 index 000000000..c7222d0fe --- /dev/null +++ b/neutron/agent/linux/ip_link_support.py @@ -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.*) \] \]" + + 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 diff --git a/neutron/cmd/sanity/checks.py b/neutron/cmd/sanity/checks.py index da3766355..3fbd6fae0 100644 --- a/neutron/cmd/sanity/checks.py +++ b/neutron/cmd/sanity/checks.py @@ -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 diff --git a/neutron/cmd/sanity_check.py b/neutron/cmd/sanity_check.py index e2d1e640d..b6a5b601b 100644 --- a/neutron/cmd/sanity_check.py +++ b/neutron/cmd/sanity_check.py @@ -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(): diff --git a/neutron/tests/functional/sanity/test_sanity.py b/neutron/tests/functional/sanity/test_sanity.py index bbe8911e5..4ee357d42 100644 --- a/neutron/tests/functional/sanity/test_sanity.py +++ b/neutron/tests/functional/sanity/test_sanity.py @@ -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 index 000000000..af6c8b4b5 --- /dev/null +++ b/neutron/tests/unit/agent/linux/test_ip_link_support.py @@ -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) -- 2.45.2