ML2 mechanism driver for SR-IOV capable NIC based switching, Part 2

This set of changes introduces SRIOV NIC Agent to run with
ML2 mechanism driver for SR-IOV capable NIC based switching.
This is the second part of a 2 part commit.

The review is submitted in two parts:
- Part 1
    The Mechanism Driver to support port binding for SR-IOV virtual
	functions of SRIOV capable switching NICs.

- Part2 (this part)
    The SRIOV NIC Based L2 Agent.
	Use  configurable list of mappings physical_networks to PF
        interfaces and configurable list of mappings PF interfaces
        to list of excluded VFs to get list of Virtual Functions that agent should manage.
	Current implementation supports admin state updates.

Co-authored-by: Samer Deeb <samerd@mellanox.com>
Partially implements: blueprint ml2-sriov-nic-switch

Change-Id: I533ccee067935326d5837f90ba321a962e8dc2a6
This commit is contained in:
Irena Berezovsky 2014-07-16 14:33:42 +03:00
parent 3c037a5869
commit b4b1db30db
14 changed files with 1737 additions and 3 deletions

View File

@ -1,12 +1,31 @@
# 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

View File

@ -0,0 +1,88 @@
# 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)

View File

@ -0,0 +1,32 @@
# 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")

View File

@ -0,0 +1,283 @@
# 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

View File

@ -0,0 +1,148 @@
# 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

View File

@ -0,0 +1,355 @@
# 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()

View File

@ -0,0 +1,364 @@
# 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)

View File

@ -0,0 +1,100 @@
# 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)

View File

@ -0,0 +1,127 @@
# 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)

View File

@ -0,0 +1,217 @@
# 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)

View File

@ -117,6 +117,7 @@ console_scripts =
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