# Defines configuration options for SRIOV NIC Switch MechanismDriver
+# and Agent
[ml2_sriov]
# (ListOpt) Comma-separated list of
# supported Vendor PCI Devices, in format vendor_id:product_id
#
-# supported_vendor_pci_devs = 15b3:1004
-# Example: supported_vendor_pci_devs = 15b3:1004, 8086:10c9
+# supported_vendor_pci_devs = 15b3:1004, 8086:10c9
+# Example: supported_vendor_pci_devs = 15b3:1004
#
-# (BoolOpt) Requires SRIOV neutron agent for port binding
+# (BoolOpt) Requires running SRIOV neutron agent for port binding
# agent_required = True
+[sriov_nic]
+# (ListOpt) Comma-separated list of <physical_network>:<network_device>
+# tuples mapping physical network names to the agent's node-specific
+# physical network device interfaces of SR-IOV physical function to be used
+# for VLAN networks. All physical networks listed in network_vlan_ranges on
+# the server should have mappings to appropriate interfaces on each agent.
+#
+# physical_device_mappings =
+# Example: physical_device_mappings = physnet1:eth1
+#
+# (ListOpt) Comma-separated list of <network_device>:<vfs__to_exclude>
+# tuples, mapping network_device to the agent's node-specific list of virtual
+# functions that should not be used for virtual networking.
+# vfs_to_exclude is a semicolon-separated list of virtual
+# functions to exclude from network_device. The network_device in the
+# mapping should appear in the physical_device_mappings list.
+# exclude_devices =
+# Example: exclude_list = eth1:0000:07:00.2; 0000:07:00.3
\ No newline at end of file
--- /dev/null
+# 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 oslo.config import cfg
+
+from neutron.agent.common import config
+
+
+def parse_exclude_devices(exclude_list):
+ """Parse Exclude devices list
+
+ parses excluded device list in the form:
+ dev_name:pci_dev_1;pci_dev_2
+ @param exclude list: list of string pairs in "key:value" format
+ the key part represents the network device name
+ the value part is a list of PCI slots separated by ";"
+ """
+ exclude_mapping = {}
+ for dev_mapping in exclude_list:
+ try:
+ dev_name, exclude_devices = dev_mapping.split(":", 1)
+ except ValueError:
+ raise ValueError(_("Invalid mapping: '%s'") % dev_mapping)
+ dev_name = dev_name.strip()
+ if not dev_name:
+ raise ValueError(_("Missing key in mapping: '%s'") % dev_mapping)
+ if dev_name in exclude_mapping:
+ raise ValueError(_("Device %(dev_name)s in mapping: %(mapping)s "
+ "not unique") % {'dev_name': dev_name,
+ 'mapping': dev_mapping})
+ exclude_devices_list = exclude_devices.split(";")
+ exclude_devices_set = set()
+ for dev in exclude_devices_list:
+ dev = dev.strip()
+ if dev:
+ exclude_devices_set.add(dev)
+ exclude_mapping[dev_name] = exclude_devices_set
+ return exclude_mapping
+
+DEFAULT_DEVICE_MAPPINGS = []
+DEFAULT_EXCLUDE_DEVICES = []
+
+agent_opts = [
+ cfg.IntOpt('polling_interval', default=2,
+ help=_("The number of seconds the agent will wait between "
+ "polling for local device changes.")),
+]
+
+sriov_nic_opts = [
+ cfg.ListOpt('physical_device_mappings',
+ default=DEFAULT_DEVICE_MAPPINGS,
+ help=_("List of <physical_network>:<network_device> mapping "
+ "physical network names to the agent's node-specific "
+ "physical network device of SR-IOV physical "
+ "function to be used for VLAN networks. "
+ "All physical networks listed in network_vlan_ranges "
+ "on the server should have mappings to appropriate "
+ "interfaces on each agent")),
+ cfg.ListOpt('exclude_devices',
+ default=DEFAULT_EXCLUDE_DEVICES,
+ help=_("List of <network_device>:<excluded_devices> "
+ "mapping network_device to the agent's node-specific "
+ "list of virtual functions that should not be used "
+ "for virtual networking. excluded_devices is a "
+ "semicolon separated list of virtual functions "
+ "(BDF format).to exclude from network_device. "
+ "The network_device in the mapping should appear in "
+ "the physical_device_mappings list.")),
+]
+
+
+cfg.CONF.register_opts(agent_opts, 'AGENT')
+cfg.CONF.register_opts(sriov_nic_opts, 'SRIOV_NIC')
+config.register_agent_state_opts_helper(cfg.CONF)
+config.register_root_helper(cfg.CONF)
--- /dev/null
+# 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 neutron.common import exceptions as n_exc
+
+
+class SriovNicError(n_exc.NeutronException):
+ pass
+
+
+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 InvalidPciSlotError(SriovNicError):
+ message = _("Invalid pci slot %(pci_slot)s")
--- /dev/null
+# 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.
+
+# @author: Samer Deeb, Mellanox Technologies, Ltd
+
+import os
+import re
+
+from neutron.openstack.common import log as logging
+from neutron.plugins.sriovnicagent.common import exceptions as exc
+from neutron.plugins.sriovnicagent import pci_lib
+
+LOG = logging.getLogger(__name__)
+
+
+class PciOsWrapper(object):
+ """OS wrapper for checking virtual functions"""
+
+ DEVICE_PATH = "/sys/class/net/%s/device"
+ PCI_PATH = "/sys/class/net/%s/device/virtfn%s/net"
+ VIRTFN_FORMAT = "^virtfn(?P<vf_index>\d+)"
+ VIRTFN_REG_EX = re.compile(VIRTFN_FORMAT)
+
+ @classmethod
+ def scan_vf_devices(cls, dev_name):
+ """Scan os directories to get VF devices
+
+ @param dev_name: pf network device name
+ @return: list of virtual functions
+ """
+ vf_list = []
+ dev_path = cls.DEVICE_PATH % dev_name
+ if not os.path.isdir(dev_path):
+ LOG.error(_("Failed to get devices for %s"), dev_name)
+ raise exc.InvalidDeviceError(dev_name=dev_name,
+ reason=_("Device not found"))
+ file_list = os.listdir(dev_path)
+ for file_name in file_list:
+ pattern_match = cls.VIRTFN_REG_EX.match(file_name)
+ if pattern_match:
+ vf_index = int(pattern_match.group("vf_index"))
+ file_path = os.path.join(dev_path, file_name)
+ if os.path.islink(file_path):
+ file_link = os.readlink(file_path)
+ pci_slot = os.path.basename(file_link)
+ vf_list.append((pci_slot, vf_index))
+ if not vf_list:
+ raise exc.InvalidDeviceError(
+ dev_name=dev_name,
+ reason=_("Device has no virtual functions"))
+ return vf_list
+
+ @classmethod
+ def is_assigned_vf(cls, dev_name, vf_index):
+ """Check if VF is assigned.
+
+ Checks if a given vf index of a given device name is assigned
+ by checking the relevant path in the system
+ @param dev_name: pf network device name
+ @param vf_index: vf index
+ """
+ path = cls.PCI_PATH % (dev_name, vf_index)
+ return not (os.path.isdir(path))
+
+
+class EmbSwitch(object):
+ """Class to manage logical embedded switch entity.
+
+ Embedded Switch object is logical entity representing all VFs
+ connected to same physical network
+ Each physical network is mapped to PF network device interface,
+ meaning all its VF, excluding the devices in exclude_device list.
+ @ivar pci_slot_map: dictionary for mapping each pci slot to vf index
+ @ivar pci_dev_wrapper: pci device wrapper
+ """
+
+ def __init__(self, phys_net, dev_name, exclude_devices, root_helper):
+ """Constructor
+
+ @param phys_net: physical network
+ @param dev_name: network device name
+ @param exclude_devices: list of pci slots to exclude
+ @param root_helper: root permissions helper
+ """
+ self.phys_net = phys_net
+ self.dev_name = dev_name
+ self.pci_slot_map = {}
+ self.pci_dev_wrapper = pci_lib.PciDeviceIPWrapper(dev_name,
+ root_helper)
+
+ self._load_devices(exclude_devices)
+
+ def _load_devices(self, exclude_devices):
+ """Load devices from driver and filter if needed.
+
+ @param exclude_devices: excluded devices mapping device_name: pci slots
+ """
+ scanned_pci_list = PciOsWrapper.scan_vf_devices(self.dev_name)
+ for pci_slot, vf_index in scanned_pci_list:
+ if pci_slot not in exclude_devices:
+ self.pci_slot_map[pci_slot] = vf_index
+
+ def get_pci_slot_list(self):
+ """Get list of VF addresses."""
+ return self.pci_slot_map.keys()
+
+ def get_assigned_devices(self):
+ """Get assigned Virtual Functions.
+
+ @return: list of VF mac addresses
+ """
+ vf_list = []
+ assigned_macs = []
+ for vf_index in self.pci_slot_map.itervalues():
+ if not PciOsWrapper.is_assigned_vf(self.dev_name, vf_index):
+ continue
+ vf_list.append(vf_index)
+ if vf_list:
+ assigned_macs = self.pci_dev_wrapper.get_assigned_macs(vf_list)
+ return assigned_macs
+
+ def get_device_state(self, pci_slot):
+ """Get device state.
+
+ @param pci_slot: Virtual Function address
+ """
+ vf_index = self.pci_slot_map.get(pci_slot)
+ if vf_index is None:
+ LOG.warning(_("Cannot find vf index for pci slot %s"),
+ pci_slot)
+ raise exc.InvalidPciSlotError(pci_slot=pci_slot)
+ return self.pci_dev_wrapper.get_vf_state(vf_index)
+
+ def set_device_state(self, pci_slot, state):
+ """Set device state.
+
+ @param pci_slot: Virtual Function address
+ @param state: link state
+ """
+ vf_index = self.pci_slot_map.get(pci_slot)
+ if vf_index is None:
+ LOG.warning(_("Cannot find vf index for pci slot %s"),
+ pci_slot)
+ raise exc.InvalidPciSlotError(pci_slot=pci_slot)
+ return self.pci_dev_wrapper.set_vf_state(vf_index, state)
+
+ def get_pci_device(self, pci_slot):
+ """Get mac address for given Virtual Function address
+
+ @param pci_slot: pci slot
+ @return: MAC address of virtual function
+ """
+ vf_index = self.pci_slot_map.get(pci_slot)
+ mac = None
+ if vf_index is not None:
+ if PciOsWrapper.is_assigned_vf(self.dev_name, vf_index):
+ macs = self.pci_dev_wrapper.get_assigned_macs([vf_index])
+ if macs:
+ mac = macs[0]
+ return mac
+
+
+class ESwitchManager(object):
+ """Manages logical Embedded Switch entities for physical network."""
+
+ def __init__(self, device_mappings, exclude_devices, root_helper):
+ """Constructor.
+
+ Create Embedded Switch logical entities for all given device mappings,
+ using exclude devices.
+ """
+ self.emb_switches_map = {}
+ self.pci_slot_map = {}
+ self.root_helper = root_helper
+
+ self._discover_devices(device_mappings, exclude_devices)
+
+ def device_exists(self, device_mac, pci_slot):
+ """Verify if device exists.
+
+ Check if a device mac exists and matches the given VF pci slot
+ @param device_mac: device mac
+ @param pci_slot: VF address
+ """
+ embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
+ if embedded_switch:
+ return True
+ return False
+
+ def get_assigned_devices(self, phys_net=None):
+ """Get all assigned devices.
+
+ Get all assigned devices belongs to given embedded switch
+ @param phys_net: physical network, if none get all assigned devices
+ @return: set of assigned VFs mac addresses
+ """
+ if phys_net:
+ embedded_switch = self.emb_switches_map.get(phys_net, None)
+ if not embedded_switch:
+ return set()
+ eswitch_objects = [embedded_switch]
+ else:
+ eswitch_objects = self.emb_switches_map.values()
+ assigned_devices = set()
+ for embedded_switch in eswitch_objects:
+ for device_mac in embedded_switch.get_assigned_devices():
+ assigned_devices.add(device_mac)
+ return assigned_devices
+
+ def get_device_state(self, device_mac, pci_slot):
+ """Get device state.
+
+ Get the device state (up/True or down/False)
+ @param device_mac: device mac
+ @param pci_slot: VF pci slot
+ @return: device state (True/False) None if failed
+ """
+ embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
+ if embedded_switch:
+ return embedded_switch.get_device_state(pci_slot)
+ return False
+
+ def set_device_state(self, device_mac, pci_slot, admin_state_up):
+ """Set device state
+
+ Sets the device state (up or down)
+ @param device_mac: device mac
+ @param pci_slot: pci slot
+ @param admin_state_up: device admin state True/False
+ """
+ embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
+ if embedded_switch:
+ embedded_switch.set_device_state(pci_slot,
+ admin_state_up)
+
+ def _discover_devices(self, device_mappings, exclude_devices):
+ """Discover which Virtual functions to manage.
+
+ Discover devices, and create embedded switch object for network device
+ @param device_mappings: device mapping physical_network:device_name
+ @param exclude_devices: excluded devices mapping device_name: pci slots
+ """
+ if exclude_devices is None:
+ exclude_devices = {}
+ for phys_net, dev_name in device_mappings.iteritems():
+ self._create_emb_switch(phys_net, dev_name,
+ exclude_devices.get(dev_name, set()))
+
+ def _create_emb_switch(self, phys_net, dev_name, exclude_devices):
+ embedded_switch = EmbSwitch(phys_net, dev_name, exclude_devices,
+ self.root_helper)
+ self.emb_switches_map[phys_net] = embedded_switch
+ for pci_slot in embedded_switch.get_pci_slot_list():
+ self.pci_slot_map[pci_slot] = embedded_switch
+
+ def _get_emb_eswitch(self, device_mac, pci_slot):
+ """Get embedded switch.
+
+ Get embedded switch by pci slot and validate pci has device mac
+ @param device_mac: device mac
+ @param pci_slot: pci slot
+ """
+ embedded_switch = self.pci_slot_map.get(pci_slot)
+ if embedded_switch:
+ used_device_mac = embedded_switch.get_pci_device(pci_slot)
+ if used_device_mac != device_mac:
+ LOG.warning(_("device pci mismatch: %(device_mac)s "
+ "- %(pci_slot)s"), {"device_mac": device_mac,
+ "pci_slot": pci_slot})
+ embedded_switch = None
+ return embedded_switch
--- /dev/null
+# 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.
+
+# @author: Samer Deeb, Mellanox Technologies, Ltd
+
+import re
+
+from neutron.agent.linux import ip_lib
+from neutron.openstack.common import log as logging
+from neutron.plugins.sriovnicagent.common import exceptions as exc
+
+LOG = logging.getLogger(__name__)
+
+
+class PciDeviceIPWrapper(ip_lib.IPWrapper):
+ """Wrapper class for ip link commands.
+
+ wrapper for getting/setting pci device details using ip link...
+ """
+ VF_PATTERN = "^vf(\s+)(?P<vf_index>\d+)(\s+)"
+ MAC_PATTERN = "MAC(\s+)(?P<mac>[a-fA-F0-9:]+),"
+ STATE_PATTERN = "(\s+)link-state(\s+)(?P<state>\w+)"
+ ANY_PATTERN = "(.*),"
+
+ VF_LINE_FORMAT = VF_PATTERN + MAC_PATTERN + ANY_PATTERN + STATE_PATTERN
+ VF_DETAILS_REG_EX = re.compile(VF_LINE_FORMAT)
+
+ class LinkState:
+ ENABLE = "enable"
+ DISABLE = "disable"
+
+ def __init__(self, dev_name, root_helper=None):
+ super(ip_lib.IPWrapper, self).__init__(root_helper=root_helper)
+ self.dev_name = dev_name
+
+ def get_assigned_macs(self, vf_list):
+ """Get assigned mac addresses for vf list.
+
+ @param vf_list: list of vf indexes
+ @return: list of assigned mac addresses
+ """
+ try:
+ out = self._execute('', "link", ("show", self.dev_name),
+ self.root_helper)
+ except Exception as e:
+ LOG.exception(_("Failed executing ip command"))
+ raise exc.IpCommandError(dev_name=self.dev_name,
+ reason=str(e))
+ vf_lines = self._get_vf_link_show(vf_list, out)
+ vf_details_list = []
+ if vf_lines:
+ for vf_line in vf_lines:
+ vf_details = self._parse_vf_link_show(vf_line)
+ if vf_details:
+ vf_details_list.append(vf_details)
+ return [vf_details.get("MAC") for vf_details in
+ vf_details_list]
+
+ def get_vf_state(self, vf_index):
+ """Get vf state {True/False}
+
+ @param vf_index: vf index
+ @todo: Handle "auto" state
+ """
+ try:
+ out = self._execute('', "link", ("show", self.dev_name),
+ self.root_helper)
+ except Exception as e:
+ LOG.exception(_("Failed executing ip command"))
+ raise exc.IpCommandError(dev_name=self.dev_name,
+ reason=str(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",
+ self.LinkState.DISABLE)
+ if state != self.LinkState.DISABLE:
+ return True
+ return False
+
+ def set_vf_state(self, vf_index, state):
+ """sets vf state.
+
+ @param vf_index: vf index
+ @param state: required state {True/False}
+ """
+ status_str = self.LinkState.ENABLE if state else \
+ self.LinkState.DISABLE
+
+ try:
+ self._execute('', "link", ("set", self.dev_name, "vf",
+ str(vf_index), "state", status_str),
+ self.root_helper)
+ except Exception as e:
+ LOG.exception(_("Failed executing ip command"))
+ raise exc.IpCommandError(dev_name=self.dev_name,
+ reason=str(e))
+
+ 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
--- /dev/null
+# 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 socket
+import sys
+import time
+
+import eventlet
+eventlet.monkey_patch()
+
+from oslo.config import cfg
+
+from neutron.agent import rpc as agent_rpc
+from neutron.agent import securitygroups_rpc as sg_rpc
+from neutron.common import config as common_config
+from neutron.common import constants as q_constants
+from neutron.common import rpc as n_rpc
+from neutron.common import topics
+from neutron.common import utils as q_utils
+from neutron import context
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import loopingcall
+from neutron.plugins.sriovnicagent.common import config # noqa
+from neutron.plugins.sriovnicagent.common import exceptions as exc
+from neutron.plugins.sriovnicagent import eswitch_manager as esm
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SriovNicSwitchRpcCallbacks(n_rpc.RpcCallback,
+ sg_rpc.SecurityGroupAgentRpcCallbackMixin):
+
+ # Set RPC API version to 1.0 by default.
+ # history
+ # 1.1 Support Security Group RPC
+ RPC_API_VERSION = '1.1'
+
+ def __init__(self, context, agent):
+ super(SriovNicSwitchRpcCallbacks, self).__init__()
+ self.context = context
+ self.agent = agent
+ self.sg_agent = agent
+
+ def port_update(self, context, **kwargs):
+ LOG.debug("port_update received")
+ port = kwargs.get('port')
+ # Put the port mac address in the updated_devices set.
+ # Do not store port details, as if they're used for processing
+ # notifications there is no guarantee the notifications are
+ # processed in the same order as the relevant API requests.
+ self.agent.updated_devices.add(port['mac_address'])
+ LOG.debug(_("port_update RPC received for port: %s"), port['id'])
+
+
+class SriovNicSwitchPluginApi(agent_rpc.PluginApi,
+ sg_rpc.SecurityGroupServerRpcApiMixin):
+ pass
+
+
+class SriovNicSwitchAgent(sg_rpc.SecurityGroupAgentRpcMixin):
+ def __init__(self, physical_devices_mappings, exclude_devices,
+ polling_interval, root_helper):
+
+ self.polling_interval = polling_interval
+ self.root_helper = root_helper
+ self.setup_eswitch_mgr(physical_devices_mappings,
+ exclude_devices)
+ configurations = {'device_mappings': physical_devices_mappings}
+ self.agent_state = {
+ 'binary': 'neutron-sriov-nic-agent',
+ 'host': cfg.CONF.host,
+ 'topic': q_constants.L2_AGENT_TOPIC,
+ 'configurations': configurations,
+ 'agent_type': q_constants.AGENT_TYPE_NIC_SWITCH,
+ 'start_flag': True}
+
+ # Stores port update notifications for processing in the main loop
+ self.updated_devices = set()
+ self._setup_rpc()
+ self.init_firewall()
+ # Initialize iteration counter
+ self.iter_num = 0
+
+ def _setup_rpc(self):
+ self.agent_id = 'nic-switch-agent.%s' % socket.gethostname()
+ LOG.info(_("RPC agent_id: %s"), self.agent_id)
+
+ self.topic = topics.AGENT
+ self.plugin_rpc = SriovNicSwitchPluginApi(topics.PLUGIN)
+ self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
+ # RPC network init
+ self.context = context.get_admin_context_without_session()
+ # Handle updates from service
+ self.endpoints = [SriovNicSwitchRpcCallbacks(self.context, self)]
+ # Define the listening consumers for the agent
+ consumers = [[topics.PORT, topics.UPDATE],
+ [topics.NETWORK, topics.DELETE],
+ [topics.SECURITY_GROUP, topics.UPDATE]]
+ self.connection = agent_rpc.create_consumers(self.endpoints,
+ self.topic,
+ consumers)
+
+ report_interval = cfg.CONF.AGENT.report_interval
+ if report_interval:
+ heartbeat = loopingcall.FixedIntervalLoopingCall(
+ self._report_state)
+ heartbeat.start(interval=report_interval)
+
+ def _report_state(self):
+ try:
+ devices = len(self.eswitch_mgr.get_assigned_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)
+ except Exception:
+ LOG.exception(_("Failed reporting state!"))
+
+ def setup_eswitch_mgr(self, device_mappings, exclude_devices={}):
+ self.eswitch_mgr = esm.ESwitchManager(device_mappings,
+ exclude_devices,
+ self.root_helper)
+
+ def scan_devices(self, registered_devices, updated_devices):
+ curr_devices = self.eswitch_mgr.get_assigned_devices()
+ device_info = {}
+ device_info['current'] = curr_devices
+ device_info['added'] = curr_devices - registered_devices
+ # we don't want to process updates for devices that don't exist
+ device_info['updated'] = updated_devices & curr_devices
+ # we need to clean up after devices are removed
+ device_info['removed'] = registered_devices - curr_devices
+ return device_info
+
+ def _device_info_has_changes(self, device_info):
+ return (device_info.get('added')
+ or device_info.get('updated')
+ or device_info.get('removed'))
+
+ def process_network_devices(self, device_info):
+ resync_a = False
+ resync_b = False
+
+ self.prepare_devices_filter(device_info.get('added'))
+
+ if device_info.get('updated'):
+ self.refresh_firewall()
+ # Updated devices are processed the same as new ones, as their
+ # admin_state_up may have changed. The set union prevents duplicating
+ # work when a device is new and updated in the same polling iteration.
+ devices_added_updated = (set(device_info.get('added'))
+ | set(device_info.get('updated')))
+ if devices_added_updated:
+ resync_a = self.treat_devices_added_updated(devices_added_updated)
+
+ if device_info.get('removed'):
+ resync_b = self.treat_devices_removed(device_info['removed'])
+ # If one of the above operations fails => resync with plugin
+ return (resync_a | resync_b)
+
+ def treat_device(self, device, pci_slot, admin_state_up):
+ if self.eswitch_mgr.device_exists(device, pci_slot):
+ try:
+ self.eswitch_mgr.set_device_state(device, pci_slot,
+ admin_state_up)
+ except exc.SriovNicError:
+ LOG.exception(_("Failed to set device %s state"), device)
+ return
+ if admin_state_up:
+ # update plugin about port status
+ self.plugin_rpc.update_device_up(self.context,
+ device,
+ self.agent_id,
+ cfg.CONF.host)
+ else:
+ self.plugin_rpc.update_device_down(self.context,
+ device,
+ self.agent_id,
+ cfg.CONF.host)
+ else:
+ LOG.info(_("No device with MAC %s defined on agent."), device)
+
+ def treat_devices_added_updated(self, devices):
+ try:
+ devices_details_list = self.plugin_rpc.get_devices_details_list(
+ self.context, devices, self.agent_id)
+ except Exception as e:
+ LOG.debug("Unable to get port details for devices "
+ "with MAC address %(devices)s: %(e)s",
+ {'devices': devices, 'e': e})
+ # resync is needed
+ return True
+
+ for device_details in devices_details_list:
+ device = device_details['device']
+ LOG.debug("Port with MAC address %s is added", device)
+
+ if 'port_id' in device_details:
+ LOG.info(_("Port %(device)s updated. Details: %(details)s"),
+ {'device': device, 'details': device_details})
+ profile = device_details['profile']
+ self.treat_device(device_details['device'],
+ profile.get('pci_slot'),
+ device_details['admin_state_up'])
+ else:
+ LOG.info(_("Device with MAC %s not defined on plugin"), device)
+ return False
+
+ def treat_devices_removed(self, devices):
+ resync = False
+ for device in devices:
+ LOG.info(_("Removing device with mac_address %s"), device)
+ try:
+ dev_details = self.plugin_rpc.update_device_down(self.context,
+ device,
+ 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})
+ resync = True
+ continue
+ if dev_details['exists']:
+ LOG.info(_("Port %s updated."), device)
+ else:
+ LOG.debug(_("Device %s not defined on plugin"), device)
+ return resync
+
+ def daemon_loop(self):
+ sync = True
+ devices = set()
+
+ LOG.info(_("SRIOV NIC Agent RPC Daemon Started!"))
+
+ while True:
+ start = time.time()
+ LOG.debug("Agent rpc_loop - iteration:%d started",
+ self.iter_num)
+ if sync:
+ LOG.info(_("Agent out of sync with plugin!"))
+ devices.clear()
+ sync = False
+ device_info = {}
+ # Save updated devices dict to perform rollback in case
+ # resync would be needed, and then clear self.updated_devices.
+ # As the greenthread should not yield between these
+ # two statements, this will should be thread-safe.
+ updated_devices_copy = self.updated_devices
+ self.updated_devices = set()
+ try:
+ device_info = self.scan_devices(devices, updated_devices_copy)
+ if self._device_info_has_changes(device_info):
+ LOG.debug(_("Agent loop found changes! %s"), device_info)
+ # If treat devices fails - indicates must resync with
+ # plugin
+ sync = self.process_network_devices(device_info)
+ devices = device_info['current']
+ except Exception:
+ LOG.exception(_("Error in agent loop. Devices info: %s"),
+ device_info)
+ sync = True
+ # Restore devices that were removed from this set earlier
+ # without overwriting ones that may have arrived since.
+ self.updated_devices |= updated_devices_copy
+
+ # sleep till end of polling interval
+ elapsed = (time.time() - start)
+ if (elapsed < self.polling_interval):
+ time.sleep(self.polling_interval - elapsed)
+ else:
+ LOG.debug(_("Loop iteration exceeded interval "
+ "(%(polling_interval)s vs. %(elapsed)s)!"),
+ {'polling_interval': self.polling_interval,
+ 'elapsed': elapsed})
+ self.iter_num = self.iter_num + 1
+
+
+class SriovNicAgentConfigParser(object):
+ def __init__(self):
+ self.device_mappings = {}
+ self.exclude_devices = {}
+
+ def parse(self):
+ """Parses device_mappings and exclude_devices.
+
+ Parse and validate the consistency in both mappings
+ """
+ self.device_mappings = q_utils.parse_mappings(
+ cfg.CONF.SRIOV_NIC.physical_device_mappings)
+ self.exclude_devices = config.parse_exclude_devices(
+ cfg.CONF.SRIOV_NIC.exclude_devices)
+ self._validate()
+
+ def _validate(self):
+ """ Validate configuration.
+
+ Validate that network_device in excluded_device
+ exists in device mappings
+ """
+ dev_net_set = set(self.device_mappings.itervalues())
+ for dev_name in self.exclude_devices.iterkeys():
+ if dev_name not in dev_net_set:
+ raise ValueError(_("Device name %(dev_name)s is missing from "
+ "physical_device_mappings") % {'dev_name':
+ dev_name})
+
+
+def main():
+ common_config.init(sys.argv[1:])
+
+ common_config.setup_logging(cfg.CONF)
+ try:
+ config_parser = SriovNicAgentConfigParser()
+ config_parser.parse()
+ device_mappings = config_parser.device_mappings
+ exclude_devices = config_parser.exclude_devices
+
+ except ValueError as e:
+ LOG.error(_("Failed on Agent configuration parse : %s."
+ " Agent terminated!"), e)
+ raise SystemExit(1)
+ LOG.info(_("Physical Devices mappings: %s"), device_mappings)
+ LOG.info(_("Exclude Devices: %s"), exclude_devices)
+
+ polling_interval = cfg.CONF.AGENT.polling_interval
+ root_helper = cfg.CONF.AGENT.root_helper
+ try:
+ agent = SriovNicSwitchAgent(device_mappings,
+ exclude_devices,
+ polling_interval,
+ root_helper)
+ except exc.SriovNicError:
+ LOG.exception(_("Agent Initialization Failed"))
+ raise SystemExit(1)
+ # Start everything.
+ LOG.info(_("Agent initialized successfully, now running... "))
+ agent.daemon_loop()
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+# 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 contextlib
+import os
+
+import mock
+import testtools
+
+
+from neutron.plugins.sriovnicagent.common import exceptions as exc
+from neutron.plugins.sriovnicagent import eswitch_manager as esm
+from neutron.tests import base
+
+
+class TestCreateESwitchManager(base.BaseTestCase):
+ SCANNED_DEVICES = [('0000:06:00.1', 0),
+ ('0000:06:00.2', 1),
+ ('0000:06:00.3', 2)]
+
+ def test_create_eswitch_mgr_fail(self):
+ device_mappings = {'physnet1': 'p6p1'}
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.scan_vf_devices",
+ side_effect=exc.InvalidDeviceError(dev_name="p6p1",
+ reason="device"
+ " not found")),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=True)):
+
+ with testtools.ExpectedException(exc.InvalidDeviceError):
+ esm.ESwitchManager(device_mappings, None, None)
+
+ def test_create_eswitch_mgr_ok(self):
+ device_mappings = {'physnet1': 'p6p1'}
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.scan_vf_devices",
+ return_value=self.SCANNED_DEVICES),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=True)):
+
+ esm.ESwitchManager(device_mappings, None, None)
+
+
+class TestESwitchManagerApi(base.BaseTestCase):
+ SCANNED_DEVICES = [('0000:06:00.1', 0),
+ ('0000:06:00.2', 1),
+ ('0000:06:00.3', 2)]
+
+ ASSIGNED_MAC = '00:00:00:00:00:66'
+ PCI_SLOT = '0000:06:00.1'
+ WRONG_MAC = '00:00:00:00:00:67'
+ WRONG_PCI = "0000:06:00.6"
+
+ def setUp(self):
+ super(TestESwitchManagerApi, self).setUp()
+ device_mappings = {'physnet1': 'p6p1'}
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.scan_vf_devices",
+ return_value=self.SCANNED_DEVICES),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=True)):
+ self.eswitch_mgr = esm.ESwitchManager(device_mappings, None, None)
+
+ def test_get_assigned_devices(self):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_assigned_devices",
+ return_value=[self.ASSIGNED_MAC]):
+ result = self.eswitch_mgr.get_assigned_devices()
+ self.assertEqual(set([self.ASSIGNED_MAC]), result)
+
+ def test_get_device_status_true(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_device_state",
+ return_value=True)):
+ result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
+ self.PCI_SLOT)
+ self.assertTrue(result)
+
+ def test_get_device_status_false(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_device_state",
+ return_value=False)):
+ result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
+ self.PCI_SLOT)
+ self.assertFalse(result)
+
+ def test_get_device_status_mismatch(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_device_state",
+ return_value=True)):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "LOG.warning") as log_mock:
+ result = self.eswitch_mgr.get_device_state(self.WRONG_MAC,
+ self.PCI_SLOT)
+ log_mock.assert_called_with('device pci mismatch: '
+ '%(device_mac)s - %(pci_slot)s',
+ {'pci_slot': self.PCI_SLOT,
+ 'device_mac': self.WRONG_MAC})
+ self.assertFalse(result)
+
+ def test_set_device_status(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.set_device_state")):
+ self.eswitch_mgr.set_device_state(self.ASSIGNED_MAC,
+ self.PCI_SLOT, True)
+
+ def test_set_device_status_mismatch(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.set_device_state")):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "LOG.warning") as log_mock:
+ self.eswitch_mgr.set_device_state(self.WRONG_MAC,
+ self.PCI_SLOT, True)
+ log_mock.assert_called_with('device pci mismatch: '
+ '%(device_mac)s - %(pci_slot)s',
+ {'pci_slot': self.PCI_SLOT,
+ 'device_mac': self.WRONG_MAC})
+
+ def _mock_device_exists(self, pci_slot, mac_address, expected_result):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC):
+ result = self.eswitch_mgr.device_exists(mac_address,
+ pci_slot)
+ self.assertEqual(expected_result, result)
+
+ def test_device_exists_true(self):
+ self._mock_device_exists(self.PCI_SLOT,
+ self.ASSIGNED_MAC,
+ True)
+
+ def test_device_exists_false(self):
+ self._mock_device_exists(self.WRONG_PCI,
+ self.WRONG_MAC,
+ False)
+
+ def test_device_exists_mismatch(self):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "EmbSwitch.get_pci_device",
+ return_value=self.ASSIGNED_MAC):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "LOG.warning") as log_mock:
+ result = self.eswitch_mgr.device_exists(self.WRONG_MAC,
+ self.PCI_SLOT)
+ log_mock.assert_called_with('device pci mismatch: '
+ '%(device_mac)s - %(pci_slot)s',
+ {'pci_slot': self.PCI_SLOT,
+ 'device_mac': self.WRONG_MAC})
+ self.assertFalse(result)
+
+
+class TestEmbSwitch(base.BaseTestCase):
+ DEV_NAME = "eth2"
+ PHYS_NET = "default"
+ ASSIGNED_MAC = '00:00:00:00:00:66'
+ PCI_SLOT = "0000:06:00.1"
+ WRONG_PCI_SLOT = "0000:06:00.4"
+ SCANNED_DEVICES = [('0000:06:00.1', 0),
+ ('0000:06:00.2', 1),
+ ('0000:06:00.3', 2)]
+
+ def setUp(self):
+ super(TestEmbSwitch, self).setUp()
+ exclude_devices = set()
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.scan_vf_devices",
+ return_value=self.SCANNED_DEVICES):
+ self.emb_switch = esm.EmbSwitch(self.PHYS_NET, self.DEV_NAME,
+ exclude_devices, None)
+
+ def test_get_assigned_devices(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.get_assigned_macs",
+ return_value=[self.ASSIGNED_MAC]),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=True)):
+ result = self.emb_switch.get_assigned_devices()
+ self.assertEqual([self.ASSIGNED_MAC], result)
+
+ def test_get_assigned_devices_empty(self):
+ with mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=False):
+ result = self.emb_switch.get_assigned_devices()
+ self.assertFalse(result)
+
+ def test_get_device_state_ok(self):
+ with mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.get_vf_state",
+ return_value=False):
+ result = self.emb_switch.get_device_state(self.PCI_SLOT)
+ self.assertFalse(result)
+
+ def test_get_device_state_fail(self):
+ with mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.get_vf_state",
+ return_value=False):
+ self.assertRaises(exc.InvalidPciSlotError,
+ self.emb_switch.get_device_state,
+ self.WRONG_PCI_SLOT)
+
+ def test_set_device_state_ok(self):
+ with mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.set_vf_state"):
+ with mock.patch("neutron.plugins.sriovnicagent.pci_lib.LOG."
+ "warning") as log_mock:
+ self.emb_switch.set_device_state(self.PCI_SLOT, True)
+ self.assertEqual(0, log_mock.call_count)
+
+ def test_set_device_state_fail(self):
+ with mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.set_vf_state"):
+ self.assertRaises(exc.InvalidPciSlotError,
+ self.emb_switch.set_device_state,
+ self.WRONG_PCI_SLOT, True)
+
+ def test_get_pci_device(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.get_assigned_macs",
+ return_value=[self.ASSIGNED_MAC]),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=True)):
+ result = self.emb_switch.get_pci_device(self.PCI_SLOT)
+ self.assertEqual(self.ASSIGNED_MAC, result)
+
+ def test_get_pci_device_fail(self):
+ with contextlib.nested(
+ mock.patch("neutron.plugins.sriovnicagent.pci_lib."
+ "PciDeviceIPWrapper.get_assigned_macs",
+ return_value=[self.ASSIGNED_MAC]),
+ mock.patch("neutron.plugins.sriovnicagent.eswitch_manager."
+ "PciOsWrapper.is_assigned_vf",
+ return_value=True)):
+ result = self.emb_switch.get_pci_device(self.WRONG_PCI_SLOT)
+ self.assertIsNone(result)
+
+ def test_get_pci_list(self):
+ result = self.emb_switch.get_pci_slot_list()
+ self.assertEqual([tup[0] for tup in self.SCANNED_DEVICES], result)
+
+
+class TestPciOsWrapper(base.BaseTestCase):
+ DEV_NAME = "p7p1"
+ VF_INDEX = 1
+ DIR_CONTENTS = [
+ "mlx4_port1",
+ "virtfn0",
+ "virtfn1",
+ "virtfn2"
+ ]
+ DIR_CONTENTS_NO_MATCH = [
+ "mlx4_port1",
+ "mlx4_port1"
+ ]
+ LINKS = {
+ "virtfn0": "../0000:04:00.1",
+ "virtfn1": "../0000:04:00.2",
+ "virtfn2": "../0000:04:00.3"
+ }
+ PCI_SLOTS = [
+ ('0000:04:00.1', 0),
+ ('0000:04:00.2', 1),
+ ('0000:04:00.3', 2)
+ ]
+
+ def test_scan_vf_devices(self):
+ def _get_link(file_path):
+ file_name = os.path.basename(file_path)
+ return self.LINKS[file_name]
+
+ with contextlib.nested(
+ mock.patch("os.path.isdir",
+ return_value=True),
+ mock.patch("os.listdir",
+ return_value=self.DIR_CONTENTS),
+ mock.patch("os.path.islink",
+ return_value=True),
+ mock.patch("os.readlink",
+ side_effect=_get_link),):
+ result = esm.PciOsWrapper.scan_vf_devices(self.DEV_NAME)
+ self.assertEqual(self.PCI_SLOTS, result)
+
+ def test_scan_vf_devices_no_dir(self):
+ with mock.patch("os.path.isdir", return_value=False):
+ self.assertRaises(exc.InvalidDeviceError,
+ esm.PciOsWrapper.scan_vf_devices,
+ self.DEV_NAME)
+
+ def test_scan_vf_devices_no_content(self):
+ with contextlib.nested(
+ mock.patch("os.path.isdir",
+ return_value=True),
+ mock.patch("os.listdir",
+ return_value=[])):
+ self.assertRaises(exc.InvalidDeviceError,
+ esm.PciOsWrapper.scan_vf_devices,
+ self.DEV_NAME)
+
+ def test_scan_vf_devices_no_match(self):
+ with contextlib.nested(
+ mock.patch("os.path.isdir",
+ return_value=True),
+ mock.patch("os.listdir",
+ return_value=self.DIR_CONTENTS_NO_MATCH)):
+ self.assertRaises(exc.InvalidDeviceError,
+ esm.PciOsWrapper.scan_vf_devices,
+ self.DEV_NAME)
+
+ def _mock_assign_vf(self, dir_exists):
+ with mock.patch("os.path.isdir",
+ return_value=dir_exists):
+ result = esm.PciOsWrapper.is_assigned_vf(self.DEV_NAME,
+ self.VF_INDEX)
+ self.assertEqual(not dir_exists, result)
+
+ def test_is_assigned_vf_true(self):
+ self._mock_assign_vf(True)
+
+ def test_is_assigned_vf_false(self):
+ self._mock_assign_vf(False)
--- /dev/null
+# 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.plugins.sriovnicagent.common import exceptions as exc
+from neutron.plugins.sriovnicagent 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: <BROADCAST,MULTICAST> 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))
+
+ MAC_MAPPING = {
+ 0: "fa:16:3e:b4:81:ac",
+ 1: "00:00:00:00:00:11",
+ 2: "fa:16:3e:68:4e:79",
+ }
+
+ def setUp(self):
+ super(TestPciLib, self).setUp()
+ self.pci_wrapper = pci_lib.PciDeviceIPWrapper(self.DEV_NAME)
+
+ def test_get_assigned_macs(self):
+ with mock.patch.object(self.pci_wrapper,
+ "_execute") as mock_exec:
+ mock_exec.return_value = self.VF_LINK_SHOW
+ result = self.pci_wrapper.get_assigned_macs([self.VF_INDEX])
+ self.assertEqual([self.MAC_MAPPING[self.VF_INDEX]], result)
+
+ def test_get_assigned_macs_fail(self):
+ with mock.patch.object(self.pci_wrapper,
+ "_execute") as mock_exec:
+ mock_exec.side_effect = Exception()
+ self.assertRaises(exc.IpCommandError,
+ self.pci_wrapper.get_assigned_macs,
+ [self.VF_INDEX])
+
+ def test_get_vf_state_enable(self):
+ with mock.patch.object(self.pci_wrapper,
+ "_execute") as mock_exec:
+ mock_exec.return_value = self.VF_LINK_SHOW
+ result = self.pci_wrapper.get_vf_state(self.VF_INDEX)
+ self.assertTrue(result)
+
+ def test_get_vf_state_disable(self):
+ with mock.patch.object(self.pci_wrapper,
+ "_execute") as mock_exec:
+ mock_exec.return_value = self.VF_LINK_SHOW
+ result = self.pci_wrapper.get_vf_state(self.VF_INDEX_DISABLE)
+ self.assertFalse(result)
+
+ def test_get_vf_state_fail(self):
+ with mock.patch.object(self.pci_wrapper,
+ "_execute") as mock_exec:
+ mock_exec.side_effect = Exception()
+ self.assertRaises(exc.IpCommandError,
+ self.pci_wrapper.get_vf_state,
+ self.VF_INDEX)
+
+ def test_set_vf_state(self):
+ with mock.patch.object(self.pci_wrapper, "_execute"):
+ result = self.pci_wrapper.set_vf_state(self.VF_INDEX,
+ True)
+ self.assertIsNone(result)
+
+ def test_set_vf_state_fail(self):
+ with mock.patch.object(self.pci_wrapper,
+ "_execute") as mock_exec:
+ mock_exec.side_effect = Exception()
+ self.assertRaises(exc.IpCommandError,
+ self.pci_wrapper.set_vf_state,
+ self.VF_INDEX,
+ True)
--- /dev/null
+# 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 oslo.config import cfg
+
+from neutron.common import utils as q_utils
+from neutron.plugins.sriovnicagent.common import config
+from neutron.plugins.sriovnicagent import sriov_nic_agent as agent
+from neutron.tests import base
+
+
+class TestSriovAgentConfig(base.BaseTestCase):
+ EXCLUDE_DEVICES_LIST = ['p7p1:0000:07:00.1;0000:07:00.2',
+ 'p3p1:0000:04:00.3']
+
+ EXCLUDE_DEVICES_LIST_INVALID = ['p7p2:0000:07:00.1;0000:07:00.2']
+
+ EXCLUDE_DEVICES_WITH_SPACES_LIST = ['p7p1: 0000:07:00.1 ; 0000:07:00.2',
+ 'p3p1:0000:04:00.3 ']
+
+ EXCLUDE_DEVICES_WITH_SPACES_ERROR = ['p7p1',
+ 'p3p1:0000:04:00.3 ']
+
+ EXCLUDE_DEVICES = {'p7p1': set(['0000:07:00.1', '0000:07:00.2']),
+ 'p3p1': set(['0000:04:00.3'])}
+
+ DEVICE_MAPPING_LIST = ['physnet7:p7p1',
+ 'physnet3:p3p1']
+
+ DEVICE_MAPPING_WITH_ERROR_LIST = ['physnet7',
+ 'physnet3:p3p1']
+
+ DEVICE_MAPPING_WITH_SPACES_LIST = ['physnet7 : p7p1',
+ 'physnet3 : p3p1 ']
+ DEVICE_MAPPING = {'physnet7': 'p7p1',
+ 'physnet3': 'p3p1'}
+
+ def test_defaults(self):
+ self.assertEqual(config.DEFAULT_DEVICE_MAPPINGS,
+ cfg.CONF.SRIOV_NIC.physical_device_mappings)
+ self.assertEqual(config.DEFAULT_EXCLUDE_DEVICES,
+ cfg.CONF.SRIOV_NIC.exclude_devices)
+ self.assertEqual(2,
+ cfg.CONF.AGENT.polling_interval)
+
+ def test_device_mappings(self):
+ cfg.CONF.set_override('physical_device_mappings',
+ self.DEVICE_MAPPING_LIST,
+ 'SRIOV_NIC')
+ device_mappings = q_utils.parse_mappings(
+ cfg.CONF.SRIOV_NIC.physical_device_mappings)
+ self.assertEqual(device_mappings, self.DEVICE_MAPPING)
+
+ def test_device_mappings_with_error(self):
+ cfg.CONF.set_override('physical_device_mappings',
+ self.DEVICE_MAPPING_WITH_ERROR_LIST,
+ 'SRIOV_NIC')
+ self.assertRaises(ValueError, q_utils.parse_mappings,
+ cfg.CONF.SRIOV_NIC.physical_device_mappings)
+
+ def test_device_mappings_with_spaces(self):
+ cfg.CONF.set_override('physical_device_mappings',
+ self.DEVICE_MAPPING_WITH_SPACES_LIST,
+ 'SRIOV_NIC')
+ device_mappings = q_utils.parse_mappings(
+ cfg.CONF.SRIOV_NIC.physical_device_mappings)
+ self.assertEqual(device_mappings, self.DEVICE_MAPPING)
+
+ def test_exclude_devices(self):
+ cfg.CONF.set_override('exclude_devices',
+ self.EXCLUDE_DEVICES_LIST,
+ 'SRIOV_NIC')
+ exclude_devices = config.parse_exclude_devices(
+ cfg.CONF.SRIOV_NIC.exclude_devices)
+ self.assertEqual(exclude_devices, self.EXCLUDE_DEVICES)
+
+ def test_exclude_devices_with_spaces(self):
+ cfg.CONF.set_override('exclude_devices',
+ self.EXCLUDE_DEVICES_WITH_SPACES_LIST,
+ 'SRIOV_NIC')
+ exclude_devices = config.parse_exclude_devices(
+ cfg.CONF.SRIOV_NIC.exclude_devices)
+ self.assertEqual(exclude_devices, self.EXCLUDE_DEVICES)
+
+ def test_exclude_devices_with_error(self):
+ cfg.CONF.set_override('exclude_devices',
+ self.EXCLUDE_DEVICES_WITH_SPACES_ERROR,
+ 'SRIOV_NIC')
+ self.assertRaises(ValueError, config.parse_exclude_devices,
+ cfg.CONF.SRIOV_NIC.exclude_devices)
+
+ def test_validate_config_ok(self):
+ cfg.CONF.set_override('physical_device_mappings',
+ self.DEVICE_MAPPING_LIST,
+ 'SRIOV_NIC')
+ cfg.CONF.set_override('exclude_devices',
+ self.EXCLUDE_DEVICES_LIST,
+ 'SRIOV_NIC')
+ config_parser = agent.SriovNicAgentConfigParser()
+ config_parser.parse()
+ device_mappings = config_parser.device_mappings
+ exclude_devices = config_parser.exclude_devices
+ self.assertEqual(exclude_devices, self.EXCLUDE_DEVICES)
+ self.assertEqual(device_mappings, self.DEVICE_MAPPING)
+
+ def test_validate_config_fail(self):
+ cfg.CONF.set_override('physical_device_mappings',
+ self.DEVICE_MAPPING_LIST,
+ 'SRIOV_NIC')
+ cfg.CONF.set_override('exclude_devices',
+ self.EXCLUDE_DEVICES_LIST_INVALID,
+ 'SRIOV_NIC')
+ config_parser = agent.SriovNicAgentConfigParser()
+ self.assertRaises(ValueError, config_parser.parse)
--- /dev/null
+# 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 oslo.config import cfg
+
+from neutron.plugins.sriovnicagent.common import config # noqa
+from neutron.plugins.sriovnicagent import sriov_nic_agent
+from neutron.tests import base
+
+DEVICE_MAC = '11:22:33:44:55:66'
+
+
+class TestSriovAgent(base.BaseTestCase):
+ def setUp(self):
+ super(TestSriovAgent, self).setUp()
+ # disable setting up periodic state reporting
+ cfg.CONF.set_override('report_interval', 0, 'AGENT')
+ cfg.CONF.set_override('rpc_backend',
+ 'neutron.openstack.common.rpc.impl_fake')
+ cfg.CONF.set_default('firewall_driver',
+ 'neutron.agent.firewall.NoopFirewallDriver',
+ group='SECURITYGROUP')
+ cfg.CONF.set_default('enable_security_group',
+ False,
+ group='SECURITYGROUP')
+
+ class MockFixedIntervalLoopingCall(object):
+ def __init__(self, f):
+ self.f = f
+
+ def start(self, interval=0):
+ self.f()
+
+ mock.patch('neutron.openstack.common.loopingcall.'
+ 'FixedIntervalLoopingCall',
+ new=MockFixedIntervalLoopingCall)
+
+ self.agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, None)
+
+ def test_treat_devices_removed_with_existed_device(self):
+ agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, None)
+ devices = [DEVICE_MAC]
+ with mock.patch.object(agent.plugin_rpc,
+ "update_device_down") as fn_udd:
+ fn_udd.return_value = {'device': DEVICE_MAC,
+ 'exists': True}
+ with mock.patch.object(sriov_nic_agent.LOG,
+ 'info') as log:
+ resync = agent.treat_devices_removed(devices)
+ self.assertEqual(2, log.call_count)
+ self.assertFalse(resync)
+ self.assertTrue(fn_udd.called)
+
+ def test_treat_devices_removed_with_not_existed_device(self):
+ agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, None)
+ devices = [DEVICE_MAC]
+ with mock.patch.object(agent.plugin_rpc,
+ "update_device_down") as fn_udd:
+ fn_udd.return_value = {'device': DEVICE_MAC,
+ 'exists': False}
+ with mock.patch.object(sriov_nic_agent.LOG,
+ 'debug') as log:
+ resync = agent.treat_devices_removed(devices)
+ self.assertEqual(1, log.call_count)
+ self.assertFalse(resync)
+ self.assertTrue(fn_udd.called)
+
+ def test_treat_devices_removed_failed(self):
+ agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, None)
+ devices = [DEVICE_MAC]
+ with mock.patch.object(agent.plugin_rpc,
+ "update_device_down") as fn_udd:
+ fn_udd.side_effect = Exception()
+ with mock.patch.object(sriov_nic_agent.LOG,
+ 'debug') as log:
+ resync = agent.treat_devices_removed(devices)
+ self.assertEqual(1, log.call_count)
+ self.assertTrue(resync)
+ self.assertTrue(fn_udd.called)
+
+ def mock_scan_devices(self, expected, mock_current,
+ registered_devices, updated_devices):
+ self.agent.eswitch_mgr = mock.Mock()
+ self.agent.eswitch_mgr.get_assigned_devices.return_value = mock_current
+
+ results = self.agent.scan_devices(registered_devices, updated_devices)
+ self.assertEqual(expected, results)
+
+ def test_scan_devices_returns_empty_sets(self):
+ registered = set()
+ updated = set()
+ mock_current = set()
+ expected = {'current': set(),
+ 'updated': set(),
+ 'added': set(),
+ 'removed': set()}
+ self.mock_scan_devices(expected, mock_current, registered, updated)
+
+ def test_scan_devices_no_changes(self):
+ registered = set(['1', '2'])
+ updated = set()
+ mock_current = set(['1', '2'])
+ expected = {'current': set(['1', '2']),
+ 'updated': set(),
+ 'added': set(),
+ 'removed': set()}
+ self.mock_scan_devices(expected, mock_current, registered, updated)
+
+ def test_scan_devices_new_and_removed(self):
+ registered = set(['1', '2'])
+ updated = set()
+ mock_current = set(['2', '3'])
+ expected = {'current': set(['2', '3']),
+ 'updated': set(),
+ 'added': set(['3']),
+ 'removed': set(['1'])}
+ self.mock_scan_devices(expected, mock_current, registered, updated)
+
+ def test_scan_devices_new_updates(self):
+ registered = set(['1'])
+ updated = set(['2'])
+ mock_current = set(['1', '2'])
+ expected = {'current': set(['1', '2']),
+ 'updated': set(['2']),
+ 'added': set(['2']),
+ 'removed': set()}
+ self.mock_scan_devices(expected, mock_current, registered, updated)
+
+ def test_scan_devices_updated_missing(self):
+ registered = set(['1'])
+ updated = set(['2'])
+ mock_current = set(['1'])
+ expected = {'current': set(['1']),
+ 'updated': set(),
+ 'added': set(),
+ 'removed': set()}
+ self.mock_scan_devices(expected, mock_current, registered, updated)
+
+ def test_process_network_devices(self):
+ agent = self.agent
+ device_info = {'current': set(),
+ 'added': set(['mac3', 'mac4']),
+ 'updated': set(['mac2', 'mac3']),
+ 'removed': set(['mac1'])}
+ agent.prepare_devices_filter = mock.Mock()
+ agent.refresh_firewall = mock.Mock()
+ agent.treat_devices_added_updated = mock.Mock(return_value=False)
+ agent.treat_devices_removed = mock.Mock(return_value=False)
+
+ agent.process_network_devices(device_info)
+
+ agent.prepare_devices_filter.assert_called_with(set(['mac3', 'mac4']))
+ self.assertTrue(agent.refresh_firewall.called)
+ agent.treat_devices_added_updated.assert_called_with(set(['mac2',
+ 'mac3',
+ 'mac4']))
+ agent.treat_devices_removed.assert_called_with(set(['mac1']))
+
+ def test_treat_devices_added_updated_admin_state_up_true(self):
+ agent = self.agent
+ mock_details = {'device': 'aa:bb:cc:dd:ee:ff',
+ 'port_id': 'port123',
+ 'network_id': 'net123',
+ 'admin_state_up': True,
+ 'network_type': 'vlan',
+ 'segmentation_id': 100,
+ 'profile': {'pci_slot': '1:2:3.0'},
+ 'physical_network': 'physnet1'}
+ agent.plugin_rpc = mock.Mock()
+ agent.plugin_rpc.get_devices_details_list.return_value = [mock_details]
+ agent.eswitch_mgr = mock.Mock()
+ agent.eswitch_mgr.device_exists.return_value = True
+ agent.set_device_state = mock.Mock()
+ resync_needed = agent.treat_devices_added_updated(
+ set(['aa:bb:cc:dd:ee:ff']))
+
+ self.assertFalse(resync_needed)
+ agent.eswitch_mgr.device_exists.assert_called_with('aa:bb:cc:dd:ee:ff',
+ '1:2:3.0')
+ agent.eswitch_mgr.set_device_state.assert_called_with(
+ 'aa:bb:cc:dd:ee:ff',
+ '1:2:3.0',
+ True)
+ self.assertTrue(agent.plugin_rpc.update_device_up.called)
+
+ def test_treat_devices_added_updated_admin_state_up_false(self):
+ agent = self.agent
+ mock_details = {'device': 'aa:bb:cc:dd:ee:ff',
+ 'port_id': 'port123',
+ 'network_id': 'net123',
+ 'admin_state_up': False,
+ 'network_type': 'vlan',
+ 'segmentation_id': 100,
+ 'profile': {'pci_slot': '1:2:3.0'},
+ 'physical_network': 'physnet1'}
+ agent.plugin_rpc = mock.Mock()
+ agent.plugin_rpc.get_devices_details_list.return_value = [mock_details]
+ agent.remove_port_binding = mock.Mock()
+ resync_needed = agent.treat_devices_added_updated(
+ set(['aa:bb:cc:dd:ee:ff']))
+
+ self.assertFalse(resync_needed)
+ self.assertFalse(agent.plugin_rpc.update_device_up.called)
neutron-vpn-agent = neutron.services.vpn.agent:main
neutron-metering-agent = neutron.services.metering.agents.metering_agent:main
neutron-ofagent-agent = neutron.plugins.ofagent.agent.main:main
+ neutron-sriov-nic-agent = neutron.plugins.sriovnicagent.sriov_nic_agent:main
neutron-sanity-check = neutron.cmd.sanity_check:main
neutron.core_plugins =
bigswitch = neutron.plugins.bigswitch.plugin:NeutronRestProxyV2