From 4d143b710de22cd8c052c02fab7fdb5273ab4a93 Mon Sep 17 00:00:00 2001 From: Robert Kukura Date: Mon, 11 Aug 2014 14:54:13 -0400 Subject: [PATCH] ML2: Driver API changes for hierarchical port binding The following ML2 driver API changes are required to support hierarchical port binding: * Add segments_to_bind PortContext property containing the list of network segments with which a mechanism driver should try to bind the port. All mechanism drivers that bind ports should now use this property in place of network.network_segments, which contains only the network's static segments. * Replace several PortContext properties (bound_segment, bound_driver, original_bound_segment, original_bound_driver) with new properties (binding_levels, top_bound_segment, bottom_bound_segment, original_binding_levels, original_top_bound_segment, original_bottom_bound_segment) in order to represent hierarchical bindings. * Add stubbed-out continue_binding() method to PortContext, allowing mechanism drivers to partially bind the port. All existing drivers and unit tests are updated accordingly. The DB schema changes and logic required for hierarchical port binding will be implemented in dependent patches. Gerrit Spec: https://review.openstack.org/#/c/139886/ Partially-implements: blueprint ml2-hierarchical-port-binding Change-Id: Icb1a016f4661e427cb6cfa3452802ba5e64b7124 --- neutron/plugins/ml2/driver_api.py | 194 ++++++++++++++---- neutron/plugins/ml2/driver_context.py | 74 +++++-- .../ml2/drivers/cisco/apic/mechanism_apic.py | 8 +- .../drivers/cisco/nexus/mech_cisco_nexus.py | 15 +- .../ml2/drivers/freescale/mechanism_fslsdn.py | 2 +- .../plugins/ml2/drivers/l2pop/mech_driver.py | 2 +- neutron/plugins/ml2/drivers/mech_agent.py | 2 +- .../ml2/drivers/mech_bigswitch/driver.py | 6 +- .../plugins/ml2/drivers/mech_nuage/driver.py | 6 +- .../ml2/drivers/mech_sriov/mech_driver.py | 2 +- neutron/plugins/ml2/drivers/mechanism_odl.py | 2 +- neutron/plugins/ml2/plugin.py | 2 +- neutron/plugins/ml2/rpc.py | 2 +- neutron/tests/unit/ml2/_test_mech_agent.py | 45 ++-- .../apic/test_cisco_apic_mechanism_driver.py | 2 +- .../drivers/cisco/nexus/test_cisco_mech.py | 12 +- .../drivers/cisco/nexus/test_cisco_nexus.py | 2 +- .../unit/ml2/drivers/mechanism_logger.py | 12 +- .../tests/unit/ml2/drivers/mechanism_test.py | 51 +++-- neutron/tests/unit/ml2/test_rpcapi.py | 2 +- 20 files changed, 325 insertions(+), 118 deletions(-) diff --git a/neutron/plugins/ml2/driver_api.py b/neutron/plugins/ml2/driver_api.py index cc68181c887..943d5297816 100644 --- a/neutron/plugins/ml2/driver_api.py +++ b/neutron/plugins/ml2/driver_api.py @@ -26,6 +26,12 @@ NETWORK_TYPE = 'network_type' PHYSICAL_NETWORK = 'physical_network' SEGMENTATION_ID = 'segmentation_id' +# The following keys are used in the binding level dictionaries +# available via the binding_levels and original_binding_levels +# PortContext properties. +BOUND_DRIVER = 'bound_driver' +BOUND_SEGMENT = 'bound_segment' + @six.add_metaclass(abc.ABCMeta) class TypeDriver(object): @@ -260,17 +266,100 @@ class PortContext(object): pass @abc.abstractproperty - def bound_segment(self): - """Return the currently bound segment dictionary.""" + def binding_levels(self): + """Return dictionaries describing the current binding levels. + + This property returns a list of dictionaries describing each + binding level if the port is bound or partially bound, or None + if the port is unbound. Each returned dictionary contains the + name of the bound driver under the BOUND_DRIVER key, and the + bound segment dictionary under the BOUND_SEGMENT key. + + The first entry (index 0) describes the top-level binding, + which always involves one of the port's network's static + segments. In the case of a hierarchical binding, subsequent + entries describe the lower-level bindings in descending order, + which may involve dynamic segments. Adjacent levels where + different drivers bind the same static or dynamic segment are + possible. The last entry (index -1) describes the bottom-level + binding that supplied the port's binding:vif_type and + binding:vif_details attribute values. + + Within calls to MechanismDriver.bind_port, descriptions of the + levels above the level currently being bound are returned. + """ pass @abc.abstractproperty - def original_bound_segment(self): - """Return the original bound segment dictionary. + def original_binding_levels(self): + """Return dictionaries describing the original binding levels. - Return the original bound segment dictionary, prior to a call - to update_port. Method is only valid within calls to - update_port_precommit and update_port_postcommit. + This property returns a list of dictionaries describing each + original binding level if the port was previously bound, or + None if the port was unbound. The content is as described for + the binding_levels property. + + This property is only valid within calls to + update_port_precommit and update_port_postcommit. It returns + None otherwise. + """ + pass + + @abc.abstractproperty + def top_bound_segment(self): + """Return the current top-level bound segment dictionary. + + This property returns the current top-level bound segment + dictionary, or None if the port is unbound. For a bound port, + top_bound_segment is equivalent to + binding_levels[0][BOUND_SEGMENT], and returns one of the + port's network's static segments. + """ + pass + + @abc.abstractproperty + def original_top_bound_segment(self): + """Return the original top-level bound segment dictionary. + + This property returns the original top-level bound segment + dictionary, or None if the port was previously unbound. For a + previously bound port, original_top_bound_segment is + equivalent to original_binding_levels[0][BOUND_SEGMENT], and + returns one of the port's network's static segments. + + This property is only valid within calls to + update_port_precommit and update_port_postcommit. It returns + None otherwise. + """ + pass + + @abc.abstractproperty + def bottom_bound_segment(self): + """Return the current bottom-level bound segment dictionary. + + This property returns the current bottom-level bound segment + dictionary, or None if the port is unbound. For a bound port, + bottom_bound_segment is equivalent to + binding_levels[-1][BOUND_SEGMENT], and returns the segment + whose binding supplied the port's binding:vif_type and + binding:vif_details attribute values. + """ + pass + + @abc.abstractproperty + def original_bottom_bound_segment(self): + """Return the original bottom-level bound segment dictionary. + + This property returns the orignal bottom-level bound segment + dictionary, or None if the port was previously unbound. For a + previously bound port, original_bottom_bound_segment is + equivalent to original_binding_levels[-1][BOUND_SEGMENT], and + returns the segment whose binding supplied the port's previous + binding:vif_type and binding:vif_details attribute values. + + This property is only valid within calls to + update_port_precommit and update_port_postcommit. It returns + None otherwise. """ pass @@ -289,17 +378,18 @@ class PortContext(object): pass @abc.abstractproperty - def bound_driver(self): - """Return the currently bound mechanism driver name.""" - pass + def segments_to_bind(self): + """Return the list of segments with which to bind the port. - @abc.abstractproperty - def original_bound_driver(self): - """Return the original bound mechanism driver name. + This property returns the list of segment dictionaries with + which the mechanism driver may bind the port. When + establishing a top-level binding, these will be the port's + network's static segments. For each subsequent level, these + will be the segments passed to continue_binding by the + mechanism driver that bound the level above. - Return the original bound mechanism driver name, prior to a - call to update_port. Method is only valid within calls to - update_port_precommit and update_port_postcommit. + This property is only valid within calls to + MechanismDriver.bind_port. It returns None otherwise. """ pass @@ -315,16 +405,36 @@ class PortContext(object): @abc.abstractmethod def set_binding(self, segment_id, vif_type, vif_details, status=None): - """Set the binding for the port. + """Set the bottom-level binding for the port. :param segment_id: Network segment bound for the port. :param vif_type: The VIF type for the bound port. :param vif_details: Dictionary with details for VIF driver. :param status: Port status to set if not None. - Called by MechanismDriver.bind_port to indicate success and - specify binding details to use for port. The segment_id must - identify an item in network.network_segments. + This method is called by MechanismDriver.bind_port to indicate + success and specify binding details to use for port. The + segment_id must identify an item in the current value of the + segments_to_bind property. + """ + pass + + @abc.abstractmethod + def continue_binding(self, segment_id, next_segments_to_bind): + """Continue binding the port with different segments. + + :param segment_id: Network segment partially bound for the port. + :param next_segments_to_bind: Segments to continue binding with. + + This method is called by MechanismDriver.bind_port to indicate + it was able to partially bind the port, but that one or more + additional mechanism drivers are required to complete the + binding. The segment_id must identify an item in the current + value of the segments_to_bind property. The list of segments + IDs passed as next_segments_to_bind identify dynamic (or + static) segments of the port's network that will be used to + populate segments_to_bind for the next lower level of a + hierarchical binding. """ pass @@ -656,22 +766,36 @@ class MechanismDriver(object): :param context: PortContext instance describing the port - Called outside any transaction to attempt to establish a port - binding using this mechanism driver. If the driver is able to - bind the port, it must call context.set_binding() with the - binding details. If the binding results are committed after - bind_port() returns, they will be seen by all mechanism - drivers as update_port_precommit() and - update_port_postcommit() calls. + This method is called outside any transaction to attempt to + establish a port binding using this mechanism driver. Bindings + may be created at each of multiple levels of a hierarchical + network, and are established from the top level downward. At + each level, the mechanism driver determines whether it can + bind to any of the network segments in the + context.segments_to_bind property, based on the value of the + context.host property, any relevant port or network + attributes, and its own knowledge of the network topology. At + the top level, context.segments_to_bind contains the static + segments of the port's network. At each lower level of + binding, it contains static or dynamic segments supplied by + the driver that bound at the level above. If the driver is + able to complete the binding of the port to any segment in + context.segments_to_bind, it must call context.set_binding + with the binding details. If it can partially bind the port, + it must call context.continue_binding with the network + segments to be used to bind at the next lower level. - Note that if some other thread or process concurrently binds - or updates the port, these binding results will not be - committed, and update_port_precommit() and - update_port_postcommit() will not be called on the mechanism - drivers with these results. Because binding results can be - discarded rather than committed, drivers should avoid making - persistent state changes in bind_port(), or else must ensure - that such state changes are eventually cleaned up. + If the binding results are committed after bind_port returns, + they will be seen by all mechanism drivers as + update_port_precommit and update_port_postcommit calls. But if + some other thread or process concurrently binds or updates the + port, these binding results will not be committed, and + update_port_precommit and update_port_postcommit will not be + called on the mechanism drivers with these results. Because + binding results can be discarded rather than committed, + drivers should avoid making persistent state changes in + bind_port, or else must ensure that such state changes are + eventually cleaned up. """ pass diff --git a/neutron/plugins/ml2/driver_context.py b/neutron/plugins/ml2/driver_context.py index 735a5c5e3a4..3c946d3b570 100644 --- a/neutron/plugins/ml2/driver_context.py +++ b/neutron/plugins/ml2/driver_context.py @@ -16,10 +16,15 @@ from oslo.serialization import jsonutils from neutron.common import constants +from neutron.common import exceptions as exc from neutron.extensions import portbindings +from neutron.i18n import _LW +from neutron.openstack.common import log from neutron.plugins.ml2 import db from neutron.plugins.ml2 import driver_api as api +LOG = log.getLogger(__name__) + class MechanismDriverContext(object): """MechanismDriver context base class.""" @@ -109,19 +114,54 @@ class PortContext(MechanismDriverContext, api.PortContext): return self._network_context @property - def bound_segment(self): - id = self._binding.segment - if id: - for segment in self._network_context.network_segments: - if segment[api.ID] == id: - return segment + def binding_levels(self): + # TODO(rkukura): Implement for hierarchical port binding. + if self._binding.segment: + return [{ + api.BOUND_DRIVER: self._binding.driver, + api.BOUND_SEGMENT: self._expand_segment(self._binding.segment) + }] @property - def original_bound_segment(self): + def original_binding_levels(self): + # TODO(rkukura): Implement for hierarchical port binding. if self._original_bound_segment_id: - for segment in self._network_context.network_segments: - if segment[api.ID] == self._original_bound_segment_id: - return segment + return [{ + api.BOUND_DRIVER: self._original_bound_driver, + api.BOUND_SEGMENT: + self._expand_segment(self._original_bound_segment_id) + }] + + @property + def top_bound_segment(self): + # TODO(rkukura): Implement for hierarchical port binding. + if self._binding.segment: + return self._expand_segment(self._binding.segment) + + @property + def original_top_bound_segment(self): + # TODO(rkukura): Implement for hierarchical port binding. + if self._original_bound_segment_id: + return self._expand_segment(self._original_bound_segment_id) + + @property + def bottom_bound_segment(self): + # TODO(rkukura): Implement for hierarchical port binding. + if self._binding.segment: + return self._expand_segment(self._binding.segment) + + @property + def original_bottom_bound_segment(self): + # TODO(rkukura): Implement for hierarchical port binding. + if self._original_bound_segment_id: + return self._expand_segment(self._original_bound_segment_id) + + def _expand_segment(self, segment_id): + segment = db.get_segment_by_id(self._plugin_context.session, + segment_id) + if not segment: + LOG.warning(_LW("Could not expand segment %s"), segment_id) + return segment @property def host(self): @@ -132,12 +172,9 @@ class PortContext(MechanismDriverContext, api.PortContext): return self._original_port.get(portbindings.HOST_ID) @property - def bound_driver(self): - return self._binding.driver - - @property - def original_bound_driver(self): - return self._original_bound_driver + def segments_to_bind(self): + # TODO(rkukura): Implement for hierarchical port binding. + return self._network_context.network_segments def host_agents(self, agent_type): return self._plugin.get_agents(self._plugin_context, @@ -152,6 +189,11 @@ class PortContext(MechanismDriverContext, api.PortContext): self._binding.vif_details = jsonutils.dumps(vif_details) self._new_port_status = status + def continue_binding(self, segment_id, next_segments_to_bind): + # TODO(rkukura): Implement for hierarchical port binding. + msg = _("Hierarchical port binding not yet implemented") + raise exc.Invalid(message=msg) + def allocate_dynamic_segment(self, segment): network_id = self._network_context.current['id'] diff --git a/neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py b/neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py index f57bbf20513..819edc9cb38 100644 --- a/neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py +++ b/neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py @@ -92,13 +92,13 @@ class APICMechanismDriver(api.MechanismDriver): tenant_id = self.name_mapper.tenant(context, tenant_id) # Get segmentation id - if not context.bound_segment: + segment = context.top_bound_segment + if not segment: LOG.debug("Port %s is not bound to a segment", port) return seg = None - if (context.bound_segment.get(api.NETWORK_TYPE) - in [constants.TYPE_VLAN]): - seg = context.bound_segment.get(api.SEGMENTATION_ID) + if (segment.get(api.NETWORK_TYPE) in [constants.TYPE_VLAN]): + seg = segment.get(api.SEGMENTATION_ID) # hosts on which this vlan is provisioned host = context.host # Create a static path attachment for the host/epg/switchport combo diff --git a/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py b/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py index e261502fa5b..5219a0f909d 100644 --- a/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py +++ b/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py @@ -177,7 +177,8 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): vlan_already_removed.append(switch_ip) def _is_vm_migration(self, context): - if not context.bound_segment and context.original_bound_segment: + if (not context.bottom_bound_segment and + context.original_bottom_bound_segment): return context.host != context.original_host def _port_action(self, port, segment, func): @@ -201,13 +202,13 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): # else process update event. if self._is_vm_migration(context): self._port_action(context.original, - context.original_bound_segment, + context.original_bottom_bound_segment, self._delete_nxos_db) else: if (self._is_deviceowner_compute(context.current) and self._is_status_active(context.current)): self._port_action(context.current, - context.bound_segment, + context.bottom_bound_segment, self._configure_nxos_db) def update_port_postcommit(self, context): @@ -217,25 +218,25 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): # else process update event. if self._is_vm_migration(context): self._port_action(context.original, - context.original_bound_segment, + context.original_bottom_bound_segment, self._delete_switch_entry) else: if (self._is_deviceowner_compute(context.current) and self._is_status_active(context.current)): self._port_action(context.current, - context.bound_segment, + context.bottom_bound_segment, self._configure_switch_entry) def delete_port_precommit(self, context): """Delete port pre-database commit event.""" if self._is_deviceowner_compute(context.current): self._port_action(context.current, - context.bound_segment, + context.bottom_bound_segment, self._delete_nxos_db) def delete_port_postcommit(self, context): """Delete port non-database commit event.""" if self._is_deviceowner_compute(context.current): self._port_action(context.current, - context.bound_segment, + context.bottom_bound_segment, self._delete_switch_entry) diff --git a/neutron/plugins/ml2/drivers/freescale/mechanism_fslsdn.py b/neutron/plugins/ml2/drivers/freescale/mechanism_fslsdn.py index 5a0e5a506cc..cc594dc8e2d 100755 --- a/neutron/plugins/ml2/drivers/freescale/mechanism_fslsdn.py +++ b/neutron/plugins/ml2/drivers/freescale/mechanism_fslsdn.py @@ -202,7 +202,7 @@ class FslsdnMechanismDriver(api.MechanismDriver): {'port': context.current['id'], 'network': context.network.current['id']}) # Prepared porting binding data - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if self.check_segment(segment): context.set_binding(segment[api.ID], self.vif_type, diff --git a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py index 4e3696386f3..8091bbc98ec 100644 --- a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py +++ b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py @@ -156,7 +156,7 @@ class L2populationMechanismDriver(api.MechanismDriver, "configuration.")) return - segment = context.bound_segment + segment = context.bottom_bound_segment if not segment: LOG.warning(_LW("Port %(port)s updated by agent %(agent)s " "isn't bound to any segment"), diff --git a/neutron/plugins/ml2/drivers/mech_agent.py b/neutron/plugins/ml2/drivers/mech_agent.py index 4e9b55434bb..330a81dd142 100644 --- a/neutron/plugins/ml2/drivers/mech_agent.py +++ b/neutron/plugins/ml2/drivers/mech_agent.py @@ -65,7 +65,7 @@ class AgentMechanismDriverBase(api.MechanismDriver): for agent in context.host_agents(self.agent_type): LOG.debug("Checking agent: %s", agent) if agent['alive']: - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if self.try_to_bind_segment_for_agent(context, segment, agent): LOG.debug("Bound using segment: %s", segment) diff --git a/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py b/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py index c3d61fcd75c..cfae1d93779 100644 --- a/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py +++ b/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py @@ -127,7 +127,7 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, port = copy.deepcopy(context.current) net = context.network.current port['network'] = net - port['bound_segment'] = context.bound_segment + port['bound_segment'] = context.top_bound_segment actx = ctx.get_admin_context() prepped_port = self._extend_port_dict_binding(actx, port) prepped_port = self._map_state_and_status(prepped_port) @@ -151,7 +151,7 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, # TODO(kevinbenton): check controller to see if the port exists # so this driver can be run in parallel with others that add # support for external port bindings - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if segment[api.NETWORK_TYPE] == pconst.TYPE_VLAN: context.set_binding( segment[api.ID], portbindings.VIF_TYPE_BRIDGE, @@ -161,7 +161,7 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, # IVS hosts will have a vswitch with the same name as the hostname if self.does_vswitch_exist(context.host): - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if segment[api.NETWORK_TYPE] == pconst.TYPE_VLAN: context.set_binding( segment[api.ID], portbindings.VIF_TYPE_IVS, diff --git a/neutron/plugins/ml2/drivers/mech_nuage/driver.py b/neutron/plugins/ml2/drivers/mech_nuage/driver.py index b83e3072858..97d12cd4f21 100644 --- a/neutron/plugins/ml2/drivers/mech_nuage/driver.py +++ b/neutron/plugins/ml2/drivers/mech_nuage/driver.py @@ -62,8 +62,8 @@ class NuageMechanismDriver(plugin.NuagePlugin, # talking to backend. # 1) binding has happened successfully. # 2) Its a VM port. - if ((not context.original_bound_segment and - context.bound_segment) and + if ((not context.original_top_bound_segment and + context.top_bound_segment) and port['device_owner'].startswith(port_prefix)): np_name = cfg.CONF.RESTPROXY.default_net_partition_name self._create_update_port(context._plugin_context, @@ -80,7 +80,7 @@ class NuageMechanismDriver(plugin.NuagePlugin, "network %(network)s", {'port': context.current['id'], 'network': context.network.current['id']}) - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if self._check_segment(segment): context.set_binding(segment[api.ID], self.vif_type, diff --git a/neutron/plugins/ml2/drivers/mech_sriov/mech_driver.py b/neutron/plugins/ml2/drivers/mech_sriov/mech_driver.py index dd3421dc917..b50e26bd172 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/mech_driver.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/mech_driver.py @@ -119,7 +119,7 @@ class SriovNicSwitchMechanismDriver(api.MechanismDriver): self.try_to_bind(context) def try_to_bind(self, context, agent=None): - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if self.check_segment(segment, agent): context.set_binding(segment[api.ID], self.vif_type, diff --git a/neutron/plugins/ml2/drivers/mechanism_odl.py b/neutron/plugins/ml2/drivers/mechanism_odl.py index b1d48c59448..70445a6944e 100644 --- a/neutron/plugins/ml2/drivers/mechanism_odl.py +++ b/neutron/plugins/ml2/drivers/mechanism_odl.py @@ -100,7 +100,7 @@ class OpenDaylightMechanismDriver(api.MechanismDriver): "network %(network)s", {'port': context.current['id'], 'network': context.network.current['id']}) - for segment in context.network.network_segments: + for segment in context.segments_to_bind: if self.check_segment(segment): context.set_binding(segment[api.ID], self.vif_type, diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 3a68f204718..9cc261a7c97 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -469,7 +469,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, def _notify_port_updated(self, mech_context): port = mech_context._port - segment = mech_context.bound_segment + segment = mech_context.bottom_bound_segment if not segment: # REVISIT(rkukura): This should notify agent to unplug port network = mech_context.network.current diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index 6efa6dabdd5..1e6e64807d1 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -70,7 +70,7 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): {'device': device, 'agent_id': agent_id}) return {'device': device} - segment = port_context.bound_segment + segment = port_context.bottom_bound_segment port = port_context.current if not segment: diff --git a/neutron/tests/unit/ml2/_test_mech_agent.py b/neutron/tests/unit/ml2/_test_mech_agent.py index 71aeecf13c1..c69d837f9b9 100644 --- a/neutron/tests/unit/ml2/_test_mech_agent.py +++ b/neutron/tests/unit/ml2/_test_mech_agent.py @@ -72,16 +72,38 @@ class FakePortContext(api.PortContext): return self._network_context @property - def bound_segment(self): - if self._bound_segment_id: - for segment in self._network_context.network_segments: - if segment[api.ID] == self._bound_segment_id: - return segment + def binding_levels(self): + if self._bound_segment: + return [{ + api.BOUND_DRIVER: 'fake_driver', + api.BOUND_SEGMENT: self._expand_segment(self._bound_segment) + }] @property - def original_bound_segment(self): + def original_binding_levels(self): return None + @property + def top_bound_segment(self): + return self._expand_segment(self._bound_segment) + + @property + def original_top_bound_segment(self): + return None + + @property + def bottom_bound_segment(self): + return self._expand_segment(self._bound_segment) + + @property + def original_bottom_bound_segment(self): + return None + + def _expand_segment(self, segment_id): + for segment in self._network_context.network_segments: + if segment[api.ID] == self._bound_segment_id: + return segment + @property def host(self): return '' @@ -91,12 +113,8 @@ class FakePortContext(api.PortContext): return None @property - def bound_driver(self): - return None - - @property - def original_bound_driver(self): - return None + def segments_to_bind(self): + return self._network_context.network_segments def host_agents(self, agent_type): if agent_type == self._agent_type: @@ -109,6 +127,9 @@ class FakePortContext(api.PortContext): self._bound_vif_type = vif_type self._bound_vif_details = vif_details + def continue_binding(self, segment_id, next_segments_to_bind): + pass + def allocate_dynamic_segment(self, segment): pass diff --git a/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py b/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py index 58b5638c186..4459e9d4990 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py +++ b/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py @@ -321,7 +321,7 @@ class FakePortContext(object): return self._network @property - def bound_segment(self): + def top_bound_segment(self): return self._bound_segment def set_binding(self, segment_id, vif_type, cap_port_filter): diff --git a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py index e1f6388ff50..a0d1c390589 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py +++ b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py @@ -96,13 +96,13 @@ class CiscoML2MechanismTestCase(test_ml2_plugin.Ml2PluginV2TestCase): # Mock port context values for bound_segments and 'status'. self.mock_bound_segment = mock.patch.object( driver_context.PortContext, - 'bound_segment', + 'bottom_bound_segment', new_callable=mock.PropertyMock).start() self.mock_bound_segment.return_value = BOUND_SEGMENT1 self.mock_original_bound_segment = mock.patch.object( driver_context.PortContext, - 'original_bound_segment', + 'original_bottom_bound_segment', new_callable=mock.PropertyMock).start() self.mock_original_bound_segment.return_value = None @@ -559,16 +559,16 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase, The first one should only change the current host_id and remove the binding resulting in the mechanism drivers receiving: PortContext.original['binding:host_id']: previous value - PortContext.original_bound_segment: previous value + PortContext.original_bottom_bound_segment: previous value PortContext.current['binding:host_id']: current (new) value - PortContext.bound_segment: None + PortContext.bottom_bound_segment: None The second one binds the new host resulting in the mechanism drivers receiving: PortContext.original['binding:host_id']: previous value - PortContext.original_bound_segment: None + PortContext.original_bottom_bound_segment: None PortContext.current['binding:host_id']: previous value - PortContext.bound_segment: new value + PortContext.bottom_bound_segment: new value """ # Create network, subnet and port. diff --git a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py index 58100149fcf..4f9cefdcfe9 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py +++ b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py @@ -93,7 +93,7 @@ class FakePortContext(object): return self._network @property - def bound_segment(self): + def bottom_bound_segment(self): return self._segment diff --git a/neutron/tests/unit/ml2/drivers/mechanism_logger.py b/neutron/tests/unit/ml2/drivers/mechanism_logger.py index 401badb1663..21996208061 100644 --- a/neutron/tests/unit/ml2/drivers/mechanism_logger.py +++ b/neutron/tests/unit/ml2/drivers/mechanism_logger.py @@ -84,18 +84,14 @@ class LoggerMechanismDriver(api.MechanismDriver): network_context = context.network LOG.info(_("%(method)s called with port settings %(current)s " "(original settings %(original)s) " - "bound to segment %(segment)s " - "(original segment %(original_segment)s) " - "using driver %(driver)s " - "(original driver %(original_driver)s) " + "binding levels %(levels)s " + "(original binding levels %(original_levels)s) " "on network %(network)s"), {'method': method_name, 'current': context.current, 'original': context.original, - 'segment': context.bound_segment, - 'original_segment': context.original_bound_segment, - 'driver': context.bound_driver, - 'original_driver': context.original_bound_driver, + 'levels': context.binding_levels, + 'original_levels': context.original_binding_levels, 'network': network_context.current}) def create_port_precommit(self, context): diff --git a/neutron/tests/unit/ml2/drivers/mechanism_test.py b/neutron/tests/unit/ml2/drivers/mechanism_test.py index 090dfad4370..324a873bf2c 100644 --- a/neutron/tests/unit/ml2/drivers/mechanism_test.py +++ b/neutron/tests/unit/ml2/drivers/mechanism_test.py @@ -91,12 +91,14 @@ class TestMechanismDriver(api.MechanismDriver): if vif_type in (portbindings.VIF_TYPE_UNBOUND, portbindings.VIF_TYPE_BINDING_FAILED): - assert(context.bound_segment is None) - assert(context.bound_driver is None) + self._check_unbound(context.binding_levels, + context.top_bound_segment, + context.bottom_bound_segment) assert(context.current['id'] not in self.bound_ports) else: - assert(isinstance(context.bound_segment, dict)) - assert(context.bound_driver == 'test') + self._check_bound(context.binding_levels, + context.top_bound_segment, + context.bottom_bound_segment) assert(context.current['id'] in self.bound_ports) if original_expected: @@ -106,20 +108,41 @@ class TestMechanismDriver(api.MechanismDriver): assert(vif_type is not None) if vif_type in (portbindings.VIF_TYPE_UNBOUND, portbindings.VIF_TYPE_BINDING_FAILED): - assert(context.original_bound_segment is None) - assert(context.original_bound_driver is None) + self._check_unbound(context.original_binding_levels, + context.original_top_bound_segment, + context.original_bottom_bound_segment) else: - assert(isinstance(context.original_bound_segment, dict)) - assert(context.original_bound_driver == 'test') + self._check_bound(context.original_binding_levels, + context.original_top_bound_segment, + context.original_bottom_bound_segment) else: assert(context.original is None) - assert(context.original_bound_segment is None) - assert(context.original_bound_driver is None) + self._check_unbound(context.original_binding_levels, + context.original_top_bound_segment, + context.original_bottom_bound_segment) network_context = context.network assert(isinstance(network_context, api.NetworkContext)) self._check_network_context(network_context, False) + def _check_unbound(self, levels, top_segment, bottom_segment): + assert(levels is None) + assert(top_segment is None) + assert(bottom_segment is None) + + def _check_bound(self, levels, top_segment, bottom_segment): + assert(isinstance(levels, list)) + top_level = levels[0] + assert(isinstance(top_level, dict)) + assert(isinstance(top_segment, dict)) + assert(top_segment == top_level[api.BOUND_SEGMENT]) + assert('test' == top_level[api.BOUND_DRIVER]) + bottom_level = levels[-1] + assert(isinstance(bottom_level, dict)) + assert(isinstance(bottom_segment, dict)) + assert(bottom_segment == bottom_level[api.BOUND_SEGMENT]) + assert('test' == bottom_level[api.BOUND_DRIVER]) + def create_port_precommit(self, context): self._check_port_context(context, False) @@ -127,8 +150,8 @@ class TestMechanismDriver(api.MechanismDriver): self._check_port_context(context, False) def update_port_precommit(self, context): - if (context.original_bound_driver == 'test' and - context.bound_driver != 'test'): + if (context.original_top_bound_segment and + not context.top_bound_segment): self.bound_ports.remove(context.original['id']) self._check_port_context(context, True) @@ -144,8 +167,8 @@ class TestMechanismDriver(api.MechanismDriver): def bind_port(self, context): self._check_port_context(context, False) - host = context.current.get(portbindings.HOST_ID, None) - segment = context.network.network_segments[0][api.ID] + host = context.host + segment = context.segments_to_bind[0][api.ID] if host == "host-ovs-no_filter": context.set_binding(segment, portbindings.VIF_TYPE_OVS, {portbindings.CAP_PORT_FILTER: False}) diff --git a/neutron/tests/unit/ml2/test_rpcapi.py b/neutron/tests/unit/ml2/test_rpcapi.py index 2acf8dd1c2e..77ac520159f 100644 --- a/neutron/tests/unit/ml2/test_rpcapi.py +++ b/neutron/tests/unit/ml2/test_rpcapi.py @@ -87,7 +87,7 @@ class RpcCallbacksTestCase(base.BaseTestCase): device='fake_device')) def test_get_device_details_port_context_without_bounded_segment(self): - self.plugin.get_bound_port_context().bound_segment = None + self.plugin.get_bound_port_context().bottom_bound_segment = None self.assertEqual( {'device': 'fake_device'}, self.callbacks.get_device_details('fake_context',