From c5d8fd6329e405d1d584f4fedd7d0ede7da278da Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 11 May 2020 17:59:48 +0000 Subject: [PATCH] Use pyroute2 for SRIOV VF commands Recent changes in some versions of iproute2 CLI output (v4.18), have invalidated the regular expression used to parse the "ip link" output. To solve this problem and avoid future ones, pyroute2 is used to retrieve the virtual functions information and set the VF attributes (spoofcheck, min_tx_rate, max_tx_rate and link_state). pyroute2 extended the "ip link" support to retrieve this information, adding "ext_mask=1" in the get command. If no virtual functions are present in this particular network interface, the added method, "get_link_vfs", will return an empty list. The set commands can return a "InterfaceOperationNotSupported" in case the operation is not supported. For min_tx_rate, if the driver does not support to set a minimum bandwidth, an "InvalidArgument" (from a pyroute2.NetlinkError(22)) exception will be raised. Change-Id: I680da4f64bd114f1caecaaeedbf8a4b1915a0849 Closes-Bug: #1878042 --- neutron/agent/linux/ip_lib.py | 21 +- neutron/agent/linux/ip_link_support.py | 108 ----------- neutron/cmd/sanity/checks.py | 36 ---- neutron/cmd/sanity_check.py | 23 --- .../mech_sriov/agent/common/exceptions.py | 8 - .../mech_sriov/agent/eswitch_manager.py | 35 ++-- .../agent/extension_drivers/qos_driver.py | 15 +- .../ml2/drivers/mech_sriov/agent/pci_lib.py | 158 ++++----------- .../mech_sriov/agent/sriov_nic_agent.py | 9 +- neutron/privileged/agent/linux/ip_lib.py | 42 ++++ .../tests/functional/sanity/test_sanity.py | 6 - .../unit/agent/linux/test_ip_link_support.py | 181 ------------------ .../agent/test_linuxbridge_neutron_agent.py | 2 +- .../extension_drivers/test_qos_driver.py | 14 +- .../mech_sriov/agent/test_eswitch_manager.py | 38 ++-- .../drivers/mech_sriov/agent/test_pci_lib.py | 172 ++++++----------- .../mech_sriov/agent/test_sriov_nic_agent.py | 8 +- .../privileged/agent/linux/test_ip_lib.py | 33 ++++ 18 files changed, 237 insertions(+), 672 deletions(-) delete mode 100644 neutron/agent/linux/ip_link_support.py delete mode 100644 neutron/tests/unit/agent/linux/test_ip_link_support.py diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 31f8e3da7d3..414e57d4950 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -29,7 +29,6 @@ from pyroute2.netlink import exceptions as netlink_exceptions from pyroute2.netlink import rtnl from pyroute2.netlink.rtnl import ifaddrmsg from pyroute2.netlink.rtnl import ifinfmsg -from pyroute2 import NetlinkError from pyroute2 import netns from neutron._i18n import _ @@ -100,9 +99,7 @@ class AddressNotReady(exceptions.NeutronException): "become ready: %(reason)s") -class InvalidArgument(exceptions.NeutronException): - message = _("Invalid value %(value)s for parameter %(parameter)s " - "provided.") +InvalidArgument = privileged.InvalidArgument class SubProcessBase(object): @@ -442,13 +439,8 @@ class IpLinkCommand(IpDeviceCommandBase): self.name, self._parent.namespace, ifinfmsg.IFF_ALLMULTI) def set_mtu(self, mtu_size): - try: - privileged.set_link_attribute( - self.name, self._parent.namespace, mtu=mtu_size) - except NetlinkError as e: - if e.code == errno.EINVAL: - raise InvalidArgument(parameter="MTU", value=mtu_size) - raise + privileged.set_link_attribute( + self.name, self._parent.namespace, mtu=mtu_size) def set_up(self): privileged.set_link_attribute( @@ -520,6 +512,13 @@ class IpLinkCommand(IpDeviceCommandBase): def exists(self): return privileged.interface_exists(self.name, self._parent.namespace) + def get_vfs(self): + return privileged.get_link_vfs(self.name, self._parent.namespace) + + def set_vf_feature(self, vf_config): + return privileged.set_link_vf_feature( + self.name, self._parent.namespace, vf_config) + class IpAddrCommand(IpDeviceCommandBase): COMMAND = 'addr' diff --git a/neutron/agent/linux/ip_link_support.py b/neutron/agent/linux/ip_link_support.py deleted file mode 100644 index 46d40ed4feb..00000000000 --- a/neutron/agent/linux/ip_link_support.py +++ /dev/null @@ -1,108 +0,0 @@ -# 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_lib import exceptions as n_exc -from oslo_log import log as logging - -from neutron._i18n import _ -from neutron.agent.linux import utils - - -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_MIN_TX_RATE = "min_tx_rate" - IP_LINK_CAPABILITY_SPOOFCHK = "spoofchk" - IP_LINK_SUB_CAPABILITY_QOS = "qos" - - -class IpLinkSupport(object): - VF_BLOCK_REGEX = r"\[ vf NUM(?P.*) \] \]" - - CAPABILITY_REGEX = r"\[ %s (.*)" - SUB_CAPABILITY_REGEX = r"\[ %(cap)s (.*) \[ %(subcap)s (.*)" - - @classmethod - def get_vf_mgmt_section(cls): - """Parses ip link help output, and gets vf block""" - - output = cls._get_ip_link_output() - 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): - """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, - check_exit_code=False, - return_stderr=True, - log_fail_as_error=False) - except Exception as e: - LOG.exception("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 2748e6889f5..e9d1ba69844 100644 --- a/neutron/cmd/sanity/checks.py +++ b/neutron/cmd/sanity/checks.py @@ -31,7 +31,6 @@ from neutron.agent.l3 import ha_router from neutron.agent.l3 import namespaces from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib -from neutron.agent.linux import ip_link_support from neutron.agent.linux import keepalived from neutron.agent.linux import utils as agent_utils from neutron.cmd import runtime_checks @@ -149,41 +148,6 @@ def icmpv6_header_match_supported(): actions="NORMAL") -def _vf_management_support(required_caps): - is_supported = True - try: - vf_section = ip_link_support.IpLinkSupport.get_vf_mgmt_section() - for cap in required_caps: - if not ip_link_support.IpLinkSupport.vf_mgmt_capability_supported( - vf_section, cap): - is_supported = False - LOG.debug("ip link command does not support " - "vf capability '%(cap)s'", {'cap': cap}) - except ip_link_support.UnsupportedIpLinkCommand: - LOG.exception("Unexpected exception while checking supported " - "ip link command") - return False - return is_supported - - -def vf_management_supported(): - required_caps = ( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_SPOOFCHK, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE) - return _vf_management_support(required_caps) - - -def vf_extended_management_supported(): - required_caps = ( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_STATE, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_SPOOFCHK, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, - ) - return _vf_management_support(required_caps) - - def netns_read_requires_helper(): nsname = "netnsreadtest-" + uuidutils.generate_uuid() ip_lib.create_network_namespace(nsname) diff --git a/neutron/cmd/sanity_check.py b/neutron/cmd/sanity_check.py index b19a435b7bf..f16e209296d 100644 --- a/neutron/cmd/sanity_check.py +++ b/neutron/cmd/sanity_check.py @@ -192,25 +192,6 @@ def check_icmpv6_header_match(): return result -def check_vf_management(): - result = checks.vf_management_supported() - if not result: - LOG.error('Check for VF management support failed. ' - 'Please ensure that the version of ip link ' - 'being used has VF support.') - return result - - -def check_vf_extended_management(): - result = checks.vf_extended_management_supported() - if not result: - LOG.error('Check for VF extended management support failed. ' - 'Please ensure that the version of ip link ' - 'being used has VF extended support: version ' - '"iproute2-ss140804", git tag "v3.16.0"') - return result - - def check_ovsdb_native(): result = checks.ovsdb_native_supported() if not result: @@ -325,10 +306,6 @@ OPTS = [ help=_('Check for ARP header match support')), BoolOptCallback('icmpv6_header_match', check_icmpv6_header_match, help=_('Check for ICMPv6 header match support')), - BoolOptCallback('vf_management', check_vf_management, - help=_('Check for VF management support')), - BoolOptCallback('vf_extended_management', check_vf_extended_management, - help=_('Check for VF extended management support')), BoolOptCallback('read_netns', check_read_netns, help=_('Check netns permission settings')), BoolOptCallback('dnsmasq_local_service_supported', diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py index 8b0bf3bc8ba..b3930e97a22 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/common/exceptions.py @@ -26,13 +26,5 @@ class InvalidDeviceError(SriovNicError): message = _("Invalid Device %(dev_name)s: %(reason)s") -class IpCommandError(SriovNicError): - message = _("ip command failed on device %(dev_name)s: %(reason)s") - - -class IpCommandOperationNotSupportedError(SriovNicError): - message = _("Operation not supported on device %(dev_name)s") - - class InvalidPciSlotError(SriovNicError): message = _("Invalid pci slot %(pci_slot)s") diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py index 7b242cc4a2a..98f1fbc0e08 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py @@ -21,7 +21,6 @@ from neutron_lib.utils import helpers from oslo_log import log as logging from neutron._i18n import _ -from neutron.agent.linux import ip_link_support from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib @@ -29,6 +28,14 @@ from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib LOG = logging.getLogger(__name__) +IP_LINK_CAPABILITY_STATE = 'state' +IP_LINK_CAPABILITY_VLAN = 'vlan' +IP_LINK_CAPABILITY_RATE = 'max_tx_rate' +IP_LINK_CAPABILITY_MIN_TX_RATE = 'min_tx_rate' +IP_LINK_CAPABILITY_SPOOFCHK = 'spoofchk' +IP_LINK_SUB_CAPABILITY_QOS = 'qos' + + class PciOsWrapper(object): """OS wrapper for checking virtual functions""" @@ -200,11 +207,11 @@ class EmbSwitch(object): auto=propagate_uplink_state) def set_device_rate(self, pci_slot, rate_type, rate_kbps): - """Set device rate: rate (max_tx_rate), min_tx_rate + """Set device rate: max_tx_rate, min_tx_rate @param pci_slot: Virtual Function address - @param rate_type: device rate name type. Could be 'rate' and - 'min_tx_rate'. + @param rate_type: device rate name type. Could be 'max_tx_rate' and + 'min_tx_rate' ('rate' is not supported anymore). @param rate_kbps: device rate in kbps """ vf_index = self._get_vf_index(pci_slot) @@ -349,7 +356,7 @@ class ESwitchManager(object): embedded_switch = self._get_emb_eswitch(device_mac, pci_slot) if embedded_switch: return embedded_switch.get_device_state(pci_slot) - return pci_lib.LinkState.DISABLE + return pci_lib.LinkState.disable.name def set_device_max_rate(self, device_mac, pci_slot, max_kbps): """Set device max rate @@ -362,9 +369,7 @@ class ESwitchManager(object): embedded_switch = self._get_emb_eswitch(device_mac, pci_slot) if embedded_switch: embedded_switch.set_device_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, - max_kbps) + pci_slot, IP_LINK_CAPABILITY_RATE, max_kbps) def set_device_min_tx_rate(self, device_mac, pci_slot, min_kbps): """Set device min_tx_rate @@ -377,9 +382,7 @@ class ESwitchManager(object): embedded_switch = self._get_emb_eswitch(device_mac, pci_slot) if embedded_switch: embedded_switch.set_device_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, - min_kbps) + pci_slot, IP_LINK_CAPABILITY_MIN_TX_RATE, min_kbps) def set_device_state(self, device_mac, pci_slot, admin_state_up, propagate_uplink_state): @@ -498,9 +501,7 @@ class ESwitchManager(object): Clear the "rate" configuration from VF by setting it to 0. @param pci_slot: VF PCI slot """ - self._clear_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE) + self._clear_rate(pci_slot, IP_LINK_CAPABILITY_RATE) def clear_min_tx_rate(self, pci_slot): """Clear the VF "min_tx_rate" parameter @@ -508,16 +509,14 @@ class ESwitchManager(object): Clear the "min_tx_rate" configuration from VF by setting it to 0. @param pci_slot: VF PCI slot """ - self._clear_rate( - pci_slot, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE) + self._clear_rate(pci_slot, IP_LINK_CAPABILITY_MIN_TX_RATE) def _clear_rate(self, pci_slot, rate_type): """Clear the VF rate parameter specified in rate_type Clear the rate configuration from VF by setting it to 0. @param pci_slot: VF PCI slot - @param rate_type: rate to clear ('rate', 'min_tx_rate') + @param rate_type: rate to clear ('max_tx_rate', 'min_tx_rate') """ # NOTE(Moshe Levi): we don't use the self._get_emb_eswitch here, # because when clearing the VF it may be not assigned. This happens diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py index 9e683d95dba..094fad4b6e2 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py @@ -15,9 +15,8 @@ from oslo_log import log as logging from neutron.agent.l2.extensions import qos_linux as qos -from neutron.plugins.ml2.drivers.mech_sriov.agent.common import ( - exceptions as exc) from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.services.qos.drivers.sriov import driver LOG = logging.getLogger(__name__) @@ -55,9 +54,9 @@ class QosSRIOVAgentDriver(qos.QosLinuxAgentDriver): try: self.eswitch_mgr.set_device_max_rate( device, pci_slot, max_kbps) - except exc.SriovNicError: - LOG.exception( - "Failed to set device %s max rate", device) + except (priv_ip_lib.InterfaceOperationNotSupported, + priv_ip_lib.InvalidArgument): + LOG.exception("Failed to set device %s max rate", device) else: LOG.info("No device with MAC %s defined on agent.", device) @@ -97,8 +96,8 @@ class QosSRIOVAgentDriver(qos.QosLinuxAgentDriver): try: self.eswitch_mgr.set_device_min_tx_rate( device, pci_slot, min_tx_kbps) - except exc.SriovNicError: - LOG.exception( - "Failed to set device %s min_tx_rate", device) + except (priv_ip_lib.InterfaceOperationNotSupported, + priv_ip_lib.InvalidArgument): + LOG.exception("Failed to set device %s min_tx_rate", device) else: LOG.info("No device with MAC %s defined on agent.", device) diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py index 42f875620cf..ebc1c51ecb0 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/pci_lib.py @@ -13,86 +13,42 @@ # See the License for the specific language governing permissions and # limitations under the License. -import re +import enum from oslo_log import log as logging from neutron.agent.linux import ip_lib -from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ - import exceptions as exc + LOG = logging.getLogger(__name__) -class LinkState(object): - ENABLE = "enable" - DISABLE = "disable" - AUTO = "auto" +class LinkState(enum.Enum): + auto = 0 + enable = 1 + disable = 2 class PciDeviceIPWrapper(ip_lib.IPWrapper): - """Wrapper class for ip link commands. - - wrapper for getting/setting pci device details using ip link... - """ - VF_PATTERN = r"^vf\s+(?P\d+)\s+" - MAC_PATTERN = r"MAC\s+(?P[a-fA-F0-9:]+)," - STATE_PATTERN = r"\s+link-state\s+(?P\w+)" - ANY_PATTERN = ".*," - - VF_LINE_FORMAT = VF_PATTERN + MAC_PATTERN + ANY_PATTERN + STATE_PATTERN - VF_DETAILS_REG_EX = re.compile(VF_LINE_FORMAT) - - IP_LINK_OP_NOT_SUPPORTED = 'RTNETLINK answers: Operation not supported' + """Wrapper class for ip link commands related to virtual functions.""" def __init__(self, dev_name): super(PciDeviceIPWrapper, self).__init__() self.dev_name = dev_name - def _set_feature(self, vf_index, feature, value): - """Sets vf feature - - Checks if the feature is not supported or there's some - general error during ip link invocation and raises - exception accordingly. - - :param vf_index: vf index - :param feature: name of a feature to be passed to ip link, - such as 'state' or 'spoofchk' - :param value: value of the feature setting - """ - try: - self._as_root([], "link", ("set", self.dev_name, "vf", - str(vf_index), feature, value)) - except Exception as e: - if self.IP_LINK_OP_NOT_SUPPORTED in str(e): - raise exc.IpCommandOperationNotSupportedError( - dev_name=self.dev_name) - else: - raise exc.IpCommandError(dev_name=self.dev_name, - reason=str(e)) - def get_assigned_macs(self, vf_list): """Get assigned mac addresses for vf list. @param vf_list: list of vf indexes @return: dict mapping of vf to mac """ - try: - out = self._as_root([], "link", ("show", self.dev_name)) - except Exception as e: - LOG.exception("Failed executing ip command") - raise exc.IpCommandError(dev_name=self.dev_name, - reason=e) + ip = self.device(self.dev_name) + vfs = ip.link.get_vfs() vf_to_mac_mapping = {} - vf_lines = self._get_vf_link_show(vf_list, out) - if vf_lines: - for vf_line in vf_lines: - vf_details = self._parse_vf_link_show(vf_line) - if vf_details: - vf_num = vf_details.get('vf') - vf_mac = vf_details.get("MAC") - vf_to_mac_mapping[vf_num] = vf_mac + for vf_num in vf_list: + if vfs.get(vf_num): + vf_to_mac_mapping[vf_num] = vfs[vf_num]['mac'] + return vf_to_mac_mapping def get_vf_state(self, vf_index): @@ -100,88 +56,48 @@ class PciDeviceIPWrapper(ip_lib.IPWrapper): @param vf_index: vf index """ - try: - out = self._as_root([], "link", ("show", self.dev_name)) - except Exception as e: - LOG.exception("Failed executing ip command") - raise exc.IpCommandError(dev_name=self.dev_name, - reason=e) - vf_lines = self._get_vf_link_show([vf_index], out) - if vf_lines: - vf_details = self._parse_vf_link_show(vf_lines[0]) - if vf_details: - state = vf_details.get("link-state", - LinkState.DISABLE) - if state in (LinkState.AUTO, LinkState.ENABLE): - return state - return LinkState.DISABLE + ip = self.device(self.dev_name) + vfs = ip.link.get_vfs() + vf = vfs.get(vf_index) + if vf: + return LinkState(int(vf['link_state'])).name + + return LinkState.disable.name def set_vf_state(self, vf_index, state, auto=False): """sets vf state. @param vf_index: vf index - @param state: required state {True/False} + @param state: required state {True: enable (1) + False: disable (2)} + @param auto: set link_state to auto (0) """ + ip = self.device(self.dev_name) if auto: - status_str = LinkState.AUTO + link_state = 0 else: - status_str = LinkState.ENABLE if state else \ - LinkState.DISABLE - self._set_feature(vf_index, "state", status_str) + link_state = 1 if state else 2 + vf_config = {'vf': vf_index, 'link_state': link_state} + ip.link.set_vf_feature(vf_config) def set_vf_spoofcheck(self, vf_index, enabled): """sets vf spoofcheck @param vf_index: vf index - @param enabled: True to enable spoof checking, - False to disable + @param enabled: True to enable (1) spoof checking, + False to disable (0) """ - setting = "on" if enabled else "off" - self._set_feature(vf_index, "spoofchk", setting) + ip = self.device(self.dev_name) + vf_config = {'vf': vf_index, 'spoofchk': int(enabled)} + ip.link.set_vf_feature(vf_config) def set_vf_rate(self, vf_index, rate_type, rate_value): """sets vf rate. @param vf_index: vf index - @param rate_type: vf rate type ('rate', 'min_tx_rate') + @param rate_type: vf rate type ('max_tx_rate', 'min_tx_rate') @param rate_value: vf rate in Mbps """ - self._set_feature(vf_index, rate_type, str(rate_value)) - - def _get_vf_link_show(self, vf_list, link_show_out): - """Get link show output for VFs - - get vf link show command output filtered by given vf list - @param vf_list: list of vf indexes - @param link_show_out: link show command output - @return: list of output rows regarding given vf_list - """ - vf_lines = [] - for line in link_show_out.split("\n"): - line = line.strip() - if line.startswith("vf"): - details = line.split() - index = int(details[1]) - if index in vf_list: - vf_lines.append(line) - if not vf_lines: - LOG.warning("Cannot find vfs %(vfs)s in device %(dev_name)s", - {'vfs': vf_list, 'dev_name': self.dev_name}) - return vf_lines - - def _parse_vf_link_show(self, vf_line): - """Parses vf link show command output line. - - @param vf_line: link show vf line - """ - vf_details = {} - pattern_match = self.VF_DETAILS_REG_EX.match(vf_line) - if pattern_match: - vf_details["vf"] = int(pattern_match.group("vf_index")) - vf_details["MAC"] = pattern_match.group("mac") - vf_details["link-state"] = pattern_match.group("state") - else: - LOG.warning("failed to parse vf link show line %(line)s: " - "for %(device)s", - {'line': vf_line, 'device': self.dev_name}) - return vf_details + ip = self.device(self.dev_name) + vf_config = {'vf': vf_index, 'rate': {rate_type: int(rate_value)}} + ip.link.set_vf_feature(vf_config) diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py index 16681b66f06..c33f55f9c27 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py @@ -31,6 +31,7 @@ from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall from osprofiler import profiler +import pyroute2 import six from neutron._i18n import _ @@ -47,6 +48,7 @@ from neutron.plugins.ml2.drivers.mech_sriov.agent.common import config from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib LOG = logging.getLogger(__name__) @@ -291,10 +293,9 @@ class SriovNicSwitchAgent(object): self.eswitch_mgr.set_device_state(device, pci_slot, admin_state_up, propagate_uplink_state) - except exc.IpCommandOperationNotSupportedError: - LOG.warning("Device %s does not support state change", - device) - except exc.SriovNicError: + except priv_ip_lib.InterfaceOperationNotSupported: + LOG.warning("Device %s does not support state change", device) + except pyroute2.NetlinkError: LOG.warning("Failed to set device %s state", device) return False else: diff --git a/neutron/privileged/agent/linux/ip_lib.py b/neutron/privileged/agent/linux/ip_lib.py index 65cce5d5f2a..5cc5462b56f 100644 --- a/neutron/privileged/agent/linux/ip_lib.py +++ b/neutron/privileged/agent/linux/ip_lib.py @@ -64,6 +64,8 @@ def _get_scope_name(scope): return rtnl.rt_scope.get(scope, scope) +# TODO(ralonsoh): move those exceptions out of priv_ip_lib to avoid other +# modules to import this one. class NetworkNamespaceNotFound(RuntimeError): message = _("Network namespace %(netns_name)s could not be found.") @@ -102,6 +104,21 @@ class InterfaceOperationNotSupported(RuntimeError): super(InterfaceOperationNotSupported, self).__init__(message) +class InvalidArgument(RuntimeError): + message = _("Invalid parameter/value used on interface %(device)s, " + "namespace %(namespace)s.") + + def __init__(self, message=None, device=None, namespace=None): + # NOTE(slaweq): 'message' can be passed as an optional argument + # because of how privsep daemon works. If exception is raised in + # function called by privsep daemon, it will then try to reraise it + # and will call it always with passing only message from originally + # raised exception. + message = message or self.message % {'device': device, + 'namespace': namespace} + super(InvalidArgument, self).__init__(message) + + class IpAddressAlreadyExists(RuntimeError): message = _("IP address %(ip)s already configured on %(device)s.") @@ -234,6 +251,8 @@ def _translate_ip_device_exception(e, device=None, namespace=None): if e.code == errno.EOPNOTSUPP: raise InterfaceOperationNotSupported(device=device, namespace=namespace) + if e.code == errno.EINVAL: + raise InvalidArgument(device=device, namespace=namespace) def get_link_id(device, namespace, raise_exception=True): @@ -391,6 +410,11 @@ def set_link_attribute(device, namespace, **attributes): return _run_iproute_link("set", device, namespace, **attributes) +@privileged.default.entrypoint +def set_link_vf_feature(device, namespace, vf_config): + return _run_iproute_link("set", device, namespace=namespace, vf=vf_config) + + @privileged.default.entrypoint def get_link_attributes(device, namespace): link = _run_iproute_link("get", device, namespace)[0] @@ -407,6 +431,24 @@ def get_link_attributes(device, namespace): } +@privileged.default.entrypoint +def get_link_vfs(device, namespace): + link = _run_iproute_link('get', device, namespace=namespace, ext_mask=1)[0] + num_vfs = link.get_attr('IFLA_NUM_VF') + vfs = {} + if not num_vfs: + return vfs + + vfinfo_list = link.get_attr('IFLA_VFINFO_LIST') + for vinfo in vfinfo_list.get_attrs('IFLA_VF_INFO'): + mac = vinfo.get_attr('IFLA_VF_MAC') + link_state = vinfo.get_attr('IFLA_VF_LINK_STATE') + vfs[mac['vf']] = {'mac': mac['mac'], + 'link_state': link_state['link_state']} + + return vfs + + @privileged.default.entrypoint def add_neigh_entry(ip_version, ip_address, mac_address, device, namespace, **kwargs): diff --git a/neutron/tests/functional/sanity/test_sanity.py b/neutron/tests/functional/sanity/test_sanity.py index 77e2109bfa6..df13903eeb4 100644 --- a/neutron/tests/functional/sanity/test_sanity.py +++ b/neutron/tests/functional/sanity/test_sanity.py @@ -77,12 +77,6 @@ class SanityTestCaseRoot(base.BaseSudoTestCase): def test_icmpv6_header_match_runs(self): checks.icmpv6_header_match_supported() - def test_vf_management_runs(self): - checks.vf_management_supported() - - def test_vf_extended_management_runs(self): - checks.vf_extended_management_supported() - def test_namespace_root_read_detection_runs(self): checks.netns_read_requires_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 deleted file mode 100644 index 969e14cdbb1..00000000000 --- a/neutron/tests/unit/agent/linux/test_ip_link_support.py +++ /dev/null @@ -1,181 +0,0 @@ -# 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. - -from unittest 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_SPOOFCHK = IP_LINK_HELP_NO_STATE - - 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'], - 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_spoofchk(self): - self._test_capability( - ip_link.IpLinkConstants.IP_LINK_CAPABILITY_SPOOFCHK, - expected=False, - stderr=self.IP_LINK_HELP_NO_SPOOFCHK) - - 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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index 6732055b79c..4addd8fce12 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -454,7 +454,7 @@ class TestLinuxBridgeManager(base.BaseTestCase): return_value=vxlan_dev) as add_vxlan_fn,\ mock.patch.object(vxlan_dev.link, 'set_mtu', side_effect=ip_lib.InvalidArgument( - parameter="MTU", value=mtu)),\ + device='device_exists', namespace='ns')),\ mock.patch.object(ip_lib, 'get_device_mtu', return_value=physical_mtu),\ mock.patch.object(vxlan_dev.link, 'delete') as delete_dev: diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py index 05d6881e5b4..6286f3120c0 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/test_qos_driver.py @@ -22,9 +22,9 @@ from oslo_utils import uuidutils from neutron.objects.qos import policy from neutron.objects.qos import rule -from neutron.plugins.ml2.drivers.mech_sriov.agent.common import exceptions from neutron.plugins.ml2.drivers.mech_sriov.agent.extension_drivers import ( qos_driver) +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.tests import base @@ -116,8 +116,16 @@ class QosSRIOVAgentDriverTestCase(base.BaseTestCase): self.clear_max_rate_mock.assert_called_once_with(self.PCI_SLOT) def test__set_vf_max_rate_captures_sriov_failure(self): - self.max_rate_mock.side_effect = exceptions.SriovNicError() - self.qos_driver._set_vf_max_rate(self.ASSIGNED_MAC, self.PCI_SLOT) + msg = 'Failed to set device %s max rate' + with mock.patch.object(qos_driver, 'LOG') as mock_log: + for exc in (priv_ip_lib.InterfaceOperationNotSupported(), + priv_ip_lib.InvalidArgument()): + self.max_rate_mock.side_effect = exc + self.qos_driver._set_vf_max_rate(self.ASSIGNED_MAC, + self.PCI_SLOT) + mock_log.exception.assert_called_once_with(msg, + self.ASSIGNED_MAC) + mock_log.exception.reset_mock() def test__set_vf_max_rate_unknown_device(self): with mock.patch.object(self.qos_driver.eswitch_mgr, 'device_exists', diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py index a6458de36ee..4684ae36c20 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py @@ -16,7 +16,6 @@ import os from unittest import mock -from neutron.agent.linux import ip_link_support from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm @@ -70,8 +69,8 @@ class TestESwitchManagerApi(base.BaseTestCase): PCI_SLOT = '0000:06:00.1' WRONG_MAC = '00:00:00:00:00:67' WRONG_PCI = "0000:06:00.6" - MAX_RATE = ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE - MIN_RATE = ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE + MAX_RATE = esm.IP_LINK_CAPABILITY_RATE + MIN_RATE = esm.IP_LINK_CAPABILITY_MIN_TX_RATE def setUp(self): super(TestESwitchManagerApi, self).setUp() @@ -524,55 +523,48 @@ class TestEmbSwitch(base.BaseTestCase): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2000) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2000) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2) + 0, esm.IP_LINK_CAPABILITY_RATE, 2) def test_set_device_max_rate_ok2(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 99) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 99) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 1) + 0, esm.IP_LINK_CAPABILITY_RATE, 1) def test_set_device_max_rate_rounded_ok(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2001) - pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2001) + pci_lib_mock.assert_called_with(0, esm.IP_LINK_CAPABILITY_RATE, 2) def test_set_device_max_rate_rounded_ok2(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2499) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2499) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2) + 0, esm.IP_LINK_CAPABILITY_RATE, 2) def test_set_device_max_rate_rounded_ok3(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 2500) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2500) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 3) + 0, esm.IP_LINK_CAPABILITY_RATE, 3) def test_set_device_max_rate_disable(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." "PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock: self.emb_switch.set_device_rate( - self.PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 0) + self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 0) pci_lib_mock.assert_called_with( - 0, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 0) + 0, esm.IP_LINK_CAPABILITY_RATE, 0) def test_set_device_max_rate_fail(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." @@ -581,7 +573,7 @@ class TestEmbSwitch(base.BaseTestCase): exc.InvalidPciSlotError, self.emb_switch.set_device_rate, self.WRONG_PCI_SLOT, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, 1000) + esm.IP_LINK_CAPABILITY_RATE, 1000) def test_get_pci_device(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py index 1cfd7511b7c..05191364c54 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_pci_lib.py @@ -15,28 +15,18 @@ from unittest import mock -from neutron.agent.linux import ip_link_support -from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ - import exceptions as exc +from neutron.agent.linux import ip_lib from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib from neutron.tests import base class TestPciLib(base.BaseTestCase): DEV_NAME = "p7p1" + VF_INDEX = 1 - VF_INDEX_DISABLE = 0 - PF_LINK_SHOW = ('122: p7p1: mtu 1500 qdisc noop' - ' state DOWN mode DEFAULT group default qlen 1000') - PF_MAC = ' link/ether f4:52:14:2a:3e:c0 brd ff:ff:ff:ff:ff:ff' - VF_0_LINK_SHOW = (' vf 0 MAC fa:16:3e:b4:81:ac, vlan 4095, spoof' - ' checking off, link-state disable') - VF_1_LINK_SHOW = (' vf 1 MAC 00:00:00:00:00:11, vlan 4095, spoof' - ' checking off, link-state enable') - VF_2_LINK_SHOW = (' vf 2 MAC fa:16:3e:68:4e:79, vlan 4095, spoof' - ' checking off, link-state enable') - VF_LINK_SHOW = '\n'.join((PF_LINK_SHOW, PF_MAC, VF_0_LINK_SHOW, - VF_1_LINK_SHOW, VF_2_LINK_SHOW)) + VFS_LIST = {0: {'mac': 'fa:16:3e:b4:81:ac', 'link_state': 2}, + 1: {'mac': '00:00:00:00:00:11', 'link_state': 1}, + 2: {'mac': 'fa:16:3e:68:4e:79', 'link_state': 0}} MAC_MAPPING = { 0: "fa:16:3e:b4:81:ac", @@ -44,121 +34,69 @@ class TestPciLib(base.BaseTestCase): 2: "fa:16:3e:68:4e:79", } + STATE_MAPPING = { # VF index: state (string), according to VFS_LIST + 0: pci_lib.LinkState.disable.name, + 1: pci_lib.LinkState.enable.name, + 2: pci_lib.LinkState.auto.name, + } + def setUp(self): super(TestPciLib, self).setUp() self.pci_wrapper = pci_lib.PciDeviceIPWrapper(self.DEV_NAME) + self.mock_ip_device = mock.Mock() + self.mock_ip_device.link.get_vfs.return_value = self.VFS_LIST + mock.patch.object(ip_lib, 'IPDevice', + return_value=self.mock_ip_device).start() def test_get_assigned_macs(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.return_value = self.VF_LINK_SHOW - result = self.pci_wrapper.get_assigned_macs([self.VF_INDEX]) - self.assertEqual( - {self.VF_INDEX: self.MAC_MAPPING[self.VF_INDEX]}, result) + for idx in range(len(self.VFS_LIST)): + result = self.pci_wrapper.get_assigned_macs([idx]) + self.assertEqual({idx: self.MAC_MAPPING[idx]}, result) - def test_get_assigned_macs_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.get_assigned_macs, - [self.VF_INDEX]) + def test_get_assigned_macs_not_present(self): + result = self.pci_wrapper.get_assigned_macs([1000]) + self.assertEqual({}, result) - def test_get_vf_state_enable(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.return_value = self.VF_LINK_SHOW - result = self.pci_wrapper.get_vf_state(self.VF_INDEX) - self.assertEqual('enable', result) + def test_get_vf_state(self): + for idx in range(len(self.VFS_LIST)): + result = self.pci_wrapper.get_vf_state(idx) + self.assertEqual(self.STATE_MAPPING[idx], result) - def test_get_vf_state_disable(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.return_value = self.VF_LINK_SHOW - result = self.pci_wrapper.get_vf_state(self.VF_INDEX_DISABLE) - self.assertEqual('disable', result) - - def test_get_vf_state_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.get_vf_state, - self.VF_INDEX) + def test_get_vf_state_not_present(self): + result = self.pci_wrapper.get_vf_state(1000) + self.assertEqual(pci_lib.LinkState.disable.name, result) def test_set_vf_state(self): - with mock.patch.object(self.pci_wrapper, "_as_root"): - result = self.pci_wrapper.set_vf_state(self.VF_INDEX, - True) - self.assertIsNone(result) + self.pci_wrapper.set_vf_state(self.VF_INDEX, True) + vf = {'vf': self.VF_INDEX, 'link_state': 1} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) - def test_set_vf_state_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.set_vf_state, - self.VF_INDEX, - True) + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_state(self.VF_INDEX, False) + vf = {'vf': self.VF_INDEX, 'link_state': 2} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) + + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_state(self.VF_INDEX, False, auto=True) + vf = {'vf': self.VF_INDEX, 'link_state': 0} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) def test_set_vf_spoofcheck(self): - with mock.patch.object(self.pci_wrapper, "_as_root"): - result = self.pci_wrapper.set_vf_spoofcheck(self.VF_INDEX, - True) - self.assertIsNone(result) + self.pci_wrapper.set_vf_spoofcheck(self.VF_INDEX, True) + vf = {'vf': self.VF_INDEX, 'spoofchk': 1} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) - def test_set_vf_spoofcheck_fail(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception() - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.set_vf_spoofcheck, - self.VF_INDEX, - True) + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_spoofcheck(self.VF_INDEX, False) + vf = {'vf': self.VF_INDEX, 'spoofchk': 0} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) - def _set_vf_rate(self, rate, passed=True): - if passed: - with mock.patch.object(self.pci_wrapper, "_as_root") \ - as mock_as_root: - result = self.pci_wrapper.set_vf_rate( - self.VF_INDEX, - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE, - 1000) - self.assertIsNone(result) - mock_as_root.assert_called_once_with( - [], "link", ("set", self.DEV_NAME, "vf", - str(self.VF_INDEX), "rate", '1000')) - else: - with mock.patch.object(self.pci_wrapper, "_as_root", - side_effect=Exception()): - self.assertRaises(exc.IpCommandError, - self.pci_wrapper.set_vf_rate, - self.VF_INDEX, - rate, - 1000) + def test_set_vf_rate(self): + self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'max_tx_rate', 20) + vf = {'vf': self.VF_INDEX, 'rate': {'max_tx_rate': 20}} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) - def test_set_vf_rate_max_rate(self): - self._set_vf_rate( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_RATE) - - def test_set_vf_rate_max_rate_fail(self): - self._set_vf_rate('rate', passed=False) - - def test_set_vf_rate_min_tx_rate(self): - self._set_vf_rate( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE) - - def test_set_vf_rate_min_tx_rate_fail(self): - self._set_vf_rate( - ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, - passed=False) - - def test_set_vf_state_not_supported(self): - with mock.patch.object(self.pci_wrapper, - "_as_root") as mock_as_root: - mock_as_root.side_effect = Exception( - pci_lib.PciDeviceIPWrapper.IP_LINK_OP_NOT_SUPPORTED) - self.assertRaises(exc.IpCommandOperationNotSupportedError, - self.pci_wrapper.set_vf_state, - self.VF_INDEX, - state=True) + self.mock_ip_device.link.set_vf_feature.reset_mock() + self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'min_tx_rate', 10) + vf = {'vf': self.VF_INDEX, 'rate': {'min_tx_rate': 10}} + self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf) diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py index a7b1a3743f8..3934bba3c75 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py @@ -19,12 +19,13 @@ from neutron_lib.api.definitions import portbindings from neutron_lib import constants from oslo_config import cfg from oslo_utils import uuidutils +import pyroute2 from neutron.agent.l2 import l2_agent_extensions_manager as l2_ext_manager from neutron.agent import rpc as agent_rpc from neutron.plugins.ml2.drivers.mech_sriov.agent.common import config # noqa -from neutron.plugins.ml2.drivers.mech_sriov.agent.common import exceptions from neutron.plugins.ml2.drivers.mech_sriov.agent import sriov_nic_agent +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.tests import base DEVICE_MAC = '11:22:33:44:55:66' @@ -423,8 +424,7 @@ class TestSriovAgent(base.BaseTestCase): agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.set_device_state.side_effect = ( - exceptions.IpCommandOperationNotSupportedError( - dev_name='aa:bb:cc:dd:ee:ff')) + priv_ip_lib.InterfaceOperationNotSupported()) self.assertTrue(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', admin_state_up=True)) @@ -435,7 +435,7 @@ class TestSriovAgent(base.BaseTestCase): agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.set_device_state.side_effect = ( - exceptions.SriovNicError()) + pyroute2.NetlinkError(22)) self.assertFalse(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', admin_state_up=True)) diff --git a/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py b/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py index 4d6d4818409..51c2bd67508 100644 --- a/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py +++ b/neutron/tests/unit/privileged/agent/linux/test_ip_lib.py @@ -226,6 +226,39 @@ class IpLibTestCase(base.BaseTestCase): except OSError as e: self.assertEqual(errno.EINVAL, e.errno) + def _clean(self, client_mode): + priv_lib.privileged.default.client_mode = client_mode + + def test_get_link_vfs(self): + # NOTE(ralonsoh): there should be a functional test checking this + # method, but this is not possible due to the lack of SR-IOV capable + # NICs in the CI servers. + vf_info = [] + for idx in range(3): + vf_info.append(pyroute2.netlink.nlmsg_base()) + mac_info = {'mac': 'mac_%s' % idx, 'vf': idx} + link_state = {'link_state': idx} # see SR-IOV pci_lib.LinkState + vf_info[idx].setvalue( + {'attrs': [('IFLA_VF_MAC', mac_info), + ('IFLA_VF_LINK_STATE', link_state)]}) + vfinfo_list = pyroute2.netlink.nlmsg_base() + vfinfo_list.setvalue({'attrs': [('IFLA_VF_INFO', vf_info[0]), + ('IFLA_VF_INFO', vf_info[1]), + ('IFLA_VF_INFO', vf_info[2])]}) + value = pyroute2.netlink.nlmsg_base() + value.setvalue({'attrs': [('IFLA_NUM_VF', 3), + ('IFLA_VFINFO_LIST', vfinfo_list)]}) + client_mode = priv_lib.privileged.default.client_mode + priv_lib.privileged.default.client_mode = False + self.addCleanup(self._clean, client_mode) + with mock.patch.object(priv_lib, '_run_iproute_link') as mock_iplink: + mock_iplink.return_value = [value] + result = priv_lib.get_link_vfs('device', 'namespace') + self.assertEqual({0: {'mac': 'mac_0', 'link_state': 0}, + 1: {'mac': 'mac_1', 'link_state': 1}, + 2: {'mac': 'mac_2', 'link_state': 2}}, + result) + class MakeSerializableTestCase(base.BaseTestCase):