Multiple port binding for ML2

Functionality is added to the ML2 plugin to handle multiple port
bindings

Co-Authored-By: Anindita Das <anindita.das@intel.com>
Co-Authored-By: Miguel Lavalle <miguel.lavalle@huawei.com>

Partial-Bug: #1580880

Change-Id: Ie31d4e27e3f55edfe334c4029ca9ed685e684c39
This commit is contained in:
Jakub Libosvar 2017-07-27 14:15:11 +00:00 committed by Miguel Lavalle
parent e8168345ef
commit f7b62a7f29
18 changed files with 870 additions and 64 deletions

View File

@ -29,6 +29,7 @@ from neutron.agent import resource_cache
from neutron.api.rpc.callbacks import resources from neutron.api.rpc.callbacks import resources
from neutron.common import constants as n_const from neutron.common import constants as n_const
from neutron.common import rpc as n_rpc from neutron.common import rpc as n_rpc
from neutron.common import utils
from neutron import objects from neutron import objects
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -239,6 +240,9 @@ class CacheBackedPluginApi(PluginApi):
# match format of old RPC interface # match format of old RPC interface
mac_addr = str(netaddr.EUI(str(port_obj.mac_address), mac_addr = str(netaddr.EUI(str(port_obj.mac_address),
dialect=netaddr.mac_unix_expanded)) dialect=netaddr.mac_unix_expanded))
binding = utils.get_port_binding_by_status_and_host(
port_obj.binding, constants.ACTIVE, raise_if_not_found=True,
port_id=port_obj.id)
entry = { entry = {
'device': device, 'device': device,
'network_id': port_obj.network_id, 'network_id': port_obj.network_id,
@ -259,7 +263,7 @@ class CacheBackedPluginApi(PluginApi):
'port_security_enabled', True), 'port_security_enabled', True),
'qos_policy_id': port_obj.qos_policy_id, 'qos_policy_id': port_obj.qos_policy_id,
'network_qos_policy_id': net_qos_policy_id, 'network_qos_policy_id': net_qos_policy_id,
'profile': port_obj.binding.profile, 'profile': binding.profile,
'security_groups': list(port_obj.security_group_ids) 'security_groups': list(port_obj.security_group_ids)
} }
LOG.debug("Returning: %s", entry) LOG.debug("Returning: %s", entry)

View File

@ -334,3 +334,23 @@ class FilterIDForIPNotFound(e.NotFound):
class FailedToAddQdiscToDevice(e.NeutronException): class FailedToAddQdiscToDevice(e.NeutronException):
message = _("Failed to add %(direction)s qdisc " message = _("Failed to add %(direction)s qdisc "
"to device %(device)s.") "to device %(device)s.")
class PortBindingNotFound(e.NotFound):
message = _("Binding for port %(port_id)s for host %(host)s could not be "
"found.")
class PortBindingAlreadyActive(e.Conflict):
message = _("Binding for port %(port_id)s on host %(host)s is already "
"active.")
class PortBindingAlreadyExists(e.Conflict):
message = _("Binding for port %(port_id)s on host %(host)s already "
"exists.")
class PortBindingError(e.NeutronException):
message = _("Binding for port %(port_id)s on host %(host)s could not be "
"created or updated.")

View File

@ -33,6 +33,7 @@ import uuid
import eventlet import eventlet
from eventlet.green import subprocess from eventlet.green import subprocess
import netaddr import netaddr
from neutron_lib.api.definitions import portbindings_extended as pb_ext
from neutron_lib import constants as n_const from neutron_lib import constants as n_const
from neutron_lib.utils import helpers from neutron_lib.utils import helpers
from oslo_config import cfg from oslo_config import cfg
@ -43,6 +44,7 @@ import six
import neutron import neutron
from neutron._i18n import _ from neutron._i18n import _
from neutron.common import exceptions
from neutron.db import api as db_api from neutron.db import api as db_api
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
@ -783,3 +785,36 @@ def bytes_to_bits(value):
def bits_to_kilobits(value, base): def bits_to_kilobits(value, base):
# NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result # NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result
return int((value + (base - 1)) / base) return int((value + (base - 1)) / base)
def get_port_binding_by_status_and_host(bindings, status, host='',
raise_if_not_found=False,
port_id=None):
"""Returns from an iterable the binding with the specified status and host.
The input iterable can contain zero or one binding in status ACTIVE
and zero or many bindings in status INACTIVE. As a consequence, to
unequivocally retrieve an inactive binding, the caller must specify a non
empty value for host. If host is the empty string, the first binding
satisfying the specified status will be returned. If no binding is found
with the specified status and host, None is returned or PortBindingNotFound
is raised if raise_if_not_found is True
:param bindings: An iterable containing port bindings
:param status: The status of the port binding to return. Possible values
are ACTIVE or INACTIVE as defined in
:file:`neutron_lib/constants.py`.
:param host: str representing the host of the binding to return.
:param raise_if_not_found: If a binding is not found and this parameter is
True, a PortBindingNotFound exception is raised
:param port_id: The id of the binding's port
:returns: The searched for port binding or None if it is not found
:raises: PortBindingNotFound if the binding is not found and
raise_if_not_found is True
"""
for binding in bindings:
if binding[pb_ext.STATUS] == status:
if not host or binding[pb_ext.HOST] == host:
return binding
if raise_if_not_found:
raise exceptions.PortBindingNotFound(port_id=port_id, host=host)

View File

@ -0,0 +1,77 @@
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.api.definitions import portbindings_extended as pbe_ext
from neutron_lib.api import extensions as api_extensions
from neutron_lib.plugins import directory
from neutron.api import extensions
from neutron.api.v2 import base
EXT_ALIAS = pbe_ext.ALIAS
class Portbindings_extended(api_extensions.ExtensionDescriptor):
"""Extension class supporting port bindings.
This class is used by neutron's extension framework to make
metadata about the port bindings available to external applications.
With admin rights one will be able to update and read the values.
"""
@classmethod
def get_name(cls):
return pbe_ext.NAME
@classmethod
def get_alias(cls):
return pbe_ext.ALIAS
@classmethod
def get_description(cls):
return pbe_ext.DESCRIPTION
@classmethod
def get_updated(cls):
return pbe_ext.UPDATED_TIMESTAMP
@classmethod
def get_resources(cls):
plugin = directory.get_plugin()
params = pbe_ext.SUB_RESOURCE_ATTRIBUTE_MAP[
pbe_ext.COLLECTION_NAME]['parameters']
parent = pbe_ext.SUB_RESOURCE_ATTRIBUTE_MAP[
pbe_ext.COLLECTION_NAME]['parent']
controller = base.create_resource(
pbe_ext.COLLECTION_NAME,
pbe_ext.RESOURCE_NAME,
plugin,
params,
member_actions=pbe_ext.ACTION_MAP[pbe_ext.RESOURCE_NAME],
parent=parent,
allow_pagination=True,
allow_sorting=True,
)
exts = [
extensions.ResourceExtension(
pbe_ext.COLLECTION_NAME,
controller,
parent,
member_actions=pbe_ext.ACTION_MAP[pbe_ext.RESOURCE_NAME],
attr_map=params,
),
]
return exts

View File

@ -264,7 +264,8 @@ class Port(base.NeutronDbObject):
# Version 1.1: Add data_plane_status field # Version 1.1: Add data_plane_status field
# Version 1.2: Added segment_id to binding_levels # Version 1.2: Added segment_id to binding_levels
# Version 1.3: distributed_binding -> distributed_bindings # Version 1.3: distributed_binding -> distributed_bindings
VERSION = '1.3' # Version 1.4: Attribute binding becomes ListOfObjectsField
VERSION = '1.4'
db_model = models_v2.Port db_model = models_v2.Port
@ -282,7 +283,7 @@ class Port(base.NeutronDbObject):
'allowed_address_pairs': obj_fields.ListOfObjectsField( 'allowed_address_pairs': obj_fields.ListOfObjectsField(
'AllowedAddressPair', nullable=True 'AllowedAddressPair', nullable=True
), ),
'binding': obj_fields.ObjectField( 'binding': obj_fields.ListOfObjectsField(
'PortBinding', nullable=True 'PortBinding', nullable=True
), ),
'data_plane_status': obj_fields.ObjectField( 'data_plane_status': obj_fields.ObjectField(
@ -473,6 +474,19 @@ class Port(base.NeutronDbObject):
bindings = primitive.pop('distributed_bindings', []) bindings = primitive.pop('distributed_bindings', [])
primitive['distributed_binding'] = (bindings[0] primitive['distributed_binding'] = (bindings[0]
if bindings else None) if bindings else None)
if _target_version < (1, 4):
# In version 1.4 we add support for multiple port bindings.
# Previous versions only support one port binding. The following
# lines look for the active port binding, which is the only one
# needed in previous versions
if 'binding' in primitive:
original_binding = primitive['binding']
primitive['binding'] = None
for a_binding in original_binding:
if (a_binding['versioned_object.data']['status'] ==
constants.ACTIVE):
primitive['binding'] = a_binding
break
@classmethod @classmethod
def get_ports_by_router(cls, context, router_id, owner, subnet): def get_ports_by_router(cls, context, router_id, owner, subnet):

View File

@ -50,6 +50,11 @@ class InstanceSnapshot(object):
session.add(self._model_class(**{col: getattr(self, col) session.add(self._model_class(**{col: getattr(self, col)
for col in self._cols})) for col in self._cols}))
def __getitem__(self, item):
if item not in self._cols:
raise KeyError(item)
return getattr(self, item)
class MechanismDriverContext(object): class MechanismDriverContext(object):
"""MechanismDriver context base class.""" """MechanismDriver context base class."""

View File

@ -58,8 +58,10 @@ class PortBinding(model_base.BASEV2):
port = orm.relationship( port = orm.relationship(
models_v2.Port, models_v2.Port,
load_on_pending=True, load_on_pending=True,
# TODO(mlavalle) change name of the relationship to reflect that it is
# now an iterable
backref=orm.backref("port_binding", backref=orm.backref("port_binding",
lazy='joined', uselist=False, lazy='joined',
cascade='delete')) cascade='delete'))
revises_on_change = ('port', ) revises_on_change = ('port', )

View File

@ -23,6 +23,7 @@ from neutron_lib.api.definitions import network_mtu_writable as mtuw_apidef
from neutron_lib.api.definitions import port as port_def from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import portbindings_extended as pbe_ext
from neutron_lib.api.definitions import subnet as subnet_def from neutron_lib.api.definitions import subnet as subnet_def
from neutron_lib.api.definitions import vlantransparent as vlan_apidef from neutron_lib.api.definitions import vlantransparent as vlan_apidef
from neutron_lib.api import extensions from neutron_lib.api import extensions
@ -64,6 +65,7 @@ from neutron.api.rpc.handlers import metadata_rpc
from neutron.api.rpc.handlers import resources_rpc from neutron.api.rpc.handlers import resources_rpc
from neutron.api.rpc.handlers import securitygroups_rpc from neutron.api.rpc.handlers import securitygroups_rpc
from neutron.common import constants as n_const from neutron.common import constants as n_const
from neutron.common import exceptions as n_exc
from neutron.common import rpc as n_rpc from neutron.common import rpc as n_rpc
from neutron.common import utils from neutron.common import utils
from neutron.db import _model_query as model_query from neutron.db import _model_query as model_query
@ -87,6 +89,8 @@ from neutron.db import subnet_service_type_mixin
from neutron.db import vlantransparent_db from neutron.db import vlantransparent_db
from neutron.extensions import providernet as provider from neutron.extensions import providernet as provider
from neutron.extensions import vlantransparent from neutron.extensions import vlantransparent
from neutron.objects import base as base_obj
from neutron.objects import ports as ports_obj
from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import db from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_context from neutron.plugins.ml2 import driver_context
@ -113,7 +117,7 @@ def _ml2_port_result_filter_hook(query, filters):
if not values: if not values:
return query return query
bind_criteria = models.PortBinding.host.in_(values) bind_criteria = models.PortBinding.host.in_(values)
return query.filter(models_v2.Port.port_binding.has(bind_criteria)) return query.filter(models_v2.Port.port_binding.any(bind_criteria))
@resource_extend.has_resource_extenders @resource_extend.has_resource_extenders
@ -161,7 +165,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"ip-substring-filtering", "ip-substring-filtering",
"port-security-groups-filtering", "port-security-groups-filtering",
"empty-string-filtering", "empty-string-filtering",
"port-mac-address-regenerate"] "port-mac-address-regenerate",
"binding-extended"]
@property @property
def supported_extension_aliases(self): def supported_extension_aliases(self):
@ -241,12 +246,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
**kwargs): **kwargs):
port_id = object_id port_id = object_id
port = db.get_port(context, port_id) port = db.get_port(context, port_id)
if not port or not port.port_binding: port_binding = utils.get_port_binding_by_status_and_host(
getattr(port, 'port_binding', []), const.ACTIVE)
if not port or not port_binding:
LOG.debug("Port %s was deleted so its status cannot be updated.", LOG.debug("Port %s was deleted so its status cannot be updated.",
port_id) port_id)
return return
if port.port_binding.vif_type in (portbindings.VIF_TYPE_BINDING_FAILED, if port_binding.vif_type in (portbindings.VIF_TYPE_BINDING_FAILED,
portbindings.VIF_TYPE_UNBOUND): portbindings.VIF_TYPE_UNBOUND):
# NOTE(kevinbenton): we hit here when a port is created without # NOTE(kevinbenton): we hit here when a port is created without
# a host ID and the dhcp agent notifies that its wiring is done # a host ID and the dhcp agent notifies that its wiring is done
LOG.debug("Port %s cannot update to ACTIVE because it " LOG.debug("Port %s cannot update to ACTIVE because it "
@ -320,13 +327,15 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
new_mac=port['mac_address']) new_mac=port['mac_address'])
return mac_change return mac_change
def _process_port_binding(self, mech_context, attrs): def _clear_port_binding(self, mech_context, binding, port, original_host):
plugin_context = mech_context._plugin_context binding.vif_type = portbindings.VIF_TYPE_UNBOUND
binding = mech_context._binding binding.vif_details = ''
port = mech_context.current db.clear_binding_levels(mech_context._plugin_context, port['id'],
port_id = port['id'] original_host)
changes = False mech_context._clear_binding_levels()
def _process_port_binding_attributes(self, binding, attrs):
changes = False
host = const.ATTR_NOT_SPECIFIED host = const.ATTR_NOT_SPECIFIED
if attrs and portbindings.HOST_ID in attrs: if attrs and portbindings.HOST_ID in attrs:
host = attrs.get(portbindings.HOST_ID) or '' host = attrs.get(portbindings.HOST_ID) or ''
@ -354,23 +363,28 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
msg = _("binding:profile value too large") msg = _("binding:profile value too large")
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
changes = True changes = True
return changes, original_host
def _process_port_binding(self, mech_context, attrs):
plugin_context = mech_context._plugin_context
binding = mech_context._binding
port = mech_context.current
changes, original_host = self._process_port_binding_attributes(binding,
attrs)
# Unbind the port if needed. # Unbind the port if needed.
if changes: if changes:
binding.vif_type = portbindings.VIF_TYPE_UNBOUND self._clear_port_binding(mech_context, binding, port,
binding.vif_details = '' original_host)
db.clear_binding_levels(plugin_context, port_id, original_host)
mech_context._clear_binding_levels()
port['status'] = const.PORT_STATUS_DOWN port['status'] = const.PORT_STATUS_DOWN
super(Ml2Plugin, self).update_port( super(Ml2Plugin, self).update_port(
mech_context._plugin_context, port_id, mech_context._plugin_context, port['id'],
{port_def.RESOURCE_NAME: {'status': const.PORT_STATUS_DOWN}}) {port_def.RESOURCE_NAME:
{'status': const.PORT_STATUS_DOWN}})
if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE: if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
binding.vif_type = portbindings.VIF_TYPE_UNBOUND self._clear_port_binding(mech_context, binding, port,
binding.vif_details = '' original_host)
db.clear_binding_levels(plugin_context, port_id, original_host)
mech_context._clear_binding_levels()
binding.host = '' binding.host = ''
self._update_port_dict_binding(port, binding) self._update_port_dict_binding(port, binding)
@ -379,7 +393,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
@db_api.retry_db_errors @db_api.retry_db_errors
def _bind_port_if_needed(self, context, allow_notify=False, def _bind_port_if_needed(self, context, allow_notify=False,
need_notify=False): need_notify=False, allow_commit=True):
if not context.network.network_segments: if not context.network.network_segments:
LOG.debug("Network %s has no segments, skipping binding", LOG.debug("Network %s has no segments, skipping binding",
context.network.current['id']) context.network.current['id'])
@ -399,7 +413,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
context, need_notify) context, need_notify)
if count == MAX_BIND_TRIES or not try_again: if count == MAX_BIND_TRIES or not try_again:
if self._should_bind_port(context): if self._should_bind_port(context) and allow_commit:
# At this point, we attempted to bind a port and reached # At this point, we attempted to bind a port and reached
# its final binding state. Binding either succeeded or # its final binding state. Binding either succeeded or
# exhausted all attempts, thus no need to try again. # exhausted all attempts, thus no need to try again.
@ -488,7 +502,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# mechanism driver update_port_*commit() calls. # mechanism driver update_port_*commit() calls.
try: try:
port_db = self._get_port(plugin_context, port_id) port_db = self._get_port(plugin_context, port_id)
cur_binding = port_db.port_binding cur_binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
except exc.PortNotFound: except exc.PortNotFound:
port_db, cur_binding = None, None port_db, cur_binding = None, None
if not port_db or not cur_binding: if not port_db or not cur_binding:
@ -532,8 +547,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# to optimize this code to avoid fetching it. # to optimize this code to avoid fetching it.
cur_binding = db.get_distributed_port_binding_by_host( cur_binding = db.get_distributed_port_binding_by_host(
plugin_context, port_id, orig_binding.host) plugin_context, port_id, orig_binding.host)
cur_context_binding = cur_binding
if new_binding.status == const.INACTIVE:
cur_context_binding = (
utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.INACTIVE,
host=new_binding.host))
cur_context = driver_context.PortContext( cur_context = driver_context.PortContext(
self, plugin_context, port, network, cur_binding, None, self, plugin_context, port, network, cur_context_binding, None,
original_port=oport) original_port=oport)
# Commit our binding results only if port has not been # Commit our binding results only if port has not been
@ -549,20 +570,24 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
if commit: if commit:
# Update the port's binding state with our binding # Update the port's binding state with our binding
# results. # results.
cur_binding.vif_type = new_binding.vif_type if new_binding.status == const.INACTIVE:
cur_binding.vif_details = new_binding.vif_details cur_context_binding.status = const.ACTIVE
cur_binding.status = const.INACTIVE
else:
cur_context_binding.vif_type = new_binding.vif_type
cur_context_binding.vif_details = new_binding.vif_details
db.clear_binding_levels(plugin_context, port_id, db.clear_binding_levels(plugin_context, port_id,
cur_binding.host) cur_binding.host)
db.set_binding_levels(plugin_context, db.set_binding_levels(plugin_context,
bind_context._binding_levels) bind_context._binding_levels)
# refresh context with a snapshot of updated state # refresh context with a snapshot of updated state
cur_context._binding = driver_context.InstanceSnapshot( cur_context._binding = driver_context.InstanceSnapshot(
cur_binding) cur_context_binding)
cur_context._binding_levels = bind_context._binding_levels cur_context._binding_levels = bind_context._binding_levels
# Update PortContext's port dictionary to reflect the # Update PortContext's port dictionary to reflect the
# updated binding state. # updated binding state.
self._update_port_dict_binding(port, cur_binding) self._update_port_dict_binding(port, cur_context_binding)
# Update the port status if requested by the bound driver. # Update the port status if requested by the bound driver.
if (bind_context._binding_levels and if (bind_context._binding_levels and
@ -632,9 +657,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
@resource_extend.extends([port_def.COLLECTION_NAME]) @resource_extend.extends([port_def.COLLECTION_NAME])
def _ml2_extend_port_dict_binding(port_res, port_db): def _ml2_extend_port_dict_binding(port_res, port_db):
plugin = directory.get_plugin() plugin = directory.get_plugin()
port_binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
# None when called during unit tests for other plugins. # None when called during unit tests for other plugins.
if port_db.port_binding: if port_binding:
plugin._update_port_dict_binding(port_res, port_db.port_binding) plugin._update_port_dict_binding(port_res, port_binding)
# ML2's resource extend functions allow extension drivers that extend # ML2's resource extend functions allow extension drivers that extend
# attributes for the resources to add those attributes to the result. # attributes for the resources to add those attributes to the result.
@ -1304,7 +1331,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
original_port=original_port) original_port=original_port)
with db_api.context_manager.writer.using(context): with db_api.context_manager.writer.using(context):
port_db = self._get_port(context, id) port_db = self._get_port(context, id)
binding = port_db.port_binding binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
if not binding: if not binding:
raise exc.PortNotFound(port_id=id) raise exc.PortNotFound(port_id=id)
mac_address_updated = self._check_mac_update_allowed( mac_address_updated = self._check_mac_update_allowed(
@ -1522,7 +1550,9 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
with db_api.context_manager.writer.using(context): with db_api.context_manager.writer.using(context):
try: try:
port_db = self._get_port(context, id) port_db = self._get_port(context, id)
binding = port_db.port_binding binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE,
raise_if_not_found=True, port_id=id)
except exc.PortNotFound: except exc.PortNotFound:
LOG.debug("The port '%s' was deleted", id) LOG.debug("The port '%s' was deleted", id)
return return
@ -1640,14 +1670,15 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# related attribute port_binding could disappear in # related attribute port_binding could disappear in
# concurrent port deletion. # concurrent port deletion.
# It's not an error condition. # It's not an error condition.
binding = port_db.port_binding binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
if not binding: if not binding:
LOG.info("Binding info for port %s was not found, " LOG.info("Binding info for port %s was not found, "
"it might have been deleted already.", "it might have been deleted already.",
port_id) port_id)
return return
levels = db.get_binding_levels(plugin_context, port_db.id, levels = db.get_binding_levels(plugin_context, port_db.id,
port_db.port_binding.host) binding.host)
port_context = driver_context.PortContext( port_context = driver_context.PortContext(
self, plugin_context, port, network, binding, levels) self, plugin_context, port, network, binding, levels)
@ -1683,7 +1714,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
plugin_context, port['id'], host) plugin_context, port['id'], host)
bindlevelhost_match = host bindlevelhost_match = host
else: else:
binding = port_db.port_binding binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
bindlevelhost_match = binding.host if binding else None bindlevelhost_match = binding.host if binding else None
if not binding: if not binding:
LOG.info("Binding info for port %s was not found, " LOG.info("Binding info for port %s was not found, "
@ -1768,11 +1800,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# listening for db events can modify the port if necessary # listening for db events can modify the port if necessary
context.session.flush() context.session.flush()
updated_port = self._make_port_dict(port) updated_port = self._make_port_dict(port)
levels = db.get_binding_levels(context, port.id, binding = utils.get_port_binding_by_status_and_host(
port.port_binding.host) port.port_binding, const.ACTIVE, raise_if_not_found=True,
port_id=port_id)
levels = db.get_binding_levels(context, port.id, binding.host)
mech_context = driver_context.PortContext( mech_context = driver_context.PortContext(
self, context, updated_port, network, port.port_binding, self, context, updated_port, network, binding, levels,
levels, original_port=original_port) original_port=original_port)
self.mechanism_manager.update_port_precommit(mech_context) self.mechanism_manager.update_port_precommit(mech_context)
updated = True updated = True
elif port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE: elif port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
@ -1964,3 +1998,213 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self.mechanism_manager.update_network_precommit(mech_context) self.mechanism_manager.update_network_precommit(mech_context)
elif event == events.AFTER_CREATE or event == events.AFTER_DELETE: elif event == events.AFTER_CREATE or event == events.AFTER_DELETE:
self.mechanism_manager.update_network_postcommit(mech_context) self.mechanism_manager.update_network_postcommit(mech_context)
@staticmethod
def _validate_compute_port(port):
if not port['device_owner'].startswith(
const.DEVICE_OWNER_COMPUTE_PREFIX):
msg = _('Invalid port %s. Operation only valid on compute '
'ports') % port['id']
raise exc.BadRequest(resource='port', msg=msg)
def _make_port_binding_dict(self, binding, fields=None):
res = {key: binding[key] for key in (
pbe_ext.HOST, pbe_ext.VIF_TYPE, pbe_ext.VNIC_TYPE,
pbe_ext.STATUS)}
if isinstance(binding, ports_obj.PortBinding):
res[pbe_ext.PROFILE] = binding.profile or {}
res[pbe_ext.VIF_DETAILS] = binding.vif_details or {}
else:
res[pbe_ext.PROFILE] = self._get_profile(binding)
res[pbe_ext.VIF_DETAILS] = self._get_vif_details(binding)
return db_utils.resource_fields(res, fields)
def _get_port_binding_attrs(self, binding, host=None):
return {portbindings.VNIC_TYPE: binding.get(pbe_ext.VNIC_TYPE),
portbindings.HOST_ID: binding.get(pbe_ext.HOST) or host,
portbindings.PROFILE: binding.get(pbe_ext.PROFILE, {})}
def _process_active_binding_change(self, changes, mech_context, port_dict,
original_host):
if changes:
self._clear_port_binding(mech_context,
mech_context._binding, port_dict,
original_host)
port_dict['status'] = const.PORT_STATUS_DOWN
super(Ml2Plugin, self).update_port(
mech_context._plugin_context, port_dict['id'],
{port_def.RESOURCE_NAME:
{'status': const.PORT_STATUS_DOWN}})
self._update_port_dict_binding(port_dict,
mech_context._binding)
mech_context._binding.persist_state_to_session(
mech_context._plugin_context.session)
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def create_port_binding(self, context, port_id, binding):
attrs = binding[pbe_ext.RESOURCE_NAME]
with db_api.context_manager.writer.using(context):
port_db = self._get_port(context, port_id)
self._validate_compute_port(port_db)
if self._get_binding_for_host(port_db.port_binding,
attrs[pbe_ext.HOST]):
raise n_exc.PortBindingAlreadyExists(
port_id=port_id, host=attrs[pbe_ext.HOST])
status = const.ACTIVE
is_active_binding = True
active_binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
if active_binding:
status = const.INACTIVE
is_active_binding = False
network = self.get_network(context, port_db['network_id'])
port_dict = self._make_port_dict(port_db)
new_binding = models.PortBinding(
port_id=port_id,
vif_type=portbindings.VIF_TYPE_UNBOUND,
status=status)
mech_context = driver_context.PortContext(self, context, port_dict,
network, new_binding,
None)
changes, original_host = self._process_port_binding_attributes(
mech_context._binding, self._get_port_binding_attrs(attrs))
if is_active_binding:
self._process_active_binding_change(changes, mech_context,
port_dict, original_host)
bind_context = self._bind_port_if_needed(
mech_context, allow_commit=is_active_binding)
if (bind_context._binding.vif_type ==
portbindings.VIF_TYPE_BINDING_FAILED):
raise n_exc.PortBindingError(port_id=port_id,
host=attrs[pbe_ext.HOST])
bind_context._binding.port_id = port_id
bind_context._binding.status = status
if not is_active_binding:
with db_api.context_manager.writer.using(context):
bind_context._binding.persist_state_to_session(context.session)
db.set_binding_levels(context, bind_context._binding_levels)
return self._make_port_binding_dict(bind_context._binding)
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def get_port_bindings(self, context, port_id, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
port = ports_obj.Port.get_object(context, id=port_id)
if not port:
raise exc.PortNotFound(port_id=port_id)
self._validate_compute_port(port)
filters = filters or {}
pager = base_obj.Pager(sorts, limit, page_reverse, marker)
bindings = ports_obj.PortBinding.get_objects(
context, _pager=pager, port_id=port_id, **filters)
return [self._make_port_binding_dict(binding, fields)
for binding in bindings]
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def get_port_binding(self, context, host, port_id, fields=None):
port = ports_obj.Port.get_object(context, id=port_id)
if not port:
raise exc.PortNotFound(port_id=port_id)
self._validate_compute_port(port)
binding = ports_obj.PortBinding.get_object(context, host=host,
port_id=port_id)
if not binding:
raise n_exc.PortBindingNotFound(port_id=port_id, host=host)
return self._make_port_binding_dict(binding, fields)
def _get_binding_for_host(self, bindings, host):
for binding in bindings:
if binding.host == host:
return binding
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def update_port_binding(self, context, host, port_id, binding):
attrs = binding[pbe_ext.RESOURCE_NAME]
with db_api.context_manager.writer.using(context):
port_db = self._get_port(context, port_id)
self._validate_compute_port(port_db)
original_binding = self._get_binding_for_host(port_db.port_binding,
host)
if not original_binding:
raise n_exc.PortBindingNotFound(port_id=port_id, host=host)
is_active_binding = (original_binding.status == const.ACTIVE)
network = self.get_network(context, port_db['network_id'])
port_dict = self._make_port_dict(port_db)
mech_context = driver_context.PortContext(self, context, port_dict,
network,
original_binding, None)
changes, original_host = self._process_port_binding_attributes(
mech_context._binding, self._get_port_binding_attrs(attrs,
host=host))
if is_active_binding:
self._process_active_binding_change(changes, mech_context,
port_dict, original_host)
bind_context = self._bind_port_if_needed(
mech_context, allow_commit=is_active_binding)
if (bind_context._binding.vif_type ==
portbindings.VIF_TYPE_BINDING_FAILED):
raise n_exc.PortBindingError(port_id=port_id, host=host)
if not is_active_binding:
with db_api.context_manager.writer.using(context):
bind_context._binding.persist_state_to_session(context.session)
db.set_binding_levels(context, bind_context._binding_levels)
return self._make_port_binding_dict(bind_context._binding)
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def activate(self, context, host, port_id):
with db_api.context_manager.writer.using(context):
# TODO(mlavalle) Next two lines can be removed when bug #1770267 is
# fixed
if isinstance(port_id, dict):
port_id = port_id['port_id']
port_db = self._get_port(context, port_id)
self._validate_compute_port(port_db)
active_binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.ACTIVE)
if host == (active_binding and active_binding.host):
raise n_exc.PortBindingAlreadyActive(port_id=port_id,
host=host)
inactive_binding = utils.get_port_binding_by_status_and_host(
port_db.port_binding, const.INACTIVE, host=host)
if not inactive_binding or inactive_binding.host != host:
raise n_exc.PortBindingNotFound(port_id=port_id, host=host)
network = self.get_network(context, port_db['network_id'])
port_dict = self._make_port_dict(port_db)
levels = db.get_binding_levels(context, port_id,
active_binding.host)
original_context = driver_context.PortContext(self, context,
port_dict, network,
active_binding,
levels)
self._clear_port_binding(original_context, active_binding,
port_dict, active_binding.host)
port_dict['status'] = const.PORT_STATUS_DOWN
super(Ml2Plugin, self).update_port(
context, port_dict['id'],
{port_def.RESOURCE_NAME:
{'status': const.PORT_STATUS_DOWN}})
levels = db.get_binding_levels(context, port_id,
inactive_binding.host)
bind_context = driver_context.PortContext(self, context, port_dict,
network,
inactive_binding, levels)
for count in range(MAX_BIND_TRIES):
cur_context, _, try_again = self._commit_port_binding(
original_context, bind_context, need_notify=True,
try_again=True)
if not try_again:
self.notifier.port_delete(context, port_id)
return self._make_port_binding_dict(cur_context._binding)
raise n_exc.PortBindingError(port_id=port_id, host=host)
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def delete_port_binding(self, context, host, port_id):
ports_obj.PortBinding.delete_objects(context, host=host,
port_id=port_id)

View File

@ -14,11 +14,13 @@
# under the License. # under the License.
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
from neutron_lib import constants
from neutron_lib.plugins import constants as plugin_const from neutron_lib.plugins import constants as plugin_const
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from oslo_log import log as logging from oslo_log import log as logging
from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import exc as orm_exc
from neutron.common import utils
from neutron.db import _utils as db_utils from neutron.db import _utils as db_utils
from neutron.db.models import securitygroup as sg_db from neutron.db.models import securitygroup as sg_db
from neutron.objects import ports from neutron.objects import ports
@ -93,13 +95,16 @@ def validate_log_type_for_port(log_type, port):
log_plugin = directory.get_plugin(alias=plugin_const.LOG_API) log_plugin = directory.get_plugin(alias=plugin_const.LOG_API)
drivers = log_plugin.driver_manager.drivers drivers = log_plugin.driver_manager.drivers
port_binding = utils.get_port_binding_by_status_and_host(
port.binding, constants.ACTIVE, raise_if_not_found=True,
port_id=port['id'])
for driver in drivers: for driver in drivers:
vif_type = port.binding.vif_type vif_type = port_binding.vif_type
if vif_type not in SKIPPED_VIF_TYPES: if vif_type not in SKIPPED_VIF_TYPES:
if not _validate_vif_type(driver, vif_type, port['id']): if not _validate_vif_type(driver, vif_type, port['id']):
continue continue
else: else:
vnic_type = port.binding.vnic_type vnic_type = port_binding.vnic_type
if not _validate_vnic_type(driver, vnic_type, port['id']): if not _validate_vnic_type(driver, vnic_type, port['id']):
continue continue

View File

@ -13,6 +13,7 @@
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
from neutron_lib.callbacks import events from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry from neutron_lib.callbacks import registry
from neutron_lib import constants as lib_constants
from neutron_lib.services.qos import constants as qos_consts from neutron_lib.services.qos import constants as qos_consts
from oslo_log import log as logging from oslo_log import log as logging
@ -22,6 +23,7 @@ from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import resources_rpc from neutron.api.rpc.handlers import resources_rpc
from neutron.common import constants from neutron.common import constants
from neutron.common import exceptions from neutron.common import exceptions
from neutron.common import utils
from neutron.objects.qos import policy as policy_object from neutron.objects.qos import policy as policy_object
@ -134,13 +136,16 @@ class QosServiceDriverManager(object):
self.rpc_notifications_required |= driver.requires_rpc_notifications self.rpc_notifications_required |= driver.requires_rpc_notifications
def validate_rule_for_port(self, rule, port): def validate_rule_for_port(self, rule, port):
port_binding = utils.get_port_binding_by_status_and_host(
port.binding, lib_constants.ACTIVE, raise_if_not_found=True,
port_id=port['id'])
for driver in self._drivers: for driver in self._drivers:
vif_type = port.binding.vif_type vif_type = port_binding.vif_type
if vif_type not in SKIPPED_VIF_TYPES: if vif_type not in SKIPPED_VIF_TYPES:
if not self._validate_vif_type(driver, vif_type, port['id']): if not self._validate_vif_type(driver, vif_type, port['id']):
continue continue
else: else:
vnic_type = port.binding.vnic_type vnic_type = port_binding.vnic_type
if not self._validate_vnic_type(driver, vnic_type, port['id']): if not self._validate_vnic_type(driver, vnic_type, port['id']):
continue continue

View File

@ -6,6 +6,7 @@ NETWORK_API_EXTENSIONS+=",allowed-address-pairs"
NETWORK_API_EXTENSIONS+=",auto-allocated-topology" NETWORK_API_EXTENSIONS+=",auto-allocated-topology"
NETWORK_API_EXTENSIONS+=",availability_zone" NETWORK_API_EXTENSIONS+=",availability_zone"
NETWORK_API_EXTENSIONS+=",binding" NETWORK_API_EXTENSIONS+=",binding"
NETWORK_API_EXTENSIONS+=",binding-extended"
NETWORK_API_EXTENSIONS+=",default-subnetpools" NETWORK_API_EXTENSIONS+=",default-subnetpools"
NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler" NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler"
NETWORK_API_EXTENSIONS+=",dns-integration" NETWORK_API_EXTENSIONS+=",dns-integration"

View File

@ -21,14 +21,18 @@ import ddt
import eventlet import eventlet
import mock import mock
import netaddr import netaddr
from neutron_lib.api.definitions import portbindings_extended as pb_ext
from neutron_lib import constants from neutron_lib import constants
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import uuidutils
import six import six
import testscenarios import testscenarios
import testtools import testtools
from neutron.common import constants as common_constants from neutron.common import constants as common_constants
from neutron.common import exceptions
from neutron.common import utils from neutron.common import utils
from neutron.objects import ports
from neutron.tests import base from neutron.tests import base
from neutron.tests.unit import tests from neutron.tests.unit import tests
@ -524,3 +528,31 @@ class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
expected_kilobits, expected_kilobits,
utils.bits_to_kilobits(input_bits, self.base_unit) utils.bits_to_kilobits(input_bits, self.base_unit)
) )
class TestGetPortBindingByStatusAndHost(base.BaseTestCase):
def test_get_port_binding_by_status_and_host(self):
bindings = []
self.assertIsNone(utils.get_port_binding_by_status_and_host(
bindings, constants.INACTIVE))
bindings.extend([ports.PortBinding(
port_id=uuidutils.generate_uuid(), host='host-1',
status=constants.INACTIVE),
ports.PortBinding(
port_id=uuidutils.generate_uuid(), host='host-2',
status=constants.INACTIVE)])
self.assertEqual(
'host-1', utils.get_port_binding_by_status_and_host(
bindings,
constants.INACTIVE)[pb_ext.HOST])
self.assertEqual(
'host-2', utils.get_port_binding_by_status_and_host(
bindings,
constants.INACTIVE,
host='host-2')[pb_ext.HOST])
self.assertIsNone(utils.get_port_binding_by_status_and_host(
bindings, constants.ACTIVE))
self.assertRaises(exceptions.PortBindingNotFound,
utils.get_port_binding_by_status_and_host, bindings,
constants.ACTIVE, 'host', True, 'port_id')

View File

@ -62,7 +62,7 @@ object_data = {
'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', 'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
'NetworkRBAC': '1.0-c8a67f39809c5a3c8c7f26f2f2c620b2', 'NetworkRBAC': '1.0-c8a67f39809c5a3c8c7f26f2f2c620b2',
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8', 'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
'Port': '1.3-4cb798ffc8b08f2657c0bd8afa708e9e', 'Port': '1.4-c3937b92962d5b43a09a7de2f44e0ab7',
'PortBinding': '1.0-3306deeaa6deb01e33af06777d48d578', 'PortBinding': '1.0-3306deeaa6deb01e33af06777d48d578',
'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5', 'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5',
'PortDataPlaneStatus': '1.0-25be74bda46c749653a10357676c0ab2', 'PortDataPlaneStatus': '1.0-25be74bda46c749653a10357676c0ab2',

View File

@ -11,6 +11,7 @@
# under the License. # under the License.
import mock import mock
from neutron_lib import constants
from oslo_utils import uuidutils from oslo_utils import uuidutils
import testscenarios import testscenarios
@ -418,3 +419,36 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
binding_data = ( binding_data = (
port_data['distributed_binding']['versioned_object.data']) port_data['distributed_binding']['versioned_object.data'])
self.assertEqual(binding.host, binding_data['host']) self.assertEqual(binding.host, binding_data['host'])
def test_v1_4_to_v1_3_converts_binding_to_portbinding_object(self):
port_v1_4 = self._create_test_port()
port_v1_3 = port_v1_4.obj_to_primitive(target_version='1.3')
# Port has no bindings, so binding attribute should be None
self.assertIsNone(port_v1_3['versioned_object.data']['binding'])
active_binding = ports.PortBinding(self.context, port_id=port_v1_4.id,
host='host1', vif_type='type')
inactive_binding = ports.PortBinding(
self.context, port_id=port_v1_4.id, host='host2', vif_type='type',
status=constants.INACTIVE)
active_binding.create()
inactive_binding.create()
port_v1_4 = ports.Port.get_object(self.context, id=port_v1_4.id)
port_v1_3 = port_v1_4.obj_to_primitive(target_version='1.3')
binding = port_v1_3['versioned_object.data']['binding']
# Port has active binding, so the binding attribute should point to it
self.assertEqual('host1', binding['versioned_object.data']['host'])
active_binding.delete()
port_v1_4 = ports.Port.get_object(self.context, id=port_v1_4.id)
port_v1_3 = port_v1_4.obj_to_primitive(target_version='1.3')
# Port has no active bindings, so binding attribute should be None
self.assertIsNone(port_v1_3['versioned_object.data']['binding'])
# Port with no binding attribute should be handled without raising
# exception
primitive = port_v1_4.obj_to_primitive()
primitive['versioned_object.data'].pop('binding')
port_v1_4_no_binding = port_v1_4.obj_from_primitive(primitive)
port_v1_4_no_binding.obj_to_primitive(target_version='1.3')

View File

@ -738,9 +738,14 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
plugin = directory.get_plugin() plugin = directory.get_plugin()
ups = mock.patch.object(plugin, 'update_port_status').start() ups = mock.patch.object(plugin, 'update_port_status').start()
port_id = 'fake_port_id' port_id = 'fake_port_id'
binding = mock.Mock(vif_type=portbindings.VIF_TYPE_OVS)
port = mock.Mock( def getitem(key):
id=port_id, admin_state_up=False, port_binding=binding) return constants.ACTIVE
binding = mock.MagicMock(vif_type=portbindings.VIF_TYPE_OVS)
binding.__getitem__.side_effect = getitem
port = mock.MagicMock(
id=port_id, admin_state_up=False, port_binding=[binding])
with mock.patch('neutron.plugins.ml2.plugin.db.get_port', with mock.patch('neutron.plugins.ml2.plugin.db.get_port',
return_value=port): return_value=port):
plugin._port_provisioned('port', 'evt', 'trigger', plugin._port_provisioned('port', 'evt', 'trigger',
@ -1801,8 +1806,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
# create a port and delete it so we have an expired mechanism context # create a port and delete it so we have an expired mechanism context
with self.port() as port: with self.port() as port:
plugin = directory.get_plugin() plugin = directory.get_plugin()
binding = plugin._get_port(self.context, binding = utils.get_port_binding_by_status_and_host(
port['port']['id']).port_binding plugin._get_port(self.context,
port['port']['id']).port_binding,
constants.ACTIVE)
binding['host'] = 'test' binding['host'] = 'test'
mech_context = driver_context.PortContext( mech_context = driver_context.PortContext(
plugin, self.context, port['port'], plugin, self.context, port['port'],
@ -1822,8 +1829,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
def _create_port_and_bound_context(self, port_vif_type, bound_vif_type): def _create_port_and_bound_context(self, port_vif_type, bound_vif_type):
with self.port() as port: with self.port() as port:
plugin = directory.get_plugin() plugin = directory.get_plugin()
binding = plugin._get_port( binding = utils.get_port_binding_by_status_and_host(
self.context, port['port']['id']).port_binding plugin._get_port(self.context,
port['port']['id']).port_binding,
constants.ACTIVE)
binding['host'] = 'fake_host' binding['host'] = 'fake_host'
binding['vif_type'] = port_vif_type binding['vif_type'] = port_vif_type
# Generates port context to be used before the bind. # Generates port context to be used before the bind.
@ -1937,8 +1946,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
def test_update_port_binding_host_id_none(self): def test_update_port_binding_host_id_none(self):
with self.port() as port: with self.port() as port:
plugin = directory.get_plugin() plugin = directory.get_plugin()
binding = plugin._get_port( binding = utils.get_port_binding_by_status_and_host(
self.context, port['port']['id']).port_binding plugin._get_port(self.context,
port['port']['id']).port_binding,
constants.ACTIVE)
with self.context.session.begin(subtransactions=True): with self.context.session.begin(subtransactions=True):
binding.host = 'test' binding.host = 'test'
mech_context = driver_context.PortContext( mech_context = driver_context.PortContext(
@ -1957,8 +1968,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
def test_update_port_binding_host_id_not_changed(self): def test_update_port_binding_host_id_not_changed(self):
with self.port() as port: with self.port() as port:
plugin = directory.get_plugin() plugin = directory.get_plugin()
binding = plugin._get_port( binding = utils.get_port_binding_by_status_and_host(
self.context, port['port']['id']).port_binding plugin._get_port(self.context,
port['port']['id']).port_binding,
constants.ACTIVE)
binding['host'] = 'test' binding['host'] = 'test'
mech_context = driver_context.PortContext( mech_context = driver_context.PortContext(
plugin, self.context, port['port'], plugin, self.context, port['port'],
@ -2944,8 +2957,10 @@ class TestML2Segments(Ml2PluginV2TestCase):
# add writer here to make sure that the following operations are # add writer here to make sure that the following operations are
# performed in the same session # performed in the same session
with db_api.context_manager.writer.using(self.context): with db_api.context_manager.writer.using(self.context):
binding = plugin._get_port( binding = utils.get_port_binding_by_status_and_host(
self.context, port['port']['id']).port_binding plugin._get_port(self.context,
port['port']['id']).port_binding,
constants.ACTIVE)
binding['host'] = 'host-ovs-no_filter' binding['host'] = 'host-ovs-no_filter'
mech_context = driver_context.PortContext( mech_context = driver_context.PortContext(
plugin, self.context, port['port'], plugin, self.context, port['port'],

View File

@ -15,16 +15,23 @@
import mock import mock
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import portbindings_extended as pbe_ext
from neutron_lib import constants as const from neutron_lib import constants as const
from neutron_lib import context from neutron_lib import context
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from oslo_config import cfg from oslo_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
import webob.exc
from neutron.common import exceptions
from neutron.common import utils
from neutron.conf.plugins.ml2 import config
from neutron.conf.plugins.ml2.drivers import driver_type from neutron.conf.plugins.ml2.drivers import driver_type
from neutron.plugins.ml2 import driver_context from neutron.plugins.ml2 import driver_context
from neutron.plugins.ml2 import models as ml2_models from neutron.plugins.ml2 import models as ml2_models
from neutron.plugins.ml2 import plugin as ml2_plugin
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
from neutron.tests.unit.plugins.ml2.drivers import mechanism_test
class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase): class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
@ -327,3 +334,309 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
# Get port and verify status is still DOWN. # Get port and verify status is still DOWN.
port = self._show('ports', port_id) port = self._show('ports', port_id)
self.assertEqual('DOWN', port['port']['status']) self.assertEqual('DOWN', port['port']['status'])
class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
host = 'host-ovs-no_filter'
def setUp(self):
# Enable the test mechanism driver to ensure that
# we can successfully call through to all mechanism
# driver apis.
config.register_ml2_plugin_opts()
cfg.CONF.set_override('mechanism_drivers',
['logger', 'test'],
'ml2')
driver_type.register_ml2_drivers_vlan_opts()
cfg.CONF.set_override('network_vlan_ranges',
['physnet1:1000:1099'],
group='ml2_type_vlan')
super(ExtendedPortBindingTestCase, self).setUp('ml2')
self.port_create_status = 'DOWN'
self.plugin = directory.get_plugin()
self.plugin.start_rpc_listeners()
def _create_port_binding(self, fmt, port_id, host, tenant_id=None,
**kwargs):
tenant_id = tenant_id or self._tenant_id
data = {'binding': {'host': host, 'tenant_id': tenant_id}}
if kwargs:
data['binding'].update(kwargs)
binding_resource = 'ports/%s/bindings' % port_id
binding_req = self.new_create_request(binding_resource, data, fmt)
return binding_req.get_response(self.api)
def _make_port_binding(self, fmt, port_id, host, **kwargs):
res = self._create_port_binding(fmt, port_id, host, **kwargs)
if res.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(fmt, res)
def _update_port_binding(self, fmt, port_id, host, **kwargs):
data = {'binding': kwargs}
binding_req = self.new_update_request('ports', data, port_id, fmt,
subresource='bindings',
sub_id=host)
return binding_req.get_response(self.api)
def _do_update_port_binding(self, fmt, port_id, host, **kwargs):
res = self._update_port_binding(fmt, port_id, host, **kwargs)
if res.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(fmt, res)
def _activate_port_binding(self, port_id, host, raw_response=True):
response = self._req('PUT', 'ports', id=port_id,
data={'port_id': port_id},
subresource='bindings', sub_id=host,
action='activate').get_response(self.api)
return self._check_code_and_serialize(response, raw_response)
def _check_code_and_serialize(self, response, raw_response):
if raw_response:
return response
if response.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=response.status_int)
return self.deserialize(self.fmt, response)
def _list_port_bindings(self, port_id, params=None, raw_response=True):
response = self._req(
'GET', 'ports', fmt=self.fmt, id=port_id, subresource='bindings',
params=params).get_response(self.api)
return self._check_code_and_serialize(response, raw_response)
def _show_port_binding(self, port_id, host, params=None,
raw_response=True):
response = self._req(
'GET', 'ports', fmt=self.fmt, id=port_id, subresource='bindings',
sub_id=host, params=params).get_response(self.api)
return self._check_code_and_serialize(response, raw_response)
def _delete_port_binding(self, port_id, host):
response = self._req(
'DELETE', 'ports', fmt=self.fmt, id=port_id,
subresource='bindings', sub_id=host).get_response(self.api)
return response
def _create_port_and_binding(self, **kwargs):
device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
with self.port(device_owner=device_owner) as port:
port_id = port['port']['id']
binding = self._make_port_binding(self.fmt, port_id, self.host,
**kwargs)['binding']
self._assert_bound_port_binding(binding)
return port['port'], binding
def _assert_bound_port_binding(self, binding):
self.assertEqual(self.host, binding[pbe_ext.HOST])
self.assertEqual(portbindings.VIF_TYPE_OVS,
binding[pbe_ext.VIF_TYPE])
self.assertEqual({'port_filter': False},
binding[pbe_ext.VIF_DETAILS])
def _assert_unbound_port_binding(self, binding):
self.assertFalse(binding[pbe_ext.HOST])
self.assertEqual(portbindings.VIF_TYPE_UNBOUND,
binding[pbe_ext.VIF_TYPE])
self.assertEqual({}, binding[pbe_ext.VIF_DETAILS])
self.assertEqual({}, binding[pbe_ext.PROFILE])
def test_create_port_binding(self):
profile = {'key1': 'value1'}
kwargs = {pbe_ext.PROFILE: profile}
port, binding = self._create_port_and_binding(**kwargs)
self._assert_bound_port_binding(binding)
self.assertEqual({"key1": "value1"}, binding[pbe_ext.PROFILE])
def test_create_duplicate_port_binding(self):
device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
host_arg = {portbindings.HOST_ID: self.host}
with self.port(device_owner=device_owner,
arg_list=(portbindings.HOST_ID,),
**host_arg) as port:
response = self._create_port_binding(self.fmt, port['port']['id'],
self.host)
self.assertEqual(webob.exc.HTTPConflict.code,
response.status_int)
def test_create_port_binding_failure(self):
device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
with self.port(device_owner=device_owner) as port:
port_id = port['port']['id']
response = self._create_port_binding(self.fmt, port_id,
'host-fail')
self.assertEqual(webob.exc.HTTPInternalServerError.code,
response.status_int)
self.assertTrue(exceptions.PortBindingError.__name__ in
response.text)
def test_create_port_binding_for_non_compute_owner(self):
with self.port() as port:
port_id = port['port']['id']
response = self._create_port_binding(self.fmt, port_id,
'host-ovs-no_filter')
self.assertEqual(webob.exc.HTTPBadRequest.code,
response.status_int)
def test_update_port_binding(self):
port, binding = self._create_port_and_binding()
profile = {'key1': 'value1'}
kwargs = {pbe_ext.PROFILE: profile}
binding = self._do_update_port_binding(self.fmt, port['id'], self.host,
**kwargs)['binding']
self._assert_bound_port_binding(binding)
self.assertEqual({"key1": "value1"}, binding[pbe_ext.PROFILE])
def test_update_non_existing_binding(self):
device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
with self.port(device_owner=device_owner) as port:
port_id = port['port']['id']
profile = {'key1': 'value1'}
kwargs = {pbe_ext.PROFILE: profile}
response = self._update_port_binding(self.fmt, port_id, 'a_host',
**kwargs)
self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
def test_update_port_binding_for_non_compute_owner(self):
with self.port() as port:
port_id = port['port']['id']
profile = {'key1': 'value1'}
kwargs = {pbe_ext.PROFILE: profile}
response = self._update_port_binding(self.fmt, port_id, 'a_host',
**kwargs)
self.assertEqual(webob.exc.HTTPBadRequest.code,
response.status_int)
def test_update_port_binding_failure(self):
class FakeBinding(object):
vif_type = portbindings.VIF_TYPE_BINDING_FAILED
class FakePortContext(object):
_binding = FakeBinding()
port, binding = self._create_port_and_binding()
profile = {'key1': 'value1'}
kwargs = {pbe_ext.PROFILE: profile}
with mock.patch.object(
self.plugin, '_bind_port_if_needed',
return_value=FakePortContext()):
response = self._update_port_binding(self.fmt, port['id'],
self.host, **kwargs)
self.assertEqual(webob.exc.HTTPInternalServerError.code,
response.status_int)
self.assertTrue(exceptions.PortBindingError.__name__ in
response.text)
def test_activate_port_binding(self):
port, new_binding = self._create_port_and_binding()
with mock.patch.object(mechanism_test.TestMechanismDriver,
'_check_port_context'):
active_binding = self._activate_port_binding(
port['id'], self.host, raw_response=False)
self._assert_bound_port_binding(active_binding)
updated_port = self._show('ports', port['id'])['port']
self.assertEqual(new_binding[pbe_ext.HOST],
updated_port[portbindings.HOST_ID])
self.assertEqual(new_binding[pbe_ext.PROFILE],
updated_port[portbindings.PROFILE])
self.assertEqual(new_binding[pbe_ext.VNIC_TYPE],
updated_port[portbindings.VNIC_TYPE])
self.assertEqual(new_binding[pbe_ext.VIF_TYPE],
updated_port[portbindings.VIF_TYPE])
self.assertEqual(new_binding[pbe_ext.VIF_DETAILS],
updated_port[portbindings.VIF_DETAILS])
retrieved_bindings = self._list_port_bindings(
port['id'], raw_response=False)['bindings']
retrieved_active_binding = utils.get_port_binding_by_status_and_host(
retrieved_bindings, const.ACTIVE)
self._assert_bound_port_binding(retrieved_active_binding)
retrieved_inactive_binding = utils.get_port_binding_by_status_and_host(
retrieved_bindings, const.INACTIVE)
self._assert_unbound_port_binding(retrieved_inactive_binding)
def test_activate_port_binding_for_non_compute_owner(self):
port, new_binding = self._create_port_and_binding()
data = {'port': {'device_owner': ''}}
self.new_update_request('ports', data, port['id'],
self.fmt).get_response(self.api)
response = self._activate_port_binding(port['id'], self.host)
self.assertEqual(webob.exc.HTTPBadRequest.code,
response.status_int)
def test_activate_port_binding_already_active(self):
port, new_binding = self._create_port_and_binding()
with mock.patch.object(mechanism_test.TestMechanismDriver,
'_check_port_context'):
self._activate_port_binding(port['id'], self.host)
response = self._activate_port_binding(port['id'], self.host)
self.assertEqual(webob.exc.HTTPConflict.code,
response.status_int)
def test_activate_port_binding_failure(self):
port, new_binding = self._create_port_and_binding()
with mock.patch.object(self.plugin, '_commit_port_binding',
return_value=(None, None, True,)) as p_mock:
response = self._activate_port_binding(port['id'], self.host)
self.assertEqual(webob.exc.HTTPInternalServerError.code,
response.status_int)
self.assertTrue(exceptions.PortBindingError.__name__ in
response.text)
self.assertEqual(ml2_plugin.MAX_BIND_TRIES, p_mock.call_count)
def test_activate_port_binding_non_existing_binding(self):
port, new_binding = self._create_port_and_binding()
response = self._activate_port_binding(port['id'], 'other-host')
self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
def test_list_port_bindings(self):
port, new_binding = self._create_port_and_binding()
retrieved_bindings = self._list_port_bindings(
port['id'], raw_response=False)['bindings']
self.assertEqual(2, len(retrieved_bindings))
status = const.ACTIVE
self._assert_unbound_port_binding(
utils.get_port_binding_by_status_and_host(retrieved_bindings,
status))
status = const.INACTIVE
self._assert_bound_port_binding(
utils.get_port_binding_by_status_and_host(retrieved_bindings,
status, host=self.host))
def test_list_port_bindings_with_query_parameters(self):
port, new_binding = self._create_port_and_binding()
params = '%s=%s' % (pbe_ext.STATUS, const.INACTIVE)
retrieved_bindings = self._list_port_bindings(
port['id'], params=params, raw_response=False)['bindings']
self.assertEqual(1, len(retrieved_bindings))
self._assert_bound_port_binding(retrieved_bindings[0])
def test_show_port_binding(self):
port, new_binding = self._create_port_and_binding()
retrieved_binding = self._show_port_binding(
port['id'], self.host, raw_response=False)['binding']
self._assert_bound_port_binding(retrieved_binding)
def test_show_port_binding_with_fields(self):
port, new_binding = self._create_port_and_binding()
fields = 'fields=%s' % pbe_ext.HOST
retrieved_binding = self._show_port_binding(
port['id'], self.host, raw_response=False,
params=fields)['binding']
self.assertEqual(self.host, retrieved_binding[pbe_ext.HOST])
for key in (pbe_ext.STATUS, pbe_ext.PROFILE, pbe_ext.VNIC_TYPE,
pbe_ext.VIF_TYPE, pbe_ext.VIF_DETAILS,):
self.assertNotIn(key, retrieved_binding)
def test_delete_port_binding(self):
port, new_binding = self._create_port_and_binding()
response = self._delete_port_binding(port['id'], self.host)
self.assertEqual(webob.exc.HTTPNoContent.code, response.status_int)
response = self._show_port_binding(port['id'], self.host)
self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
def test_delete_non_existing_port_binding(self):
port, new_binding = self._create_port_and_binding()
response = self._delete_port_binding(port['id'], 'other-host')
self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)

View File

@ -105,7 +105,7 @@ class TestLogDriversLoggingTypeValidations(drv_mgr.TestLogDriversManagerBase):
port_binding = ports.PortBinding( port_binding = ports.PortBinding(
self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type) self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type)
return ports.Port( return ports.Port(
self.ctxt, id=uuidutils.generate_uuid(), binding=port_binding) self.ctxt, id=uuidutils.generate_uuid(), binding=[port_binding])
def _test_validate_log_type_for_port(self, port, expected_result): def _test_validate_log_type_for_port(self, port, expected_result):
driver_manager = self._create_manager_with_drivers({ driver_manager = self._create_manager_with_drivers({

View File

@ -86,7 +86,7 @@ class TestQoSDriversRulesValidations(TestQosDriversManagerBase):
port_binding = ports_object.PortBinding( port_binding = ports_object.PortBinding(
self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type) self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type)
return ports_object.Port( return ports_object.Port(
self.ctxt, id=uuidutils.generate_uuid(), binding=port_binding) self.ctxt, id=uuidutils.generate_uuid(), binding=[port_binding])
def _test_validate_rule_for_port(self, port, expected_result): def _test_validate_rule_for_port(self, port, expected_result):
driver_manager = self._create_manager_with_drivers({ driver_manager = self._create_manager_with_drivers({