Add binding activation to OVS agent

As part of the implementation of multiple port bindings [1], add binding
activation support to the OVS agent. This will enable the execution in
OVS agents of the complete sequence of steps outlined in [1] during an
instance migration:

1) Create inactive port bindings for destination host
2) Migrate the instance to the destination host and plug its VIFs
3) Activate the port bindings in the destination host
4) Delete the port bindings for the source host

[1] https://review.openstack.org/#/c/309416/

Change-Id: Iabca39364ec95633b2a8891fc295b3ada5f4f5e0
Partial-Bug: #1580880
This commit is contained in:
Miguel Lavalle 2018-06-09 20:03:39 -05:00
parent 0694bebd6d
commit 5c3bf12496
5 changed files with 193 additions and 41 deletions

View File

@ -35,6 +35,7 @@ from neutron.common import utils
from neutron import objects from neutron import objects
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
BINDING_DEACTIVATE = 'binding_deactivate'
def create_consumers(endpoints, prefix, topic_details, start_listening=True): def create_consumers(endpoints, prefix, topic_details, start_listening=True):
@ -201,14 +202,23 @@ class CacheBackedPluginApi(PluginApi):
the payloads the handlers are expecting (an ID). the payloads the handlers are expecting (an ID).
""" """
rtype = rtype.lower() # all legacy handlers don't camelcase rtype = rtype.lower() # all legacy handlers don't camelcase
method, host = self._get_method_host(rtype, event, **kwargs) method, host_with_activation, host_with_deactivation = (
self._get_method_host(rtype, event, **kwargs))
if not hasattr(self._legacy_interface, method): if not hasattr(self._legacy_interface, method):
# TODO(kevinbenton): once these notifications are stable, emit # TODO(kevinbenton): once these notifications are stable, emit
# a deprecation warning for legacy handlers # a deprecation warning for legacy handlers
return return
payload = {rtype: {'id': resource_id}, '%s_id' % rtype: resource_id, # If there is a binding deactivation, we must also notify the
'host': host} # corresponding activation
getattr(self._legacy_interface, method)(context, **payload) if method == BINDING_DEACTIVATE:
self._legacy_interface.binding_deactivate(
context, port_id=resource_id, host=host_with_deactivation)
self._legacy_interface.binding_activate(
context, port_id=resource_id, host=host_with_activation)
else:
payload = {rtype: {'id': resource_id},
'%s_id' % rtype: resource_id}
getattr(self._legacy_interface, method)(context, **payload)
def _get_method_host(self, rtype, event, **kwargs): def _get_method_host(self, rtype, event, **kwargs):
"""Constructs the name of method to be called in the legacy interface. """Constructs the name of method to be called in the legacy interface.
@ -222,9 +232,10 @@ class CacheBackedPluginApi(PluginApi):
is_delete = event == callback_events.AFTER_DELETE is_delete = event == callback_events.AFTER_DELETE
suffix = 'delete' if is_delete else 'update' suffix = 'delete' if is_delete else 'update'
method = "%s_%s" % (rtype, suffix) method = "%s_%s" % (rtype, suffix)
host = None host_with_activation = None
host_with_deactivation = None
if is_delete or rtype != callback_resources.PORT: if is_delete or rtype != callback_resources.PORT:
return method, host return method, host_with_activation, host_with_deactivation
# A port update was received. Find out if it is a binding activation # A port update was received. Find out if it is a binding activation
# where a previous binding was deactivated # where a previous binding was deactivated
@ -245,9 +256,10 @@ class CacheBackedPluginApi(PluginApi):
getattr(kwargs['updated'], 'bindings', []), getattr(kwargs['updated'], 'bindings', []),
constants.INACTIVE, constants.INACTIVE,
host=existing_active_binding.host)): host=existing_active_binding.host)):
method = 'binding_deactivate' method = BINDING_DEACTIVATE
host = existing_active_binding.host host_with_activation = updated_active_binding.host
return method, host host_with_deactivation = existing_active_binding.host
return method, host_with_activation, host_with_deactivation
def get_devices_details_list_and_failed_devices(self, context, devices, def get_devices_details_list_and_failed_devices(self, context, devices,
agent_id, host=None): agent_id, host=None):
@ -274,15 +286,22 @@ class CacheBackedPluginApi(PluginApi):
if not segment: if not segment:
LOG.debug("Device %s is not bound to any segment.", port_obj) LOG.debug("Device %s is not bound to any segment.", port_obj)
return {'device': device} return {'device': device}
binding = utils.get_port_binding_by_status_and_host(
port_obj.bindings, constants.ACTIVE, raise_if_not_found=True,
port_id=port_obj.id)
if (port_obj.device_owner.startswith(
constants.DEVICE_OWNER_COMPUTE_PREFIX) and
binding[pb_ext.HOST] != host):
LOG.debug("Device %s has no active binding in this host",
port_obj)
return {'device': device,
n_const.NO_ACTIVE_BINDING: True}
net = self.remote_resource_cache.get_resource_by_id( net = self.remote_resource_cache.get_resource_by_id(
resources.NETWORK, port_obj.network_id) resources.NETWORK, port_obj.network_id)
net_qos_policy_id = net.qos_policy_id net_qos_policy_id = net.qos_policy_id
# match format of old RPC interface # match format of old RPC interface
mac_addr = str(netaddr.EUI(str(port_obj.mac_address), mac_addr = str(netaddr.EUI(str(port_obj.mac_address),
dialect=netaddr.mac_unix_expanded)) dialect=netaddr.mac_unix_expanded))
binding = utils.get_port_binding_by_status_and_host(
port_obj.bindings, constants.ACTIVE, raise_if_not_found=True,
port_id=port_obj.id)
entry = { entry = {
'device': device, 'device': device,
'network_id': port_obj.network_id, 'network_id': port_obj.network_id,

View File

@ -227,3 +227,6 @@ VALUES_TYPE_RANGE = "range"
# Units base # Units base
SI_BASE = 1000 SI_BASE = 1000
IEC_BASE = 1024 IEC_BASE = 1024
# Port bindings handling
NO_ACTIVE_BINDING = 'no_active_binding'

View File

@ -55,6 +55,7 @@ from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import dvr_rpc from neutron.api.rpc.handlers import dvr_rpc
from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc
from neutron.common import config from neutron.common import config
from neutron.common import constants as c_const
from neutron.common import utils as n_utils from neutron.common import utils as n_utils
from neutron.conf.agent import xenapi_conf from neutron.conf.agent import xenapi_conf
from neutron.plugins.ml2.drivers.agent import capabilities from neutron.plugins.ml2.drivers.agent import capabilities
@ -124,7 +125,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
# 1.2 Support DVR (Distributed Virtual Router) RPC # 1.2 Support DVR (Distributed Virtual Router) RPC
# 1.3 Added param devices_to_update to security_groups_provider_updated # 1.3 Added param devices_to_update to security_groups_provider_updated
# 1.4 Added support for network_update # 1.4 Added support for network_update
# 1.5 Added binding_deactivate # 1.5 Added binding_activate and binding_deactivate
target = oslo_messaging.Target(version='1.5') target = oslo_messaging.Target(version='1.5')
def __init__(self, bridge_classes, ext_manager, conf=None): def __init__(self, bridge_classes, ext_manager, conf=None):
@ -177,6 +178,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.deleted_ports = set() self.deleted_ports = set()
# Stores the port IDs whose binding has been deactivated # Stores the port IDs whose binding has been deactivated
self.deactivated_bindings = set() self.deactivated_bindings = set()
# Stores the port IDs whose binding has been activated
self.activated_bindings = set()
self.network_ports = collections.defaultdict(set) self.network_ports = collections.defaultdict(set)
# keeps association between ports and ofports to detect ofport change # keeps association between ports and ofports to detect ofport change
@ -423,6 +426,12 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
port_id = kwargs.get('port_id') port_id = kwargs.get('port_id')
self.deactivated_bindings.add(port_id) self.deactivated_bindings.add(port_id)
def binding_activate(self, context, **kwargs):
if kwargs.get('host') != self.conf.host:
return
port_id = kwargs.get('port_id')
self.activated_bindings.add(port_id)
def _clean_network_ports(self, port_id): def _clean_network_ports(self, port_id):
for port_set in self.network_ports.values(): for port_set in self.network_ports.values():
if port_id in port_set: if port_id in port_set:
@ -467,6 +476,12 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
LOG.debug(("Port id %s unplugged from integration bridge because " LOG.debug(("Port id %s unplugged from integration bridge because "
"its binding was de-activated"), port_id) "its binding was de-activated"), port_id)
def process_activated_bindings(self, port_info, activated_bindings_copy):
# Compute which ports for activated bindings are still present...
activated_bindings_copy &= port_info['current']
# ...and treat them as just added
port_info['added'] |= activated_bindings_copy
def tunnel_update(self, context, **kwargs): def tunnel_update(self, context, **kwargs):
LOG.debug("tunnel_update received") LOG.debug("tunnel_update received")
if not self.enable_tunneling: if not self.enable_tunneling:
@ -1534,6 +1549,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
def treat_devices_added_or_updated(self, devices, provisioning_needed): def treat_devices_added_or_updated(self, devices, provisioning_needed):
skipped_devices = [] skipped_devices = []
need_binding_devices = [] need_binding_devices = []
binding_no_activated_devices = set()
devices_details_list = ( devices_details_list = (
self.plugin_rpc.get_devices_details_list_and_failed_devices( self.plugin_rpc.get_devices_details_list_and_failed_devices(
self.context, self.context,
@ -1576,13 +1592,21 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
details['network_id']) details['network_id'])
self.ext_manager.handle_port(self.context, details) self.ext_manager.handle_port(self.context, details)
else: else:
LOG.warning( if c_const.NO_ACTIVE_BINDING in details:
"Device %s not defined on plugin or binding failed", # Port was added to the bridge, but its binding in this
device) # agent hasn't been activated yet. It will be treated as
# added when binding is activated
binding_no_activated_devices.add(device)
LOG.debug("Device %s has no active binding in host",
device)
else:
LOG.warning(
"Device %s not defined on plugin or binding failed",
device)
if (port and port.ofport != -1): if (port and port.ofport != -1):
self.port_dead(port) self.port_dead(port)
return (skipped_devices, need_binding_devices, return (skipped_devices, binding_no_activated_devices,
failed_devices) need_binding_devices, failed_devices)
def _update_port_network(self, port_id, network_id): def _update_port_network(self, port_id, network_id):
self._clean_network_ports(port_id) self._clean_network_ports(port_id)
@ -1673,19 +1697,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
port_info.get('updated', set())) port_info.get('updated', set()))
need_binding_devices = [] need_binding_devices = []
skipped_devices = set() skipped_devices = set()
binding_no_activated_devices = set()
if devices_added_updated: if devices_added_updated:
start = time.time() start = time.time()
(skipped_devices, need_binding_devices, (skipped_devices, binding_no_activated_devices,
failed_devices['added']) = ( need_binding_devices, failed_devices['added']) = (
self.treat_devices_added_or_updated( self.treat_devices_added_or_updated(
devices_added_updated, provisioning_needed)) devices_added_updated, provisioning_needed))
LOG.debug("process_network_ports - iteration:%(iter_num)d - " LOG.debug("process_network_ports - iteration:%(iter_num)d - "
"treat_devices_added_or_updated completed. " "treat_devices_added_or_updated completed. "
"Skipped %(num_skipped)d devices of " "Skipped %(num_skipped)d and no activated binding "
"%(num_current)d devices currently available. " "devices %(num_no_active_binding)d of %(num_current)d "
"devices currently available. "
"Time elapsed: %(elapsed).3f", "Time elapsed: %(elapsed).3f",
{'iter_num': self.iter_num, {'iter_num': self.iter_num,
'num_skipped': len(skipped_devices), 'num_skipped': len(skipped_devices),
'num_no_active_binding':
len(binding_no_activated_devices),
'num_current': len(port_info['current']), 'num_current': len(port_info['current']),
'elapsed': time.time() - start}) 'elapsed': time.time() - start})
# Update the list of current ports storing only those which # Update the list of current ports storing only those which
@ -1695,7 +1723,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
# TODO(salv-orlando): Optimize avoiding applying filters # TODO(salv-orlando): Optimize avoiding applying filters
# unnecessarily, (eg: when there are no IP address changes) # unnecessarily, (eg: when there are no IP address changes)
added_ports = port_info.get('added', set()) - skipped_devices added_ports = (port_info.get('added', set()) - skipped_devices -
binding_no_activated_devices)
self._add_port_tag_info(need_binding_devices) self._add_port_tag_info(need_binding_devices)
self.sg_agent.setup_port_filters(added_ports, self.sg_agent.setup_port_filters(added_ports,
port_info.get('updated', set())) port_info.get('updated', set()))
@ -1810,6 +1839,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.updated_ports or self.updated_ports or
self.deleted_ports or self.deleted_ports or
self.deactivated_bindings or self.deactivated_bindings or
self.activated_bindings or
self.sg_agent.firewall_refresh_needed()) self.sg_agent.firewall_refresh_needed())
def _port_info_has_changes(self, port_info): def _port_info_has_changes(self, port_info):
@ -2031,6 +2061,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
sync = False sync = False
ports = set() ports = set()
updated_ports_copy = set() updated_ports_copy = set()
activated_bindings_copy = set()
ancillary_ports = set() ancillary_ports = set()
tunnel_sync = True tunnel_sync = True
ovs_restarted = False ovs_restarted = False
@ -2091,6 +2122,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
# between these two statements, this will be thread-safe # between these two statements, this will be thread-safe
updated_ports_copy = self.updated_ports updated_ports_copy = self.updated_ports
self.updated_ports = set() self.updated_ports = set()
activated_bindings_copy = self.activated_bindings
self.activated_bindings = set()
(port_info, ancillary_port_info, consecutive_resyncs, (port_info, ancillary_port_info, consecutive_resyncs,
ports_not_ready_yet) = (self.process_port_info( ports_not_ready_yet) = (self.process_port_info(
start, polling_manager, sync, ovs_restarted, start, polling_manager, sync, ovs_restarted,
@ -2100,6 +2133,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
sync = False sync = False
self.process_deleted_ports(port_info) self.process_deleted_ports(port_info)
self.process_deactivated_bindings(port_info) self.process_deactivated_bindings(port_info)
self.process_activated_bindings(port_info,
activated_bindings_copy)
ofport_changed_ports = self.update_stale_ofport_rules() ofport_changed_ports = self.update_stale_ofport_rules()
if ofport_changed_ports: if ofport_changed_ports:
port_info.setdefault('updated', set()).update( port_info.setdefault('updated', set()).update(
@ -2154,6 +2189,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
LOG.exception("Error while processing VIF ports") LOG.exception("Error while processing VIF ports")
# Put the ports back in self.updated_port # Put the ports back in self.updated_port
self.updated_ports |= updated_ports_copy self.updated_ports |= updated_ports_copy
self.activated_bindings |= activated_bindings_copy
sync = True sync = True
port_stats = self.get_port_stats(port_info, ancillary_port_info) port_stats = self.get_port_stats(port_info, ancillary_port_info)
self.loop_count_and_wait(start, port_stats) self.loop_count_and_wait(start, port_stats)

View File

@ -16,6 +16,7 @@
import datetime import datetime
import mock import mock
import netaddr
from neutron_lib.agent import topics as lib_topics from neutron_lib.agent import topics as lib_topics
from neutron_lib.callbacks import events from neutron_lib.callbacks import events
from neutron_lib.callbacks import resources from neutron_lib.callbacks import resources
@ -24,6 +25,8 @@ from oslo_context import context as oslo_context
from oslo_utils import uuidutils from oslo_utils import uuidutils
from neutron.agent import rpc from neutron.agent import rpc
from neutron.common import constants as n_const
from neutron.objects import network
from neutron.objects import ports from neutron.objects import ports
from neutron.tests import base from neutron.tests import base
@ -176,12 +179,28 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
super(TestCacheBackedPluginApi, self).setUp() super(TestCacheBackedPluginApi, self).setUp()
self._api = rpc.CacheBackedPluginApi(lib_topics.PLUGIN) self._api = rpc.CacheBackedPluginApi(lib_topics.PLUGIN)
self._api._legacy_interface = mock.Mock() self._api._legacy_interface = mock.Mock()
self._api.remote_resource_cache = mock.Mock()
self._network_id = uuidutils.generate_uuid()
self._segment_id = uuidutils.generate_uuid()
self._segment = network.NetworkSegment(
id=self._segment_id, network_id=self._network_id,
network_type=constants.TYPE_FLAT)
self._port_id = uuidutils.generate_uuid() self._port_id = uuidutils.generate_uuid()
self._network = network.Network(id=self._network_id,
segments=[self._segment])
self._port = ports.Port( self._port = ports.Port(
id=self._port_id, id=self._port_id, network_id=self._network_id,
mac_address=netaddr.EUI('fa:16:3e:ec:c7:d9'), admin_state_up=True,
security_group_ids=set([uuidutils.generate_uuid()]),
fixed_ips=[], allowed_address_pairs=[],
device_owner=constants.DEVICE_OWNER_COMPUTE_PREFIX,
bindings=[ports.PortBinding(port_id=self._port_id, bindings=[ports.PortBinding(port_id=self._port_id,
host='host1', host='host1',
status=constants.ACTIVE)]) status=constants.ACTIVE,
profile={})],
binding_levels=[ports.PortBindingLevel(port_id=self._port_id,
host='host1',
segment=self._segment)])
def test__legacy_notifier_resource_delete(self): def test__legacy_notifier_resource_delete(self):
self._api._legacy_notifier(resources.PORT, events.AFTER_DELETE, self, self._api._legacy_notifier(resources.PORT, events.AFTER_DELETE, self,
@ -189,8 +208,7 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
existing=self._port) existing=self._port)
self._api._legacy_interface.port_update.assert_not_called() self._api._legacy_interface.port_update.assert_not_called()
self._api._legacy_interface.port_delete.assert_called_once_with( self._api._legacy_interface.port_delete.assert_called_once_with(
mock.ANY, port={'id': self._port_id}, port_id=self._port_id, mock.ANY, port={'id': self._port_id}, port_id=self._port_id)
host=None)
self._api._legacy_interface.binding_deactivate.assert_not_called() self._api._legacy_interface.binding_deactivate.assert_not_called()
def test__legacy_notifier_resource_update(self): def test__legacy_notifier_resource_update(self):
@ -201,8 +219,7 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
existing=self._port, updated=updated_port) existing=self._port, updated=updated_port)
self._api._legacy_interface.port_delete.assert_not_called() self._api._legacy_interface.port_delete.assert_not_called()
self._api._legacy_interface.port_update.assert_called_once_with( self._api._legacy_interface.port_update.assert_called_once_with(
mock.ANY, port={'id': self._port_id}, port_id=self._port_id, mock.ANY, port={'id': self._port_id}, port_id=self._port_id)
host=None)
self._api._legacy_interface.binding_deactivate.assert_not_called() self._api._legacy_interface.binding_deactivate.assert_not_called()
def _test__legacy_notifier_binding_activated(self): def _test__legacy_notifier_binding_activated(self):
@ -225,8 +242,9 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
def test__legacy_notifier_new_binding_activated(self): def test__legacy_notifier_new_binding_activated(self):
self._test__legacy_notifier_binding_activated() self._test__legacy_notifier_binding_activated()
self._api._legacy_interface.binding_deactivate.assert_called_once_with( self._api._legacy_interface.binding_deactivate.assert_called_once_with(
mock.ANY, port={'id': self._port_id}, port_id=self._port_id, mock.ANY, host='host1', port_id=self._port_id)
host='host1') self._api._legacy_interface.binding_activate.assert_called_once_with(
mock.ANY, host='host2', port_id=self._port_id)
def test__legacy_notifier_no_new_binding_activated(self): def test__legacy_notifier_no_new_binding_activated(self):
updated_port = ports.Port( updated_port = ports.Port(
@ -240,8 +258,7 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
resource_id=self._port_id, resource_id=self._port_id,
existing=self._port, updated=updated_port) existing=self._port, updated=updated_port)
self._api._legacy_interface.port_update.assert_called_once_with( self._api._legacy_interface.port_update.assert_called_once_with(
mock.ANY, port={'id': self._port_id}, port_id=self._port_id, mock.ANY, port={'id': self._port_id}, port_id=self._port_id)
host=None)
self._api._legacy_interface.port_delete.assert_not_called() self._api._legacy_interface.port_delete.assert_not_called()
self._api._legacy_interface.binding_deactivate.assert_not_called() self._api._legacy_interface.binding_deactivate.assert_not_called()
@ -257,7 +274,7 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
resource_id=self._port_id, resource_id=self._port_id,
existing=self._port, updated=None) existing=self._port, updated=None)
call = mock.call(mock.ANY, port={'id': self._port_id}, call = mock.call(mock.ANY, port={'id': self._port_id},
port_id=self._port_id, host=None) port_id=self._port_id)
self._api._legacy_interface.port_update.assert_has_calls([call, call]) self._api._legacy_interface.port_update.assert_has_calls([call, call])
self._api._legacy_interface.port_delete.assert_not_called() self._api._legacy_interface.port_delete.assert_not_called()
self._api._legacy_interface.binding_deactivate.assert_not_called() self._api._legacy_interface.binding_deactivate.assert_not_called()
@ -265,3 +282,23 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
def test__legacy_notifier_binding_activated_not_supported(self): def test__legacy_notifier_binding_activated_not_supported(self):
del self._api._legacy_interface.binding_deactivate del self._api._legacy_interface.binding_deactivate
self._test__legacy_notifier_binding_activated() self._test__legacy_notifier_binding_activated()
def test_get_device_details_binding_in_host(self):
self._api.remote_resource_cache.get_resource_by_id.side_effect = [
self._port, self._network]
entry = self._api.get_device_details(mock.ANY, self._port_id, mock.ANY,
'host1')
self.assertEqual(self._port_id, entry['device'])
self.assertEqual(self._port_id, entry['port_id'])
self.assertEqual(self._network_id, entry['network_id'])
self.assertNotIn(n_const.NO_ACTIVE_BINDING, entry)
def test_get_device_details_binding_not_in_host(self):
self._api.remote_resource_cache.get_resource_by_id.side_effect = [
self._port, self._network]
entry = self._api.get_device_details(mock.ANY, self._port_id, mock.ANY,
'host2')
self.assertEqual(self._port_id, entry['device'])
self.assertNotIn('port_id', entry)
self.assertNotIn('network_id', entry)
self.assertIn(n_const.NO_ACTIVE_BINDING, entry)

View File

@ -28,6 +28,7 @@ from neutron.agent.common import ovs_lib
from neutron.agent.common import utils from neutron.agent.common import utils
from neutron.agent.linux import async_process from neutron.agent.linux import async_process
from neutron.agent.linux import ip_lib from neutron.agent.linux import ip_lib
from neutron.common import constants as c_const
from neutron.common import rpc as n_rpc from neutron.common import rpc as n_rpc
from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
@ -797,12 +798,30 @@ class TestOvsNeutronAgent(object):
'get_port_tag_dict', 'get_port_tag_dict',
return_value={}),\ return_value={}),\
mock.patch.object(self.agent, func_name) as func: mock.patch.object(self.agent, func_name) as func:
skip_devs, need_bound_devices, _ = ( skip_devs, _, need_bound_devices, _ = (
self.agent.treat_devices_added_or_updated([], False)) self.agent.treat_devices_added_or_updated([], False))
# The function should not raise # The function should not raise
self.assertFalse(skip_devs) self.assertFalse(skip_devs)
return func.called return func.called
def test_treat_devices_added_updated_no_active_binding(self):
details = {'device': 'id',
c_const.NO_ACTIVE_BINDING: True}
port = mock.Mock()
with mock.patch.object(self.agent.plugin_rpc,
'get_devices_details_list_and_failed_devices',
return_value={'devices': [details],
'failed_devices': []}),\
mock.patch.object(self.agent.int_br,
'get_vifs_by_ids',
return_value={details['device']: port}),\
mock.patch.object(self.agent, 'port_dead') as func:
skip_devs, binding_no_activated_devices, _, _ = (
self.agent.treat_devices_added_or_updated([], False))
self.assertFalse(skip_devs)
self.assertTrue(func.called)
self.assertIn('id', binding_no_activated_devices)
def test_treat_devices_added_updated_ignores_invalid_ofport(self): def test_treat_devices_added_updated_ignores_invalid_ofport(self):
port = mock.Mock() port = mock.Mock()
port.ofport = -1 port.ofport = -1
@ -873,7 +892,8 @@ class TestOvsNeutronAgent(object):
skip_devs = self.agent.treat_devices_added_or_updated([], False) skip_devs = self.agent.treat_devices_added_or_updated([], False)
# The function should return False for resync and no device # The function should return False for resync and no device
# processed # processed
self.assertEqual((['the_skipped_one'], [], set()), skip_devs) self.assertEqual((['the_skipped_one'], set(), [], set()),
skip_devs)
ext_mgr_delete_port.assert_called_once_with( ext_mgr_delete_port.assert_called_once_with(
self.agent.context, {'port_id': 'the_skipped_one'}) self.agent.context, {'port_id': 'the_skipped_one'})
self.assertFalse(treat_vif_port.called) self.assertFalse(treat_vif_port.called)
@ -890,7 +910,7 @@ class TestOvsNeutronAgent(object):
mock.patch.object(self.agent, mock.patch.object(self.agent,
'treat_vif_port') as treat_vif_port: 'treat_vif_port') as treat_vif_port:
failed_devices = {'added': set(), 'removed': set()} failed_devices = {'added': set(), 'removed': set()}
(_, _, failed_devices['added']) = ( (_, _, _, failed_devices['added']) = (
self.agent.treat_devices_added_or_updated([], False)) self.agent.treat_devices_added_or_updated([], False))
# The function should return False for resync and no device # The function should return False for resync and no device
# processed # processed
@ -921,7 +941,7 @@ class TestOvsNeutronAgent(object):
return_value={}),\ return_value={}),\
mock.patch.object(self.agent, mock.patch.object(self.agent,
'treat_vif_port') as treat_vif_port: 'treat_vif_port') as treat_vif_port:
skip_devs, need_bound_devices, _ = ( skip_devs, _, need_bound_devices, _ = (
self.agent.treat_devices_added_or_updated([], False)) self.agent.treat_devices_added_or_updated([], False))
# The function should return False for resync # The function should return False for resync
self.assertFalse(skip_devs) self.assertFalse(skip_devs)
@ -1008,16 +1028,18 @@ class TestOvsNeutronAgent(object):
self.agent._bind_devices([{'network_id': 'non-existent', self.agent._bind_devices([{'network_id': 'non-existent',
'vif_port': vif_port}]) 'vif_port': vif_port}])
def _test_process_network_ports(self, port_info, skipped_devices=None): def _test_process_network_ports(self, port_info, skipped_devices=None,
binding_no_activated_devices=None):
failed_devices = {'added': set(), 'removed': set()} failed_devices = {'added': set(), 'removed': set()}
skipped_devices = skipped_devices or [] skipped_devices = skipped_devices or []
binding_no_activated_devices = binding_no_activated_devices or set()
added_devices = port_info.get('added', set()) added_devices = port_info.get('added', set())
with mock.patch.object(self.agent.sg_agent, with mock.patch.object(self.agent.sg_agent,
"setup_port_filters") as setup_port_filters,\ "setup_port_filters") as setup_port_filters,\
mock.patch.object( mock.patch.object(
self.agent, "treat_devices_added_or_updated", self.agent, "treat_devices_added_or_updated",
return_value=( return_value=(
skipped_devices, [], skipped_devices, binding_no_activated_devices, [],
failed_devices['added'])) as device_added_updated,\ failed_devices['added'])) as device_added_updated,\
mock.patch.object(self.agent.int_br, "get_ports_attributes", mock.patch.object(self.agent.int_br, "get_ports_attributes",
return_value=[]),\ return_value=[]),\
@ -1034,7 +1056,8 @@ class TestOvsNeutronAgent(object):
failed_devices, failed_devices,
self.agent.process_network_ports(port_info, False)) self.agent.process_network_ports(port_info, False))
setup_port_filters.assert_called_once_with( setup_port_filters.assert_called_once_with(
added_devices - set(skipped_devices), (added_devices - set(skipped_devices) -
binding_no_activated_devices),
port_info.get('updated', set())) port_info.get('updated', set()))
devices_added_updated = (added_devices | devices_added_updated = (added_devices |
port_info.get('updated', set())) port_info.get('updated', set()))
@ -1065,6 +1088,14 @@ class TestOvsNeutronAgent(object):
'added': set(['eth1', 'eth2'])} 'added': set(['eth1', 'eth2'])}
self._test_process_network_ports(port_info, skipped_devices=['eth1']) self._test_process_network_ports(port_info, skipped_devices=['eth1'])
def test_process_network_port_with_binding_no_activated_devices(self):
port_info = {'current': set(['tap0', 'tap1']),
'removed': set(['eth0']),
'added': set(['eth1', 'eth2', 'eth3'])}
self._test_process_network_ports(
port_info, skipped_devices=['eth1'],
binding_no_activated_devices=set(['eth3']))
def test_process_network_port_with_empty_port(self): def test_process_network_port_with_empty_port(self):
self._test_process_network_ports({}) self._test_process_network_ports({})
@ -1247,6 +1278,32 @@ class TestOvsNeutronAgent(object):
int_br.delete_port.assert_not_called() int_br.delete_port.assert_not_called()
self.assertEqual(set(), self.agent.deactivated_bindings) self.assertEqual(set(), self.agent.deactivated_bindings)
def test_binding_activate(self):
self.agent.binding_activate('context', port_id='id', host='host')
self.assertIn('id', self.agent.activated_bindings)
def test_binding_activate_not_for_host(self):
self.agent.binding_activate('context', port_id='id', host='other-host')
self.assertEqual(set(), self.agent.activated_bindings)
def test_process_activated_bindings(self):
port_info = {}
port_info['added'] = set(['added_port_id'])
port_info['current'] = set(['activated_port_id'])
self.agent.process_activated_bindings(port_info,
set(['activated_port_id']))
self.assertIn('added_port_id', port_info['added'])
self.assertIn('activated_port_id', port_info['added'])
def test_process_activated_bindings_activated_port_not_present(self):
port_info = {}
port_info['added'] = set(['added_port_id'])
port_info['current'] = set()
self.agent.process_activated_bindings(port_info,
set(['activated_port_id']))
self.assertIn('added_port_id', port_info['added'])
self.assertNotIn('activated_port_id', port_info['added'])
def _test_setup_physical_bridges(self, port_exists=False): def _test_setup_physical_bridges(self, port_exists=False):
with mock.patch.object(ip_lib.IPDevice, "exists") as devex_fn,\ with mock.patch.object(ip_lib.IPDevice, "exists") as devex_fn,\
mock.patch.object(sys, "exit"),\ mock.patch.object(sys, "exit"),\