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):
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
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
@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
: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
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."""
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):
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,
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']
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
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):
# 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):
# 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)
{'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,
"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"),
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)
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)
# 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,
# 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,
# 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,
"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,
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,
"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,
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
{'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:
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
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
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):
# 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
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.
return self._network
@property
- def bound_segment(self):
+ def bottom_bound_segment(self):
return self._segment
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):
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:
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)
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)
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})
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',