SR-IOV agent can handle ports with same MAC addresses

SR-IOV agent can handle ports with same MAC address (located in
different networks). The agent can retrieve, from the system, the
MAC address and the PCI slot; because the PCI slot is unique per
port in the same host, this parameter is used to match with the
Neutron port ID stored in the database (published via RPC).

RPC API bumped to version 1.9.

Closes-Bug: #1791159

Change-Id: Id8c3e0485bebc55c778ecaadaabca1c28ec56205
This commit is contained in:
Rodolfo Alonso Hernandez 2021-03-09 17:46:47 +00:00 committed by Rodolfo Alonso
parent 7e98d18927
commit 77ac42d2ee
13 changed files with 344 additions and 398 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
from datetime import datetime from datetime import datetime
import itertools import itertools
@ -38,6 +39,7 @@ from neutron import objects
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
BINDING_DEACTIVATE = 'binding_deactivate' BINDING_DEACTIVATE = 'binding_deactivate'
DeviceInfo = collections.namedtuple('DeviceInfo', 'mac pci_slot')
def create_consumers(endpoints, prefix, topic_details, start_listening=True): def create_consumers(endpoints, prefix, topic_details, start_listening=True):
@ -118,19 +120,26 @@ class PluginApi(object):
1.7 - Support get_ports_by_vnic_type_and_host 1.7 - Support get_ports_by_vnic_type_and_host
1.8 - Rename agent_restarted to refresh_tunnels in 1.8 - Rename agent_restarted to refresh_tunnels in
update_device_list to reflect its expanded purpose update_device_list to reflect its expanded purpose
1.9 - Support for device definition as DeviceInfo(mac, pci_info) for:
- get_device_details
- get_devices_details_list (indirectly, calls get_device_details)
- update_device_down
- update_device_up
- update_device_list (indirectly, called from update_device_down
and update_device_up)
''' '''
def __init__(self, topic): def __init__(self, topic):
target = oslo_messaging.Target(topic=topic, version='1.0') target = oslo_messaging.Target(topic=topic, version='1.9')
self.client = lib_rpc.get_client(target) self.client = lib_rpc.get_client(target)
def get_device_details(self, context, device, agent_id, host=None): def get_device_details(self, context, device, agent_id, host=None):
cctxt = self.client.prepare() cctxt = self.client.prepare(version='1.9')
return cctxt.call(context, 'get_device_details', device=device, return cctxt.call(context, 'get_device_details', device=device,
agent_id=agent_id, host=host) agent_id=agent_id, host=host)
def get_devices_details_list(self, context, devices, agent_id, host=None): def get_devices_details_list(self, context, devices, agent_id, host=None):
cctxt = self.client.prepare(version='1.3') cctxt = self.client.prepare(version='1.9')
return cctxt.call(context, 'get_devices_details_list', return cctxt.call(context, 'get_devices_details_list',
devices=devices, agent_id=agent_id, host=host) devices=devices, agent_id=agent_id, host=host)
@ -155,18 +164,18 @@ class PluginApi(object):
agent_id=agent_id, host=host) agent_id=agent_id, host=host)
def update_device_down(self, context, device, agent_id, host=None): def update_device_down(self, context, device, agent_id, host=None):
cctxt = self.client.prepare() cctxt = self.client.prepare(version='1.9')
return cctxt.call(context, 'update_device_down', device=device, return cctxt.call(context, 'update_device_down', device=device,
agent_id=agent_id, host=host) agent_id=agent_id, host=host)
def update_device_up(self, context, device, agent_id, host=None): def update_device_up(self, context, device, agent_id, host=None):
cctxt = self.client.prepare() cctxt = self.client.prepare(version='1.9')
return cctxt.call(context, 'update_device_up', device=device, return cctxt.call(context, 'update_device_up', device=device,
agent_id=agent_id, host=host) agent_id=agent_id, host=host)
def update_device_list(self, context, devices_up, devices_down, def update_device_list(self, context, devices_up, devices_down,
agent_id, host, refresh_tunnels=False): agent_id, host, refresh_tunnels=False):
cctxt = self.client.prepare(version='1.8') cctxt = self.client.prepare(version='1.9')
ret_devices_up = [] ret_devices_up = []
failed_devices_up = [] failed_devices_up = []

View File

@ -14,6 +14,7 @@
import netaddr import netaddr
from neutron_lib import constants from neutron_lib import constants
from neutron_lib.db import api as db_api
from neutron_lib.objects import common_types from neutron_lib.objects import common_types
from neutron_lib.utils import net as net_utils from neutron_lib.utils import net as net_utils
from oslo_log import log as logging from oslo_log import log as logging
@ -688,3 +689,19 @@ class Port(base.NeutronDbObject):
return context.session.query(models_v2.Port).filter( return context.session.query(models_v2.Port).filter(
models_v2.IPAllocation.port_id == models_v2.Port.id).filter( models_v2.IPAllocation.port_id == models_v2.Port.id).filter(
models_v2.IPAllocation.subnet_id == subnet_id).all() models_v2.IPAllocation.subnet_id == subnet_id).all()
@classmethod
def get_port_from_mac_and_pci_slot(cls, context, device_mac,
pci_slot=None):
with db_api.CONTEXT_READER.using(context):
ports = cls.get_objects(context, mac_address=device_mac)
if not ports:
return
elif not pci_slot:
return ports.pop()
else:
for port in ports:
for _binding in port.bindings:
if _binding.get('profile', {}).get('pci_slot') == pci_slot:
return port

View File

@ -143,13 +143,6 @@ def get_port(context, port_id):
return return
@db_api.CONTEXT_READER
def get_port_from_device_mac(context, device_mac):
LOG.debug("get_port_from_device_mac() called for mac %s", device_mac)
ports = port_obj.Port.get_objects(context, mac_address=device_mac)
return ports.pop() if ports else None
def get_ports_and_sgs(context, port_ids): def get_ports_and_sgs(context, port_ids):
"""Get ports from database with security group info.""" """Get ports from database with security group info."""

View File

@ -21,6 +21,7 @@ from neutron_lib.utils import helpers
from oslo_log import log as logging from oslo_log import log as logging
from neutron._i18n import _ from neutron._i18n import _
from neutron.agent import rpc as agent_rpc
from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ from neutron.plugins.ml2.drivers.mech_sriov.agent.common \
import exceptions as exc import exceptions as exc
from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib
@ -185,7 +186,8 @@ class EmbSwitch(object):
for pci_slot, vf_index in self.pci_slot_map.items(): for pci_slot, vf_index in self.pci_slot_map.items():
mac = self.get_pci_device(pci_slot) mac = self.get_pci_device(pci_slot)
if mac: if mac:
assigned_devices_info.append((mac, pci_slot)) assigned_devices_info.append(
agent_rpc.DeviceInfo(mac, pci_slot))
return assigned_devices_info return assigned_devices_info
def get_device_state(self, pci_slot): def get_device_state(self, pci_slot):

View File

@ -88,12 +88,9 @@ class SriovNicSwitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
# notifications there is no guarantee the notifications are # notifications there is no guarantee the notifications are
# processed in the same order as the relevant API requests. # processed in the same order as the relevant API requests.
mac = port['mac_address'] mac = port['mac_address']
pci_slot = None pci_slot = port.get(portbindings.PROFILE, {}).get('pci_slot')
if port.get(portbindings.PROFILE):
pci_slot = port[portbindings.PROFILE].get('pci_slot')
if pci_slot: if pci_slot:
self.agent.updated_devices.add((mac, pci_slot)) self.agent.updated_devices.add(agent_rpc.DeviceInfo(mac, pci_slot))
LOG.debug("port_update RPC received for port: %(id)s with MAC " LOG.debug("port_update RPC received for port: %(id)s with MAC "
"%(mac)s and PCI slot %(pci_slot)s slot", "%(mac)s and PCI slot %(pci_slot)s slot",
{'id': port['id'], 'mac': mac, 'pci_slot': pci_slot}) {'id': port['id'], 'mac': mac, 'pci_slot': pci_slot})
@ -277,97 +274,98 @@ class SriovNicSwitchAgent(object):
# If one of the above operations fails => resync with plugin # If one of the above operations fails => resync with plugin
return (resync_a | resync_b) return (resync_a | resync_b)
def treat_device(self, device, pci_slot, admin_state_up, spoofcheck=True, def treat_device(self, device_info, admin_state_up, spoofcheck=True,
propagate_uplink_state=False): propagate_uplink_state=False):
if self.eswitch_mgr.device_exists(device, pci_slot): if self.eswitch_mgr.device_exists(device_info.mac,
device_info.pci_slot):
try: try:
self.eswitch_mgr.set_device_spoofcheck(device, pci_slot, self.eswitch_mgr.set_device_spoofcheck(
spoofcheck) device_info.mac, device_info.pci_slot, spoofcheck)
except Exception: except Exception:
LOG.warning("Failed to set spoofcheck for device %s", LOG.warning("Failed to set spoofcheck for device %s",
device) device_info)
LOG.info("Device %(device)s spoofcheck %(spoofcheck)s", LOG.info("Device %(device)s spoofcheck %(spoofcheck)s",
{"device": device, "spoofcheck": spoofcheck}) {"device": device_info, "spoofcheck": spoofcheck})
try: try:
self.eswitch_mgr.set_device_state(device, pci_slot, self.eswitch_mgr.set_device_state(
admin_state_up, device_info.mac, device_info.pci_slot, admin_state_up,
propagate_uplink_state) propagate_uplink_state)
except priv_ip_lib.InterfaceOperationNotSupported: except priv_ip_lib.InterfaceOperationNotSupported:
LOG.warning("Device %s does not support state change", device) LOG.warning("Device %s does not support state change",
device_info)
except pyroute2.NetlinkError: except pyroute2.NetlinkError:
LOG.warning("Failed to set device %s state", device) LOG.warning("Failed to set device %s state", device_info)
return False return False
else: else:
LOG.info("No device with MAC %s defined on agent.", device) LOG.info("No device %s defined on agent.", device_info)
return False return False
return True return True
def _update_network_ports(self, network_id, port_id, mac_pci_slot): def _update_network_ports(self, network_id, port_id, device):
self._clean_network_ports(mac_pci_slot) self._clean_network_ports(device)
self.network_ports[network_id].append({ self.network_ports[network_id].append({
"port_id": port_id, "port_id": port_id,
"device": mac_pci_slot}) "device": device})
def _clean_network_ports(self, mac_pci_slot): def _clean_network_ports(self, device_to_clean):
for netid, ports_list in self.network_ports.items(): for netid, ports_list in dict(self.network_ports).items():
for port_data in ports_list: for port_data in list(ports_list):
if mac_pci_slot == port_data['device']: if device_to_clean == port_data['device']:
ports_list.remove(port_data) ports_list.remove(port_data)
if ports_list == []: if not ports_list:
self.network_ports.pop(netid) self.network_ports.pop(netid)
return port_data['port_id'] return port_data['port_id']
def treat_devices_added_updated(self, devices_info): def treat_devices_added_updated(self, devices_info):
try: try:
macs_list = set([device_info[0] for device_info in devices_info]) rpc_devices_details = self.plugin_rpc.get_devices_details_list(
devices_details_list = self.plugin_rpc.get_devices_details_list( self.context, devices_info, self.agent_id, self.conf.host)
self.context, macs_list, self.agent_id, self.conf.host)
except Exception as e: except Exception as e:
LOG.debug("Unable to get port details for devices " LOG.debug("Unable to get port details for devices "
"with MAC addresses %(devices)s: %(e)s", "with MAC addresses %(devices)s: %(e)s",
{'devices': macs_list, 'e': e}) {'devices': devices_info, 'e': e})
# resync is needed # resync is needed
return True return True
devices_up = set() devices_up = set()
devices_down = set() devices_down = set()
resync = False resync = False
for device_details in devices_details_list: for device_details in rpc_devices_details:
device = device_details['device'] mac_address = device_details['device']
LOG.debug("Port with MAC address %s is added", device) LOG.debug("Port with MAC address %s is added", mac_address)
if 'port_id' in device_details: if 'port_id' in device_details:
LOG.info("Port %(device)s updated. Details: %(details)s", LOG.info("Port %(device)s updated. Details: %(details)s",
{'device': device, 'details': device_details}) {'device': mac_address, 'details': device_details})
port_id = device_details['port_id'] port_id = device_details['port_id']
profile = device_details['profile'] profile = device_details['profile']
device_info = agent_rpc.DeviceInfo(mac_address,
profile.get('pci_slot'))
spoofcheck = device_details.get('port_security_enabled', True) spoofcheck = device_details.get('port_security_enabled', True)
if self.treat_device( if self.treat_device(
device, device_info,
profile.get('pci_slot'),
device_details['admin_state_up'], device_details['admin_state_up'],
spoofcheck, spoofcheck,
device_details['propagate_uplink_status']): device_details['propagate_uplink_status']):
if device_details['admin_state_up']: if device_details['admin_state_up']:
devices_up.add(device) devices_up.add(device_info)
else: else:
devices_down.add(device) devices_down.add(device_info)
else: else:
resync = True resync = True
self._update_network_ports(device_details['network_id'], self._update_network_ports(device_details['network_id'],
port_id, port_id, device_info)
(device, profile.get('pci_slot')))
self.ext_manager.handle_port(self.context, device_details) self.ext_manager.handle_port(self.context, device_details)
elif n_constants.NO_ACTIVE_BINDING in device_details: elif n_constants.NO_ACTIVE_BINDING in device_details:
# Port was added but its binding in this agent # Port was added but its binding in this agent
# hasn't been activated yet. It will be treated as # hasn't been activated yet. It will be treated as
# added when binding is activated # added when binding is activated
LOG.info("Device with MAC %s has no active binding in host", LOG.info("Device with MAC %s has no active binding in host",
device) mac_address)
else: else:
LOG.info("Device with MAC %s not defined on plugin", LOG.info("Device with MAC %s not defined on plugin",
device) mac_address)
self.plugin_rpc.update_device_list(self.context, self.plugin_rpc.update_device_list(self.context,
devices_up, devices_up,
devices_down, devices_down,
@ -378,39 +376,32 @@ class SriovNicSwitchAgent(object):
def treat_devices_removed(self, devices): def treat_devices_removed(self, devices):
resync = False resync = False
for device in devices: for device in devices:
mac, pci_slot = device
LOG.info("Removing device with MAC address %(mac)s and " LOG.info("Removing device with MAC address %(mac)s and "
"PCI slot %(pci_slot)s", "PCI slot %(pci_slot)s",
{'mac': mac, 'pci_slot': pci_slot}) {'mac': device.mac, 'pci_slot': device.pci_slot})
try: try:
port_id = self._clean_network_ports(device) port_id = self._clean_network_ports(device)
if port_id: if port_id:
port = {'port_id': port_id, port = {'port_id': port_id,
'device': mac, 'device': device.mac,
'profile': {'pci_slot': pci_slot}} 'profile': {'pci_slot': device.pci_slot}}
self.ext_manager.delete_port(self.context, port) self.ext_manager.delete_port(self.context, port)
else: else:
LOG.warning("port_id to device with MAC " LOG.warning("port_id to device %s not found", device)
"%s not found", mac)
dev_details = self.plugin_rpc.update_device_down(self.context, dev_details = self.plugin_rpc.update_device_down(self.context,
mac, device,
self.agent_id, self.agent_id,
cfg.CONF.host) cfg.CONF.host)
except Exception as e: except Exception as e:
LOG.debug("Removing port failed for device with MAC address " LOG.debug("Removing port failed for device %(device)s due to "
"%(mac)s and PCI slot %(pci_slot)s due to %(exc)s", "%(exc)s", {'device': device, 'exc': e})
{'mac': mac, 'pci_slot': pci_slot, 'exc': e})
resync = True resync = True
continue continue
if dev_details['exists']: if dev_details['exists']:
LOG.info("Port with MAC %(mac)s and PCI slot " LOG.info("Port from device %s updated", device)
"%(pci_slot)s updated.",
{'mac': mac, 'pci_slot': pci_slot})
else: else:
LOG.debug("Device with MAC %(mac)s and PCI slot " LOG.debug("Device %s not defined on plugin", device)
"%(pci_slot)s not defined on plugin",
{'mac': mac, 'pci_slot': pci_slot})
return resync return resync
def process_activated_bindings(self, device_info, activated_bindings_copy): def process_activated_bindings(self, device_info, activated_bindings_copy):

View File

@ -2251,7 +2251,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return ports return ports
@staticmethod @staticmethod
def _device_to_port_id(context, device): def _device_to_port_id(context, device, pci_slot=None):
# REVISIT(rkukura): Consider calling into MechanismDrivers to # REVISIT(rkukura): Consider calling into MechanismDrivers to
# process device names, or having MechanismDrivers supply list # process device names, or having MechanismDrivers supply list
# of device prefixes to strip. # of device prefixes to strip.
@ -2261,7 +2261,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# REVISIT(irenab): Consider calling into bound MD to # REVISIT(irenab): Consider calling into bound MD to
# handle the get_device_details RPC # handle the get_device_details RPC
if not uuidutils.is_uuid_like(device): if not uuidutils.is_uuid_like(device):
port = db.get_port_from_device_mac(context, device) port = ports_obj.Port.get_port_from_mac_and_pci_slot(
context, device, pci_slot=pci_slot)
if port: if port:
return port.id return port.id
return device return device

View File

@ -57,8 +57,14 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
# 1.7 Support get_ports_by_vnic_type_and_host # 1.7 Support get_ports_by_vnic_type_and_host
# 1.8 Rename agent_restarted to refresh_tunnels in # 1.8 Rename agent_restarted to refresh_tunnels in
# update_device_list to reflect its expanded purpose # update_device_list to reflect its expanded purpose
# 1.9 Support for device definition as DeviceInfo(mac, pci_info) for:
target = oslo_messaging.Target(version='1.8') # - get_device_details
# - get_devices_details_list (indirectly, calls get_device_details)
# - update_device_down
# - update_device_up
# - update_device_list (indirectly, called from update_device_down
# and update_device_up)
target = oslo_messaging.Target(version='1.9')
def __init__(self, notifier, type_manager): def __init__(self, notifier, type_manager):
self.setup_tunnel_callback_mixin(notifier, type_manager) self.setup_tunnel_callback_mixin(notifier, type_manager)
@ -78,6 +84,14 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
kwargs.get('host'), kwargs.get('host'),
kwargs.get('device') or kwargs.get('network')) kwargs.get('device') or kwargs.get('network'))
@staticmethod
def _device_to_mac_pci_slot(device):
"""This method will keep backwards compatibility with agents < 1.9"""
# NOTE(ralonsoh): this method can be removed in Z release.
if isinstance(device, list): # RPC loads from agent_rpc.DeviceInfo
return device[0], device[1]
return device, None
def get_device_details(self, rpc_context, **kwargs): def get_device_details(self, rpc_context, **kwargs):
"""Agent requests device details.""" """Agent requests device details."""
agent_id, host, device = self._get_request_details(kwargs) agent_id, host, device = self._get_request_details(kwargs)
@ -90,7 +104,9 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
{'device': device, 'agent_id': agent_id, 'host': host}) {'device': device, 'agent_id': agent_id, 'host': host})
plugin = directory.get_plugin() plugin = directory.get_plugin()
port_id = plugin._device_to_port_id(rpc_context, device) mac_or_port_id, pci_slot = self._device_to_mac_pci_slot(device)
port_id = plugin._device_to_port_id(rpc_context, mac_or_port_id,
pci_slot=pci_slot)
port_context = plugin.get_bound_port_context(rpc_context, port_context = plugin.get_bound_port_context(rpc_context,
port_id, port_id,
host, host,
@ -98,8 +114,8 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
if not port_context: if not port_context:
LOG.debug("Device %(device)s requested by agent " LOG.debug("Device %(device)s requested by agent "
"%(agent_id)s not found in database", "%(agent_id)s not found in database",
{'device': device, 'agent_id': agent_id}) {'device': mac_or_port_id, 'agent_id': agent_id})
return {'device': device} return {'device': mac_or_port_id}
port = port_context.current port = port_context.current
# caching information about networks for future use # caching information about networks for future use
@ -108,7 +124,7 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
cached_networks[port['network_id']] = ( cached_networks[port['network_id']] = (
port_context.network.current) port_context.network.current)
result = self._get_device_details(rpc_context, agent_id=agent_id, result = self._get_device_details(rpc_context, agent_id=agent_id,
host=host, device=device, host=host, device=mac_or_port_id,
port_context=port_context) port_context=port_context)
if 'network_id' in result: if 'network_id' in result:
# success so we update status # success so we update status
@ -244,13 +260,15 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
"%(agent_id)s", "%(agent_id)s",
{'device': device, 'agent_id': agent_id}) {'device': device, 'agent_id': agent_id})
plugin = directory.get_plugin() plugin = directory.get_plugin()
port_id = plugin._device_to_port_id(rpc_context, device) mac_or_device, pci_slot = self._device_to_mac_pci_slot(device)
port_id = plugin._device_to_port_id(rpc_context, mac_or_device,
pci_slot=pci_slot)
port_exists = True port_exists = True
if (host and not plugin.port_bound_to_host(rpc_context, if (host and not plugin.port_bound_to_host(rpc_context,
port_id, host)): port_id, host)):
LOG.debug("Device %(device)s not bound to the" LOG.debug("Device %(device)s not bound to the"
" agent host %(host)s", " agent host %(host)s",
{'device': device, 'host': host}) {'device': mac_or_device, 'host': host})
else: else:
try: try:
port_exists = bool(plugin.update_port_status( port_exists = bool(plugin.update_port_status(
@ -259,12 +277,12 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
port_exists = False port_exists = False
LOG.debug("delete_port and update_device_down are being " LOG.debug("delete_port and update_device_down are being "
"executed concurrently. Ignoring StaleDataError.") "executed concurrently. Ignoring StaleDataError.")
return {'device': device, return {'device': mac_or_device,
'exists': port_exists} 'exists': port_exists}
self.notify_l2pop_port_wiring(port_id, rpc_context, self.notify_l2pop_port_wiring(port_id, rpc_context,
n_const.PORT_STATUS_DOWN, host) n_const.PORT_STATUS_DOWN, host)
return {'device': device, return {'device': mac_or_device,
'exists': port_exists} 'exists': port_exists}
@profiler.trace("rpc") @profiler.trace("rpc")
@ -278,12 +296,14 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
LOG.debug("Device %(device)s up at agent %(agent_id)s", LOG.debug("Device %(device)s up at agent %(agent_id)s",
{'device': device, 'agent_id': agent_id}) {'device': device, 'agent_id': agent_id})
plugin = directory.get_plugin() plugin = directory.get_plugin()
port_id = plugin._device_to_port_id(rpc_context, device) mac_or_device, pci_slot = self._device_to_mac_pci_slot(device)
port_id = plugin._device_to_port_id(rpc_context, mac_or_device,
pci_slot=pci_slot)
port = plugin.port_bound_to_host(rpc_context, port_id, host) port = plugin.port_bound_to_host(rpc_context, port_id, host)
if host and not port: if host and not port:
LOG.debug("Device %(device)s not bound to the" LOG.debug("Device %(device)s not bound to the"
" agent host %(host)s", " agent host %(host)s",
{'device': device, 'host': host}) {'device': mac_or_device, 'host': host})
# this might mean that a VM is in the process of live migration # this might mean that a VM is in the process of live migration
# and vif was plugged on the destination compute node; # and vif was plugged on the destination compute node;
# need to notify nova explicitly # need to notify nova explicitly

View File

@ -13,6 +13,7 @@
from unittest import mock from unittest import mock
import netaddr import netaddr
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants from neutron_lib import constants
from neutron_lib.tests import tools from neutron_lib.tests import tools
from oslo_utils import uuidutils from oslo_utils import uuidutils
@ -609,3 +610,34 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
(0, 1), (0, 1),
(len(dhcp_ports), count), (len(dhcp_ports), count),
) )
def test_get_port_from_mac_and_pci_slot_no_ports(self):
self.assertIsNone(
ports.Port.get_port_from_mac_and_pci_slot(self.context,
'ca:fe:ca:fe:ca:fe'))
def test_get_port_from_mac_and_pci_slot_no_pci_slot(self):
obj = self._make_object(self.obj_fields[0])
obj.create()
mac_address = obj.mac_address
port = ports.Port.get_port_from_mac_and_pci_slot(self.context,
mac_address)
self.assertEqual(obj.id, port.id)
def test_get_port_from_mac_and_pci_slot(self):
obj = self._make_object(self.obj_fields[0])
obj.create()
mac_address = obj.mac_address
pci_slot = '0000:04:00.1'
port = ports.Port.get_port_from_mac_and_pci_slot(
self.context, mac_address, pci_slot=pci_slot)
self.assertIsNone(port)
port_binding = ports.PortBinding(
self.context, port_id=obj.id, host='any_host',
vif_type=portbindings.VIF_TYPE_OTHER,
vnic_type=portbindings.VNIC_DIRECT, profile={'pci_slot': pci_slot})
port_binding.create()
port = ports.Port.get_port_from_mac_and_pci_slot(
self.context, mac_address, pci_slot=pci_slot)
self.assertEqual(obj.id, port.id)

View File

@ -16,9 +16,11 @@
import os import os
from unittest import mock from unittest import mock
from neutron.agent import rpc as agent_rpc
from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ from neutron.plugins.ml2.drivers.mech_sriov.agent.common \
import exceptions as exc import exceptions as exc
from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm
from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib
from neutron.tests import base from neutron.tests import base
@ -429,51 +431,38 @@ class TestEmbSwitch(base.BaseTestCase):
return_value=self.SCANNED_DEVICES): return_value=self.SCANNED_DEVICES):
self.emb_switch = esm.EmbSwitch(self.DEV_NAME, exclude_devices) self.emb_switch = esm.EmbSwitch(self.DEV_NAME, exclude_devices)
@mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." def test_get_assigned_devices_info(self):
"eswitch_manager.PciOsWrapper.scan_vf_devices", with mock.patch.object(pci_lib.PciDeviceIPWrapper, 'get_assigned_macs',
return_value=[(PCI_SLOT, 0)]) return_value={0: self.ASSIGNED_MAC}), \
def test_get_assigned_devices_info(self, *args): mock.patch.object(esm.PciOsWrapper, 'pf_device_exists',
emb_switch = esm.EmbSwitch(self.DEV_NAME, ()) return_value=True), \
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_direct',
"PciDeviceIPWrapper.get_assigned_macs", return_value=True):
return_value={0: self.ASSIGNED_MAC}),\ result = self.emb_switch.get_assigned_devices_info()
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." device_info = agent_rpc.DeviceInfo(self.ASSIGNED_MAC,
"eswitch_manager.PciOsWrapper.pf_device_exists", self.PCI_SLOT)
return_value=True), \ self.assertEqual(1, len(result))
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." self.assertEqual(device_info, result[0])
"eswitch_manager.PciOsWrapper."
"is_assigned_vf_direct", return_value=True):
result = emb_switch.get_assigned_devices_info()
self.assertIn(self.ASSIGNED_MAC, list(result)[0])
self.assertIn(self.PCI_SLOT, list(result)[0])
@mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." def test_get_assigned_devices_info_multiple_slots(self):
"eswitch_manager.PciOsWrapper.scan_vf_devices", with mock.patch.object(pci_lib.PciDeviceIPWrapper, 'get_assigned_macs',
return_value=SCANNED_DEVICES) return_value=self.VF_TO_MAC_MAPPING), \
def test_get_assigned_devices_info_multiple_slots(self, *args): mock.patch.object(esm.PciOsWrapper, 'pf_device_exists',
emb_switch = esm.EmbSwitch(self.DEV_NAME, ()) return_value=True), \
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_direct',
"PciDeviceIPWrapper.get_assigned_macs", return_value=True):
return_value=self.VF_TO_MAC_MAPPING),\ devices_info = self.emb_switch.get_assigned_devices_info()
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.PciOsWrapper."
"is_assigned_vf_direct", return_value=True):
devices_info = emb_switch.get_assigned_devices_info()
for device_info in devices_info: for device_info in devices_info:
mac = device_info[0] self.assertEqual(self.EXPECTED_MAC_TO_PCI[device_info.mac],
pci_slot = device_info[1] device_info.pci_slot)
self.assertEqual(
self.EXPECTED_MAC_TO_PCI[mac], pci_slot)
def test_get_assigned_devices_empty(self): def test_get_assigned_devices_empty(self):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." with mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_direct',
"eswitch_manager.PciOsWrapper.is_assigned_vf_direct", return_value=False), \
return_value=False), \ mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_macvtap',
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." return_value=False):
"eswitch_manager.PciOsWrapper."
"is_assigned_vf_macvtap", return_value=False):
result = self.emb_switch.get_assigned_devices_info() result = self.emb_switch.get_assigned_devices_info()
self.assertFalse(result) self.assertEqual([], result)
def test_get_device_state_ok(self): def test_get_device_state_ok(self):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
from unittest import mock from unittest import mock
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
@ -30,6 +31,30 @@ from neutron.tests import base
DEVICE_MAC = '11:22:33:44:55:66' DEVICE_MAC = '11:22:33:44:55:66'
PCI_SLOT = "0000:06:00.1" PCI_SLOT = "0000:06:00.1"
DEV1 = agent_rpc.DeviceInfo('mac1', 'pci_slot1')
DEV2 = agent_rpc.DeviceInfo('mac2', 'pci_slot2')
DEV3 = agent_rpc.DeviceInfo('mac3', 'pci_slot3')
DEV4 = agent_rpc.DeviceInfo('mac4', 'pci_slot4')
RPC_DEV1 = {'device': DEV1.mac,
'port_id': 'port123',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': DEV1.pci_slot},
'physical_network': 'physnet1',
'port_security_enabled': False}
RPC_DEV2 = {'device': DEV2.mac,
'port_id': 'port321',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': DEV2.pci_slot},
'physical_network': 'physnet1',
'port_security_enabled': False}
class TestSriovAgent(base.BaseTestCase): class TestSriovAgent(base.BaseTestCase):
@ -78,7 +103,7 @@ class TestSriovAgent(base.BaseTestCase):
def test_treat_devices_removed_with_existed_device(self, *args): def test_treat_devices_removed_with_existed_device(self, *args):
agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {})
devices = [(DEVICE_MAC, PCI_SLOT)] devices = {agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)}
with mock.patch.object(agent.plugin_rpc, with mock.patch.object(agent.plugin_rpc,
"update_device_down") as fn_udd: "update_device_down") as fn_udd:
fn_udd.return_value = {'device': DEVICE_MAC, fn_udd.return_value = {'device': DEVICE_MAC,
@ -89,7 +114,7 @@ class TestSriovAgent(base.BaseTestCase):
def test_treat_devices_removed_with_not_existed_device(self, *args): def test_treat_devices_removed_with_not_existed_device(self, *args):
agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {})
devices = [(DEVICE_MAC, PCI_SLOT)] devices = {agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)}
with mock.patch.object(agent.plugin_rpc, with mock.patch.object(agent.plugin_rpc,
"update_device_down") as fn_udd: "update_device_down") as fn_udd:
fn_udd.return_value = {'device': DEVICE_MAC, fn_udd.return_value = {'device': DEVICE_MAC,
@ -103,7 +128,7 @@ class TestSriovAgent(base.BaseTestCase):
def test_treat_devices_removed_failed(self, *args): def test_treat_devices_removed_failed(self, *args):
agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {})
devices = [(DEVICE_MAC, PCI_SLOT)] devices = {agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)}
with mock.patch.object(agent.plugin_rpc, with mock.patch.object(agent.plugin_rpc,
"update_device_down") as fn_udd: "update_device_down") as fn_udd:
fn_udd.side_effect = Exception() fn_udd.side_effect = Exception()
@ -134,51 +159,51 @@ class TestSriovAgent(base.BaseTestCase):
self.mock_scan_devices(expected, mock_current, registered, updated) self.mock_scan_devices(expected, mock_current, registered, updated)
def test_scan_devices_no_changes(self): def test_scan_devices_no_changes(self):
registered = set(['1', '2']) registered = {DEV1, DEV2}
updated = set() updated = set()
mock_current = set(['1', '2']) mock_current = {DEV1, DEV2}
expected = {'current': set(['1', '2']), expected = {'current': {DEV1, DEV2},
'updated': set(), 'updated': set(),
'added': set(), 'added': set(),
'removed': set()} 'removed': set()}
self.mock_scan_devices(expected, mock_current, registered, updated) self.mock_scan_devices(expected, mock_current, registered, updated)
def test_scan_devices_new_and_removed(self): def test_scan_devices_new_and_removed(self):
registered = set(['1', '2']) registered = {DEV1, DEV2}
updated = set() updated = set()
mock_current = set(['2', '3']) mock_current = {DEV2, DEV3}
expected = {'current': set(['2', '3']), expected = {'current': {DEV2, DEV3},
'updated': set(), 'updated': set(),
'added': set(['3']), 'added': {DEV3},
'removed': set(['1'])} 'removed': {DEV1}}
self.mock_scan_devices(expected, mock_current, registered, updated) self.mock_scan_devices(expected, mock_current, registered, updated)
def test_scan_devices_updated_and_removed(self): def test_scan_devices_updated_and_removed(self):
registered = set(['1', '2']) registered = {DEV1, DEV2}
# '1' is in removed and updated tuple # 'DEV1' is in removed and updated tuple
updated = set(['1']) updated = {DEV1}
mock_current = set(['2', '3']) mock_current = {DEV2, DEV3}
expected = {'current': set(['2', '3']), expected = {'current': {DEV2, DEV3},
'updated': set(), 'updated': set(),
'added': set(['3']), 'added': {DEV3},
'removed': set(['1'])} 'removed': {DEV1}}
self.mock_scan_devices(expected, mock_current, registered, updated) self.mock_scan_devices(expected, mock_current, registered, updated)
def test_scan_devices_new_updates(self): def test_scan_devices_new_updates(self):
registered = set(['1']) registered = {DEV1}
updated = set(['2']) updated = {DEV2}
mock_current = set(['1', '2']) mock_current = {DEV1, DEV2}
expected = {'current': set(['1', '2']), expected = {'current': {DEV1, DEV2},
'updated': set(['2']), 'updated': {DEV2},
'added': set(['2']), 'added': {DEV2},
'removed': set()} 'removed': set()}
self.mock_scan_devices(expected, mock_current, registered, updated) self.mock_scan_devices(expected, mock_current, registered, updated)
def test_scan_devices_updated_missing(self): def test_scan_devices_updated_missing(self):
registered = set(['1']) registered = {DEV1}
updated = set(['2']) updated = {DEV2}
mock_current = set(['1']) mock_current = {DEV1}
expected = {'current': set(['1']), expected = {'current': {DEV1},
'updated': set(), 'updated': set(),
'added': set(), 'added': set(),
'removed': set()} 'removed': set()}
@ -187,9 +212,9 @@ class TestSriovAgent(base.BaseTestCase):
def test_process_network_devices(self): def test_process_network_devices(self):
agent = self.agent agent = self.agent
device_info = {'current': set(), device_info = {'current': set(),
'added': set(['mac3', 'mac4']), 'added': {DEV3, DEV4},
'updated': set(['mac2', 'mac3']), 'updated': {DEV2, DEV3},
'removed': set(['mac1'])} 'removed': {DEV1}}
agent.sg_agent.prepare_devices_filter = mock.Mock() agent.sg_agent.prepare_devices_filter = mock.Mock()
agent.sg_agent.refresh_firewall = mock.Mock() agent.sg_agent.refresh_firewall = mock.Mock()
agent.treat_devices_added_updated = mock.Mock(return_value=False) agent.treat_devices_added_updated = mock.Mock(return_value=False)
@ -197,226 +222,115 @@ class TestSriovAgent(base.BaseTestCase):
agent.process_network_devices(device_info) agent.process_network_devices(device_info)
agent.sg_agent.prepare_devices_filter.assert_called_with( agent.sg_agent.prepare_devices_filter.assert_called_with({DEV3, DEV4})
set(['mac3', 'mac4']))
self.assertTrue(agent.sg_agent.refresh_firewall.called) self.assertTrue(agent.sg_agent.refresh_firewall.called)
agent.treat_devices_added_updated.assert_called_with(set(['mac2', agent.treat_devices_added_updated.assert_called_with(
'mac3', {DEV2, DEV3, DEV4})
'mac4'])) agent.treat_devices_removed.assert_called_with({DEV1})
agent.treat_devices_removed.assert_called_with(set(['mac1']))
def test_treat_devices_added_updated_sends_host(self): def test_treat_devices_added_updated_sends_host(self):
agent = self.agent agent = self.agent
host = 'host1' host = 'host1'
cfg.CONF.set_override('host', host) cfg.CONF.set_override('host', host)
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
MAC = 'aa:bb:cc:dd:ee:ff' agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1]
device_details = {'device': MAC, devices = {agent_rpc.DeviceInfo(DEV1.mac, DEV1.pci_slot)}
'port_id': 'port123', agent.treat_devices_added_updated(devices)
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': True,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'},
'physical_network': 'physnet1',
'port_security_enabled': False}
agent.plugin_rpc.get_devices_details_list.return_value = (
[device_details])
agent.treat_devices_added_updated([[MAC]])
agent.plugin_rpc.get_devices_details_list.assert_called_once_with( agent.plugin_rpc.get_devices_details_list.assert_called_once_with(
mock.ANY, set([MAC]), mock.ANY, host) agent.context, devices, agent.agent_id, host)
def test_treat_devices_added_updated_and_removed(self): def test_treat_devices_added_updated_and_removed(self):
agent = self.agent agent = self.agent
MAC1 = 'aa:bb:cc:dd:ee:ff'
SLOT1 = '1:2:3.0'
MAC2 = 'aa:bb:cc:dd:ee:fe'
SLOT2 = '1:3:3.0'
mac_pci_slot_device1 = (MAC1, SLOT1)
mac_pci_slot_device2 = (MAC2, SLOT2)
mock_device1_details = {'device': MAC1,
'port_id': 'port123',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': SLOT1},
'physical_network': 'physnet1',
'port_security_enabled': False}
mock_device2_details = {'device': MAC2,
'port_id': 'port124',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': SLOT2},
'physical_network': 'physnet1',
'port_security_enabled': False}
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = ( agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1]
[mock_device1_details]) agent.treat_devices_added_updated({DEV1})
agent.treat_devices_added_updated(set([MAC1])) self.assertEqual({'net123': [{'port_id': 'port123', 'device': DEV1}]},
self.assertEqual({'net123': [{'port_id': 'port123',
'device': mac_pci_slot_device1}]},
agent.network_ports) agent.network_ports)
agent.plugin_rpc.get_devices_details_list.return_value = ( agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV2]
[mock_device2_details])
# add the second device and check the network_ports dict # add the second device and check the network_ports dict
agent.treat_devices_added_updated(set([MAC2])) agent.treat_devices_added_updated({DEV2})
self.assertEqual( self.assertEqual({'net123': [{'port_id': 'port123', 'device': DEV1},
{'net123': [{'port_id': 'port123', {'port_id': 'port321', 'device': DEV2}]},
'device': mac_pci_slot_device1}, {'port_id': 'port124', agent.network_ports)
'device': mac_pci_slot_device2}]}, with mock.patch.object(agent.plugin_rpc, "update_device_down"):
agent.network_ports) agent.treat_devices_removed({DEV2})
with mock.patch.object(agent.plugin_rpc,
"update_device_down"):
agent.treat_devices_removed([mac_pci_slot_device2])
# remove the second device and check the network_ports dict # remove the second device and check the network_ports dict
self.assertEqual({'net123': [{'port_id': 'port123', self.assertEqual({'net123': [{'port_id': 'port123', 'device': DEV1}]},
'device': mac_pci_slot_device1}]},
agent.network_ports) agent.network_ports)
def test_treat_devices_added_updated_admin_state_up_true(self): def test_treat_devices_added_updated_admin_state_up_true(self):
agent = self.agent agent = self.agent
mock_details = {'device': 'aa:bb:cc:dd:ee:ff',
'port_id': 'port123',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'},
'physical_network': 'physnet1',
'port_security_enabled': False}
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1]
agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr = mock.Mock()
agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.device_exists.return_value = True
agent.set_device_state = mock.Mock() agent.set_device_state = mock.Mock()
agent.set_device_spoofcheck = mock.Mock() agent.set_device_spoofcheck = mock.Mock()
resync_needed = agent.treat_devices_added_updated( devices = {DEV1}
set(['aa:bb:cc:dd:ee:ff'])) resync_needed = agent.treat_devices_added_updated(devices)
self.assertFalse(resync_needed) self.assertFalse(resync_needed)
agent.eswitch_mgr.device_exists.assert_called_with('aa:bb:cc:dd:ee:ff', agent.eswitch_mgr.device_exists.assert_called_with(DEV1.mac,
'1:2:3.0') DEV1.pci_slot)
agent.eswitch_mgr.set_device_state.assert_called_with( agent.eswitch_mgr.set_device_state.assert_called_with(
'aa:bb:cc:dd:ee:ff', DEV1.mac, DEV1.pci_slot, True, False)
'1:2:3.0',
True, False)
agent.eswitch_mgr.set_device_spoofcheck.assert_called_with( agent.eswitch_mgr.set_device_spoofcheck.assert_called_with(
'aa:bb:cc:dd:ee:ff', DEV1.mac, DEV1.pci_slot, False)
'1:2:3.0',
False)
agent.plugin_rpc.update_device_list.assert_called_once_with( agent.plugin_rpc.update_device_list.assert_called_once_with(
mock.ANY, agent.context, devices, set(), agent.agent_id, agent.conf.host)
set(['aa:bb:cc:dd:ee:ff']),
set(),
mock.ANY,
mock.ANY)
def test_treat_devices_added_updated_multiple_admin_state_up_true(self): def test_treat_devices_added_updated_multiple_admin_state_up_true(self):
agent = self.agent agent = self.agent
mock_details = [{'device': 'aa:bb:cc:dd:ee:ff', mock_details = [RPC_DEV1, RPC_DEV2]
'port_id': 'port123',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'},
'physical_network': 'physnet1',
'port_security_enabled': False},
{'device': '11:22:33:44:55:66',
'port_id': 'port321',
'network_id': 'net123',
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'},
'physical_network': 'physnet1',
'port_security_enabled': False}]
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = mock_details agent.plugin_rpc.get_devices_details_list.return_value = mock_details
agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr = mock.Mock()
agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.device_exists.return_value = True
agent.set_device_state = mock.Mock() agent.set_device_state = mock.Mock()
agent.set_device_spoofcheck = mock.Mock() agent.set_device_spoofcheck = mock.Mock()
resync_needed = agent.treat_devices_added_updated( devices = {DEV1, DEV2}
set(['aa:bb:cc:dd:ee:ff', resync_needed = agent.treat_devices_added_updated(devices)
'11:22:33:44:55:66']))
self.assertFalse(resync_needed) self.assertFalse(resync_needed)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0'), calls = [mock.call(DEV1.mac, DEV1.pci_slot),
mock.call('11:22:33:44:55:66', '1:2:3.0')] mock.call(DEV2.mac, DEV2.pci_slot)]
agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True) agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True, False), calls = [mock.call(DEV1.mac, DEV1.pci_slot, True, False),
mock.call('11:22:33:44:55:66', '1:2:3.0', True, False)] mock.call(DEV2.mac, DEV2.pci_slot, True, False)]
agent.eswitch_mgr.set_device_state.assert_has_calls(calls, agent.eswitch_mgr.set_device_state.assert_has_calls(calls,
any_order=True) any_order=True)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False), calls = [mock.call(DEV1.mac, DEV1.pci_slot, False),
mock.call('11:22:33:44:55:66', '1:2:3.0', False)] mock.call(DEV2.mac, DEV2.pci_slot, False)]
agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls(calls, agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls(
any_order=True) calls, any_order=True)
agent.plugin_rpc.update_device_list.assert_called_once_with( agent.plugin_rpc.update_device_list.assert_called_once_with(
mock.ANY, agent.context, devices, set(), agent.agent_id, agent.conf.host)
set(['aa:bb:cc:dd:ee:ff', '11:22:33:44:55:66']),
set(),
mock.ANY,
mock.ANY)
def test_treat_devices_added_updated_multiple_admin_states(self): def test_treat_devices_added_updated_multiple_admin_states(self):
agent = self.agent agent = self.agent
mock_details = [{'device': 'aa:bb:cc:dd:ee:ff', rpc_dev2 = copy.deepcopy(RPC_DEV2)
'port_id': 'port123', rpc_dev2['admin_state_up'] = False
'network_id': 'net123', mock_details = [RPC_DEV1, rpc_dev2]
'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'},
'physical_network': 'physnet1',
'port_security_enabled': False},
{'device': '11:22:33:44:55:66',
'port_id': 'port321',
'network_id': 'net123',
'admin_state_up': False,
'propagate_uplink_status': False,
'network_type': 'vlan',
'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'},
'physical_network': 'physnet1',
'port_security_enabled': False}]
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = mock_details agent.plugin_rpc.get_devices_details_list.return_value = mock_details
agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr = mock.Mock()
agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.device_exists.return_value = True
agent.set_device_state = mock.Mock() agent.set_device_state = mock.Mock()
agent.set_device_spoofcheck = mock.Mock() agent.set_device_spoofcheck = mock.Mock()
resync_needed = agent.treat_devices_added_updated( devices = {DEV1, DEV2}
set(['aa:bb:cc:dd:ee:ff', resync_needed = agent.treat_devices_added_updated(devices)
'11:22:33:44:55:66']))
self.assertFalse(resync_needed) self.assertFalse(resync_needed)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0'), calls = [mock.call(DEV1.mac, DEV1.pci_slot),
mock.call('11:22:33:44:55:66', '1:2:3.0')] mock.call(DEV2.mac, DEV2.pci_slot)]
agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True) agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True, False), calls = [mock.call(DEV1.mac, DEV1.pci_slot, True, False),
mock.call('11:22:33:44:55:66', '1:2:3.0', False, False)] mock.call(DEV2.mac, DEV2.pci_slot, False, False)]
agent.eswitch_mgr.set_device_state.assert_has_calls(calls, agent.eswitch_mgr.set_device_state.assert_has_calls(calls,
any_order=True) any_order=True)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False), calls = [mock.call(DEV1.mac, DEV1.pci_slot, False),
mock.call('11:22:33:44:55:66', '1:2:3.0', False)] mock.call(DEV2.mac, DEV2.pci_slot, False)]
agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls(calls, agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls(
any_order=True) calls, any_order=True)
agent.plugin_rpc.update_device_list.assert_called_once_with( agent.plugin_rpc.update_device_list.assert_called_once_with(
mock.ANY, agent.context, {DEV1}, {DEV2}, agent.agent_id, agent.conf.host)
set(['aa:bb:cc:dd:ee:ff']),
set(['11:22:33:44:55:66']),
mock.ANY,
mock.ANY)
def test_treat_device_ip_link_state_not_supported(self): def test_treat_device_ip_link_state_not_supported(self):
agent = self.agent agent = self.agent
@ -426,8 +340,7 @@ class TestSriovAgent(base.BaseTestCase):
agent.eswitch_mgr.set_device_state.side_effect = ( agent.eswitch_mgr.set_device_state.side_effect = (
priv_ip_lib.InterfaceOperationNotSupported()) priv_ip_lib.InterfaceOperationNotSupported())
self.assertTrue(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', self.assertTrue(agent.treat_device(DEV1, admin_state_up=True))
admin_state_up=True))
def test_treat_device_set_device_state_exception(self): def test_treat_device_set_device_state_exception(self):
agent = self.agent agent = self.agent
@ -437,8 +350,7 @@ class TestSriovAgent(base.BaseTestCase):
agent.eswitch_mgr.set_device_state.side_effect = ( agent.eswitch_mgr.set_device_state.side_effect = (
pyroute2.NetlinkError(22)) pyroute2.NetlinkError(22))
self.assertFalse(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', self.assertFalse(agent.treat_device(DEV1, admin_state_up=True))
admin_state_up=True))
def test_treat_device_no_device_found(self): def test_treat_device_no_device_found(self):
agent = self.agent agent = self.agent
@ -446,53 +358,32 @@ class TestSriovAgent(base.BaseTestCase):
agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr = mock.Mock()
agent.eswitch_mgr.device_exists.return_value = False agent.eswitch_mgr.device_exists.return_value = False
self.assertFalse(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', self.assertFalse(agent.treat_device(DEV1, admin_state_up=True))
admin_state_up=True))
def test_treat_devices_added_updated_admin_state_up_false(self): def test_treat_devices_added_updated_admin_state_up_false(self):
agent = self.agent agent = self.agent
mock_details = {'device': 'aa:bb:cc:dd:ee:ff',
'port_id': 'port123',
'network_id': 'net123',
'admin_state_up': False,
'propagate_uplink_status': 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 = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] rpc_dev1 = copy.deepcopy(RPC_DEV1)
rpc_dev1['admin_state_up'] = False
agent.plugin_rpc.get_devices_details_list.return_value = [rpc_dev1]
agent.remove_port_binding = mock.Mock() agent.remove_port_binding = mock.Mock()
agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr = mock.Mock()
agent.eswitch_mgr.device_exists.return_value = True agent.eswitch_mgr.device_exists.return_value = True
resync_needed = agent.treat_devices_added_updated( devices = {DEV1}
set(['aa:bb:cc:dd:ee:ff'])) resync_needed = agent.treat_devices_added_updated(devices)
self.assertFalse(resync_needed) self.assertFalse(resync_needed)
agent.plugin_rpc.update_device_list.assert_called_once_with( agent.plugin_rpc.update_device_list.assert_called_once_with(
mock.ANY, agent.context, set(), devices, agent.agent_id, agent.conf.host)
set(),
set(['aa:bb:cc:dd:ee:ff']),
mock.ANY,
mock.ANY)
def test_treat_devices_added_updated_no_device_found(self): def test_treat_devices_added_updated_no_device_found(self):
agent = self.agent 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'},
'propagate_uplink_status': False,
'physical_network': 'physnet1'}
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1]
agent.remove_port_binding = mock.Mock() agent.remove_port_binding = mock.Mock()
agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr = mock.Mock()
agent.eswitch_mgr.device_exists.return_value = False agent.eswitch_mgr.device_exists.return_value = False
resync_needed = agent.treat_devices_added_updated( devices = {DEV1}
set(['aa:bb:cc:dd:ee:ff'])) resync_needed = agent.treat_devices_added_updated(devices)
self.assertTrue(resync_needed) self.assertTrue(resync_needed)
self.assertFalse(agent.plugin_rpc.update_device_up.called) self.assertFalse(agent.plugin_rpc.update_device_up.called)
@ -502,27 +393,20 @@ class TestSriovAgent(base.BaseTestCase):
port_id1 = 'port_id1' port_id1 = 'port_id1'
port_id2 = 'port_id2' port_id2 = 'port_id2'
mac_slot_1 = ('mac1', 'slot1')
mac_slot_2 = ('mac2', 'slot2')
self.agent.network_ports[network_id1] = [{'port_id': port_id1, self.agent._update_network_ports(network_id1, port_id1, DEV1)
'device': mac_slot_1}, {'port_id': port_id2, 'device': mac_slot_2}] self.agent._update_network_ports(network_id1, port_id2, DEV2)
self.agent._update_network_ports(network_id2, port_id1, mac_slot_1) self.agent._update_network_ports(network_id2, port_id1, DEV1)
expected = {network_id1: [{'port_id': port_id2, 'device': DEV2}],
network_id2: [{'port_id': port_id1, 'device': DEV1}]}
self.assertEqual(expected, dict(self.agent.network_ports))
self.assertEqual({network_id1: [{'port_id': port_id2, self.assertEqual(port_id1, self.agent._clean_network_ports(DEV1))
'device': mac_slot_2}], network_id2: [ expected = {network_id1: [{'port_id': port_id2, 'device': DEV2}]}
{'port_id': port_id1, 'device': mac_slot_1}]}, self.assertEqual(expected, self.agent.network_ports)
self.agent.network_ports)
cleaned_port_id = self.agent._clean_network_ports(mac_slot_1) self.assertEqual(port_id2, self.agent._clean_network_ports(DEV2))
self.assertEqual(cleaned_port_id, port_id1)
self.assertEqual({network_id1: [{'port_id': port_id2,
'device': mac_slot_2}]},
self.agent.network_ports)
cleaned_port_id = self.agent._clean_network_ports(mac_slot_2)
self.assertEqual({}, self.agent.network_ports) self.assertEqual({}, self.agent.network_ports)
def test_configurations_has_rp_bandwidth(self): def test_configurations_has_rp_bandwidth(self):
@ -558,17 +442,12 @@ class TestSriovAgent(base.BaseTestCase):
rp_inv_defaults[inv_key]) rp_inv_defaults[inv_key])
def test_process_activated_bindings(self): def test_process_activated_bindings(self):
# Create several devices which are pairs of (<mac_addr>, <pci_slot>)
dev_a = ('fa:16:3e:f8:ae:af', "0000:01:00.0")
dev_b = ('fa:16:3e:f8:ae:b0', "0000:02:00.0")
dev_c = ('fa:16:3e:f8:ae:b1', "0000:03:00.0")
# Create device_info
fake_device_info = { fake_device_info = {
'current': set([dev_a, dev_b]), 'current': {DEV1, DEV2},
'added': set([dev_c]), 'added': {DEV3},
'removed': set(), 'removed': set(),
'updated': set()} 'updated': set()}
fake_activated_bindings = set([dev_a]) fake_activated_bindings = {DEV1}
self.agent.process_activated_bindings(fake_device_info, self.agent.process_activated_bindings(fake_device_info,
fake_activated_bindings) fake_activated_bindings)
self.assertLessEqual(fake_activated_bindings, self.assertLessEqual(fake_activated_bindings,
@ -592,6 +471,7 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase):
sg_agent = object() sg_agent = object()
self.sriov_rpc_callback = sriov_nic_agent.SriovNicSwitchRpcCallbacks( self.sriov_rpc_callback = sriov_nic_agent.SriovNicSwitchRpcCallbacks(
self.context, self.agent, sg_agent) self.context, self.agent, sg_agent)
self.device_info = agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)
def _create_fake_port(self): def _create_fake_port(self):
return {'id': uuidutils.generate_uuid(), return {'id': uuidutils.generate_uuid(),
@ -606,8 +486,7 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase):
port = self._create_fake_port() port = self._create_fake_port()
kwargs = {'context': self.context, 'port': port} kwargs = {'context': self.context, 'port': port}
self.sriov_rpc_callback.port_update(**kwargs) self.sriov_rpc_callback.port_update(**kwargs)
self.assertEqual(set([(DEVICE_MAC, PCI_SLOT)]), self.assertEqual({self.device_info}, self.agent.updated_devices)
self.agent.updated_devices)
def test_port_update_with_vnic_physical_direct(self): def test_port_update_with_vnic_physical_direct(self):
port = self._create_fake_port() port = self._create_fake_port()
@ -618,7 +497,7 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase):
def test_port_update_without_pci_slot(self): def test_port_update_without_pci_slot(self):
port = self._create_fake_port() port = self._create_fake_port()
port[portbindings.PROFILE] = None port[portbindings.PROFILE] = {}
kwargs = {'context': self.context, 'port': port} kwargs = {'context': self.context, 'port': port}
self.sriov_rpc_callback.port_update(**kwargs) self.sriov_rpc_callback.port_update(**kwargs)
self.assertEqual(set(), self.agent.updated_devices) self.assertEqual(set(), self.agent.updated_devices)

View File

@ -280,16 +280,6 @@ class Ml2DBTestCase(testlib_api.SqlTestCase):
port = ml2_db.get_port(self.ctx, port_id) port = ml2_db.get_port(self.ctx, port_id)
self.assertIsNone(port) self.assertIsNone(port)
def test_get_port_from_device_mac(self):
network_id = uuidutils.generate_uuid()
port_id = uuidutils.generate_uuid()
self._setup_neutron_network(network_id)
port = self._setup_neutron_port(network_id, port_id)
observed_port = ml2_db.get_port_from_device_mac(self.ctx,
port['mac_address'])
self.assertEqual(port_id, observed_port.id)
def test_generating_multiple_mac_addresses(self): def test_generating_multiple_mac_addresses(self):
mac_regex = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" mac_regex = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"

View File

@ -436,7 +436,8 @@ class RpcApiTestCase(base.BaseTestCase):
'get_device_details', rpc_method='call', 'get_device_details', rpc_method='call',
device='fake_device', device='fake_device',
agent_id='fake_agent_id', agent_id='fake_agent_id',
host='fake_host') host='fake_host',
version='1.9')
def test_devices_details_list(self): def test_devices_details_list(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN) rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
@ -444,7 +445,7 @@ class RpcApiTestCase(base.BaseTestCase):
'get_devices_details_list', rpc_method='call', 'get_devices_details_list', rpc_method='call',
devices=['fake_device1', 'fake_device2'], devices=['fake_device1', 'fake_device2'],
agent_id='fake_agent_id', host='fake_host', agent_id='fake_agent_id', host='fake_host',
version='1.3') version='1.9')
def test_update_device_down(self): def test_update_device_down(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN) rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
@ -452,7 +453,8 @@ class RpcApiTestCase(base.BaseTestCase):
'update_device_down', rpc_method='call', 'update_device_down', rpc_method='call',
device='fake_device', device='fake_device',
agent_id='fake_agent_id', agent_id='fake_agent_id',
host='fake_host') host='fake_host',
version='1.9')
def test_tunnel_sync(self): def test_tunnel_sync(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN) rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
@ -469,7 +471,8 @@ class RpcApiTestCase(base.BaseTestCase):
'update_device_up', rpc_method='call', 'update_device_up', rpc_method='call',
device='fake_device', device='fake_device',
agent_id='fake_agent_id', agent_id='fake_agent_id',
host='fake_host') host='fake_host',
version='1.9')
def test_update_device_list(self): def test_update_device_list(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN) rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
@ -480,7 +483,7 @@ class RpcApiTestCase(base.BaseTestCase):
agent_id='fake_agent_id', agent_id='fake_agent_id',
host='fake_host', host='fake_host',
refresh_tunnels=False, refresh_tunnels=False,
version='1.8') version='1.9')
def test_get_devices_details_list_and_failed_devices(self): def test_get_devices_details_list_and_failed_devices(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN) rpcapi = agent_rpc.PluginApi(topics.PLUGIN)

View File

@ -0,0 +1,20 @@
---
features:
- |
SR-IOV agent now can handle ports from different networks with the same
MAC addresses. This feature implies an upgrade in the agent and the server
RPC version (see ``neutron.plugins.ml2.rpc.RpcCallbacks`` version 1.9).
Some agent RPC methods have been updated to pass not only the device MAC
address but the PCI slot too. In case of having more than one port with
the same MAC address, the PCI slot will discriminate the requested port.
upgrade:
- |
Both the server and the agent RPC versions have been bumped to 1.9; to
provide a smooth upgrade transition, the `Upgrade Procedure
<https://docs.openstack.org/operations-guide/ops-upgrades.html>`_ should
be followed, upgrading first the servers and then the agents.
The agent RPC methods returned values are not modified to keep
compatibility with other agents (Linux Bridge, Open vSwitch). The RPC
server side is capable of attending calls from agent API < 1.9, in order to
provide backwards compatibility. If the device PCI slot is not provided,
the behavior will be the previous one.