]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
ML2: Driver API changes for hierarchical port binding
authorRobert Kukura <kukura@noironetworks.com>
Mon, 11 Aug 2014 18:54:13 +0000 (14:54 -0400)
committerRobert Kukura <kukura@noironetworks.com>
Fri, 16 Jan 2015 00:46:05 +0000 (19:46 -0500)
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

20 files changed:
neutron/plugins/ml2/driver_api.py
neutron/plugins/ml2/driver_context.py
neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py
neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py
neutron/plugins/ml2/drivers/freescale/mechanism_fslsdn.py
neutron/plugins/ml2/drivers/l2pop/mech_driver.py
neutron/plugins/ml2/drivers/mech_agent.py
neutron/plugins/ml2/drivers/mech_bigswitch/driver.py
neutron/plugins/ml2/drivers/mech_nuage/driver.py
neutron/plugins/ml2/drivers/mech_sriov/mech_driver.py
neutron/plugins/ml2/drivers/mechanism_odl.py
neutron/plugins/ml2/plugin.py
neutron/plugins/ml2/rpc.py
neutron/tests/unit/ml2/_test_mech_agent.py
neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py
neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py
neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py
neutron/tests/unit/ml2/drivers/mechanism_logger.py
neutron/tests/unit/ml2/drivers/mechanism_test.py
neutron/tests/unit/ml2/test_rpcapi.py

index cc68181c88745cdf0e754919834946aad433d47a..943d52978167fa749e966dc9edc92bed82e10400 100644 (file)
@@ -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_binding_levels(self):
+        """Return dictionaries describing the original binding levels.
+
+        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_bound_segment(self):
-        """Return the original bound segment dictionary.
+    def original_bottom_bound_segment(self):
+        """Return the original bottom-level bound segment dictionary.
 
-        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 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.
-
-        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.
+        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.
+
+        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
 
index 735a5c5e3a4e6b52d5532515ea16f9c42233c4c1..3c946d3b5704971dec29f2ae6e150b7b2005ab3e 100644 (file)
 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_binding_levels(self):
+        # TODO(rkukura): Implement for hierarchical port binding.
+        if self._original_bound_segment_id:
+            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_bound_segment(self):
+    def original_top_bound_segment(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 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']
 
index f57bbf205138821572e41aec57086d4ffef5481d..819edc9cb38d51e37ab61887ce558cdbd0fa2766 100644 (file)
@@ -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
index e261502fa5bb581d955d65c14373c7b668395ce3..5219a0f909d47d429d12aed9a4e014ada0d23152 100644 (file)
@@ -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)
index 5a0e5a506cc70035da05cc44b301dc994ab4906b..cc594dc8e2d3001ed8fe76e6e761c24429f5eb30 100755 (executable)
@@ -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,
index 4e3696386f3cbaba719c1c35f7a5b3c23eed7c9d..8091bbc98ec7149aee1f01f46e1c38c3d26726a1 100644 (file)
@@ -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"),
index 4e9b55434bb5defcecace6ac2859c964d48e36a4..330a81dd142667f4da1615b5ee9fa019b8295230 100644 (file)
@@ -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)
index c3d61fcd75cb8eb5b2aceb7dc11e838503cd6251..cfae1d937799f7d33a739d04c06b772575a39b7a 100644 (file)
@@ -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,
index b83e30728582a2e1416b571a35beb328c0a515ef..97d12cd4f211790098e9c544f02b645eeac9bf95 100644 (file)
@@ -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,
index dd3421dc91742b2d37919a0b302c1289bc798d34..b50e26bd172a620068afe92827c66f3d9ced8f42 100644 (file)
@@ -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,
index b1d48c5944893de770e770dd9f65e4499b7268fd..70445a6944e6660feeae25bab7d14b0b52317f8b 100644 (file)
@@ -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,
index 3a68f204718cf8bc42d81f781d38b99b5c4e2b7e..9cc261a7c9747253620d1836067f2e767e57fa79 100644 (file)
@@ -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
index 6efa6dabdd59570c9a7deebaa958b9e5102e5e86..1e6e64807d1b5e98dd4b6742de6ff72d78b1bc43 100644 (file)
@@ -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:
index 71aeecf13c1557b0b1d983a52a81bb294be6bf64..c69d837f9b93e276315869e2a5aa737e644a4e27 100644 (file)
@@ -72,32 +72,50 @@ 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 host(self):
-        return ''
+    def top_bound_segment(self):
+        return self._expand_segment(self._bound_segment)
 
     @property
-    def original_host(self):
+    def original_top_bound_segment(self):
         return None
 
     @property
-    def bound_driver(self):
+    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 original_bound_driver(self):
+    def host(self):
+        return ''
+
+    @property
+    def original_host(self):
         return None
 
+    @property
+    def segments_to_bind(self):
+        return self._network_context.network_segments
+
     def host_agents(self, agent_type):
         if agent_type == self._agent_type:
             return self._agents
@@ -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
 
index 58b5638c18617bd73f2873523e90215a7d065431..4459e9d49906419dc3aad69fb3b4bb42b70a5f44 100644 (file)
@@ -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):
index e1f6388ff508c6a1dbef42c80e286ff8a8120120..a0d1c390589ef5b90c440379e88344456c4abe3e 100644 (file)
@@ -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.
index 58100149fcfccf56c3ed6774d6b0b5ad0f67df2e..4f9cefdcfe97b30d162aa545b21247b4c734a1be 100644 (file)
@@ -93,7 +93,7 @@ class FakePortContext(object):
         return self._network
 
     @property
-    def bound_segment(self):
+    def bottom_bound_segment(self):
         return self._segment
 
 
index 401badb16632c5997897a4774ec77299f051f188..2199620806184eecb57a0bfa45decfa2c98a75fb 100644 (file)
@@ -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):
index 090dfad4370d9786382e246e437acf5ae9a26a27..324a873bf2ccd56a47e62f252fd684ed401eb508 100644 (file)
@@ -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})
index 2acf8dd1c2eebf6c15e74fd75d1eaed3f736a29f..77ac520159f7cbb80ff7d5eefc8e2fb579f9c07a 100644 (file)
@@ -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',