summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Libosvar <libosvar@redhat.com>2017-07-27 14:15:11 +0000
committerMiguel Lavalle <miguel.lavalle@huawei.com>2018-07-13 18:14:50 -0500
commitf7b62a7f29e6b88fdbcc3bff779b39b541d1de47 (patch)
tree09380da8526a33cb6cfa7d5bed1535004cd2523c
parente8168345ef06052dde1652776cb3dfac4324ce7e (diff)
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
Notes
Notes (review): Code-Review+2: Slawek Kaplonski <skaplons@redhat.com> Code-Review+1: Nate Johnston <nate.johnston@redhat.com> Code-Review+2: Brian Haley <haleyb.dev@gmail.com> Workflow+1: Slawek Kaplonski <skaplons@redhat.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 17 Jul 2018 20:33:00 +0000 Reviewed-on: https://review.openstack.org/414251 Project: openstack/neutron Branch: refs/heads/master
-rw-r--r--neutron/agent/rpc.py6
-rw-r--r--neutron/common/exceptions.py20
-rw-r--r--neutron/common/utils.py35
-rw-r--r--neutron/extensions/portbindings_extended.py77
-rw-r--r--neutron/objects/ports.py18
-rw-r--r--neutron/plugins/ml2/driver_context.py5
-rw-r--r--neutron/plugins/ml2/models.py4
-rw-r--r--neutron/plugins/ml2/plugin.py324
-rw-r--r--neutron/services/logapi/common/validators.py9
-rw-r--r--neutron/services/qos/drivers/manager.py9
-rw-r--r--neutron/tests/contrib/hooks/api_all_extensions1
-rw-r--r--neutron/tests/unit/common/test_utils.py32
-rw-r--r--neutron/tests/unit/objects/test_objects.py2
-rw-r--r--neutron/tests/unit/objects/test_ports.py34
-rw-r--r--neutron/tests/unit/plugins/ml2/test_plugin.py41
-rw-r--r--neutron/tests/unit/plugins/ml2/test_port_binding.py313
-rw-r--r--neutron/tests/unit/services/logapi/common/test_validators.py2
-rw-r--r--neutron/tests/unit/services/qos/drivers/test_manager.py2
18 files changed, 870 insertions, 64 deletions
diff --git a/neutron/agent/rpc.py b/neutron/agent/rpc.py
index eb0110e..732c4cd 100644
--- a/neutron/agent/rpc.py
+++ b/neutron/agent/rpc.py
@@ -29,6 +29,7 @@ from neutron.agent import resource_cache
29from neutron.api.rpc.callbacks import resources 29from neutron.api.rpc.callbacks import resources
30from neutron.common import constants as n_const 30from neutron.common import constants as n_const
31from neutron.common import rpc as n_rpc 31from neutron.common import rpc as n_rpc
32from neutron.common import utils
32from neutron import objects 33from neutron import objects
33 34
34LOG = logging.getLogger(__name__) 35LOG = logging.getLogger(__name__)
@@ -239,6 +240,9 @@ class CacheBackedPluginApi(PluginApi):
239 # match format of old RPC interface 240 # match format of old RPC interface
240 mac_addr = str(netaddr.EUI(str(port_obj.mac_address), 241 mac_addr = str(netaddr.EUI(str(port_obj.mac_address),
241 dialect=netaddr.mac_unix_expanded)) 242 dialect=netaddr.mac_unix_expanded))
243 binding = utils.get_port_binding_by_status_and_host(
244 port_obj.binding, constants.ACTIVE, raise_if_not_found=True,
245 port_id=port_obj.id)
242 entry = { 246 entry = {
243 'device': device, 247 'device': device,
244 'network_id': port_obj.network_id, 248 'network_id': port_obj.network_id,
@@ -259,7 +263,7 @@ class CacheBackedPluginApi(PluginApi):
259 'port_security_enabled', True), 263 'port_security_enabled', True),
260 'qos_policy_id': port_obj.qos_policy_id, 264 'qos_policy_id': port_obj.qos_policy_id,
261 'network_qos_policy_id': net_qos_policy_id, 265 'network_qos_policy_id': net_qos_policy_id,
262 'profile': port_obj.binding.profile, 266 'profile': binding.profile,
263 'security_groups': list(port_obj.security_group_ids) 267 'security_groups': list(port_obj.security_group_ids)
264 } 268 }
265 LOG.debug("Returning: %s", entry) 269 LOG.debug("Returning: %s", entry)
diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py
index 50b8fd8..702a359 100644
--- a/neutron/common/exceptions.py
+++ b/neutron/common/exceptions.py
@@ -334,3 +334,23 @@ class FilterIDForIPNotFound(e.NotFound):
334class FailedToAddQdiscToDevice(e.NeutronException): 334class FailedToAddQdiscToDevice(e.NeutronException):
335 message = _("Failed to add %(direction)s qdisc " 335 message = _("Failed to add %(direction)s qdisc "
336 "to device %(device)s.") 336 "to device %(device)s.")
337
338
339class PortBindingNotFound(e.NotFound):
340 message = _("Binding for port %(port_id)s for host %(host)s could not be "
341 "found.")
342
343
344class PortBindingAlreadyActive(e.Conflict):
345 message = _("Binding for port %(port_id)s on host %(host)s is already "
346 "active.")
347
348
349class PortBindingAlreadyExists(e.Conflict):
350 message = _("Binding for port %(port_id)s on host %(host)s already "
351 "exists.")
352
353
354class PortBindingError(e.NeutronException):
355 message = _("Binding for port %(port_id)s on host %(host)s could not be "
356 "created or updated.")
diff --git a/neutron/common/utils.py b/neutron/common/utils.py
index ecae06b..ad3f028 100644
--- a/neutron/common/utils.py
+++ b/neutron/common/utils.py
@@ -33,6 +33,7 @@ import uuid
33import eventlet 33import eventlet
34from eventlet.green import subprocess 34from eventlet.green import subprocess
35import netaddr 35import netaddr
36from neutron_lib.api.definitions import portbindings_extended as pb_ext
36from neutron_lib import constants as n_const 37from neutron_lib import constants as n_const
37from neutron_lib.utils import helpers 38from neutron_lib.utils import helpers
38from oslo_config import cfg 39from oslo_config import cfg
@@ -43,6 +44,7 @@ import six
43 44
44import neutron 45import neutron
45from neutron._i18n import _ 46from neutron._i18n import _
47from neutron.common import exceptions
46from neutron.db import api as db_api 48from neutron.db import api as db_api
47 49
48TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" 50TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
@@ -783,3 +785,36 @@ def bytes_to_bits(value):
783def bits_to_kilobits(value, base): 785def bits_to_kilobits(value, base):
784 # NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result 786 # NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result
785 return int((value + (base - 1)) / base) 787 return int((value + (base - 1)) / base)
788
789
790def get_port_binding_by_status_and_host(bindings, status, host='',
791 raise_if_not_found=False,
792 port_id=None):
793 """Returns from an iterable the binding with the specified status and host.
794
795 The input iterable can contain zero or one binding in status ACTIVE
796 and zero or many bindings in status INACTIVE. As a consequence, to
797 unequivocally retrieve an inactive binding, the caller must specify a non
798 empty value for host. If host is the empty string, the first binding
799 satisfying the specified status will be returned. If no binding is found
800 with the specified status and host, None is returned or PortBindingNotFound
801 is raised if raise_if_not_found is True
802
803 :param bindings: An iterable containing port bindings
804 :param status: The status of the port binding to return. Possible values
805 are ACTIVE or INACTIVE as defined in
806 :file:`neutron_lib/constants.py`.
807 :param host: str representing the host of the binding to return.
808 :param raise_if_not_found: If a binding is not found and this parameter is
809 True, a PortBindingNotFound exception is raised
810 :param port_id: The id of the binding's port
811 :returns: The searched for port binding or None if it is not found
812 :raises: PortBindingNotFound if the binding is not found and
813 raise_if_not_found is True
814 """
815 for binding in bindings:
816 if binding[pb_ext.STATUS] == status:
817 if not host or binding[pb_ext.HOST] == host:
818 return binding
819 if raise_if_not_found:
820 raise exceptions.PortBindingNotFound(port_id=port_id, host=host)
diff --git a/neutron/extensions/portbindings_extended.py b/neutron/extensions/portbindings_extended.py
new file mode 100644
index 0000000..dab95a2
--- /dev/null
+++ b/neutron/extensions/portbindings_extended.py
@@ -0,0 +1,77 @@
1# All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from neutron_lib.api.definitions import portbindings_extended as pbe_ext
16from neutron_lib.api import extensions as api_extensions
17from neutron_lib.plugins import directory
18
19from neutron.api import extensions
20from neutron.api.v2 import base
21
22EXT_ALIAS = pbe_ext.ALIAS
23
24
25class Portbindings_extended(api_extensions.ExtensionDescriptor):
26 """Extension class supporting port bindings.
27
28 This class is used by neutron's extension framework to make
29 metadata about the port bindings available to external applications.
30
31 With admin rights one will be able to update and read the values.
32 """
33 @classmethod
34 def get_name(cls):
35 return pbe_ext.NAME
36
37 @classmethod
38 def get_alias(cls):
39 return pbe_ext.ALIAS
40
41 @classmethod
42 def get_description(cls):
43 return pbe_ext.DESCRIPTION
44
45 @classmethod
46 def get_updated(cls):
47 return pbe_ext.UPDATED_TIMESTAMP
48
49 @classmethod
50 def get_resources(cls):
51 plugin = directory.get_plugin()
52
53 params = pbe_ext.SUB_RESOURCE_ATTRIBUTE_MAP[
54 pbe_ext.COLLECTION_NAME]['parameters']
55 parent = pbe_ext.SUB_RESOURCE_ATTRIBUTE_MAP[
56 pbe_ext.COLLECTION_NAME]['parent']
57 controller = base.create_resource(
58 pbe_ext.COLLECTION_NAME,
59 pbe_ext.RESOURCE_NAME,
60 plugin,
61 params,
62 member_actions=pbe_ext.ACTION_MAP[pbe_ext.RESOURCE_NAME],
63 parent=parent,
64 allow_pagination=True,
65 allow_sorting=True,
66 )
67 exts = [
68 extensions.ResourceExtension(
69 pbe_ext.COLLECTION_NAME,
70 controller,
71 parent,
72 member_actions=pbe_ext.ACTION_MAP[pbe_ext.RESOURCE_NAME],
73 attr_map=params,
74 ),
75 ]
76
77 return exts
diff --git a/neutron/objects/ports.py b/neutron/objects/ports.py
index 9474105..1b422ae 100644
--- a/neutron/objects/ports.py
+++ b/neutron/objects/ports.py
@@ -264,7 +264,8 @@ class Port(base.NeutronDbObject):
264 # Version 1.1: Add data_plane_status field 264 # Version 1.1: Add data_plane_status field
265 # Version 1.2: Added segment_id to binding_levels 265 # Version 1.2: Added segment_id to binding_levels
266 # Version 1.3: distributed_binding -> distributed_bindings 266 # Version 1.3: distributed_binding -> distributed_bindings
267 VERSION = '1.3' 267 # Version 1.4: Attribute binding becomes ListOfObjectsField
268 VERSION = '1.4'
268 269
269 db_model = models_v2.Port 270 db_model = models_v2.Port
270 271
@@ -282,7 +283,7 @@ class Port(base.NeutronDbObject):
282 'allowed_address_pairs': obj_fields.ListOfObjectsField( 283 'allowed_address_pairs': obj_fields.ListOfObjectsField(
283 'AllowedAddressPair', nullable=True 284 'AllowedAddressPair', nullable=True
284 ), 285 ),
285 'binding': obj_fields.ObjectField( 286 'binding': obj_fields.ListOfObjectsField(
286 'PortBinding', nullable=True 287 'PortBinding', nullable=True
287 ), 288 ),
288 'data_plane_status': obj_fields.ObjectField( 289 'data_plane_status': obj_fields.ObjectField(
@@ -473,6 +474,19 @@ class Port(base.NeutronDbObject):
473 bindings = primitive.pop('distributed_bindings', []) 474 bindings = primitive.pop('distributed_bindings', [])
474 primitive['distributed_binding'] = (bindings[0] 475 primitive['distributed_binding'] = (bindings[0]
475 if bindings else None) 476 if bindings else None)
477 if _target_version < (1, 4):
478 # In version 1.4 we add support for multiple port bindings.
479 # Previous versions only support one port binding. The following
480 # lines look for the active port binding, which is the only one
481 # needed in previous versions
482 if 'binding' in primitive:
483 original_binding = primitive['binding']
484 primitive['binding'] = None
485 for a_binding in original_binding:
486 if (a_binding['versioned_object.data']['status'] ==
487 constants.ACTIVE):
488 primitive['binding'] = a_binding
489 break
476 490
477 @classmethod 491 @classmethod
478 def get_ports_by_router(cls, context, router_id, owner, subnet): 492 def get_ports_by_router(cls, context, router_id, owner, subnet):
diff --git a/neutron/plugins/ml2/driver_context.py b/neutron/plugins/ml2/driver_context.py
index 68da335..91e1c41 100644
--- a/neutron/plugins/ml2/driver_context.py
+++ b/neutron/plugins/ml2/driver_context.py
@@ -50,6 +50,11 @@ class InstanceSnapshot(object):
50 session.add(self._model_class(**{col: getattr(self, col) 50 session.add(self._model_class(**{col: getattr(self, col)
51 for col in self._cols})) 51 for col in self._cols}))
52 52
53 def __getitem__(self, item):
54 if item not in self._cols:
55 raise KeyError(item)
56 return getattr(self, item)
57
53 58
54class MechanismDriverContext(object): 59class MechanismDriverContext(object):
55 """MechanismDriver context base class.""" 60 """MechanismDriver context base class."""
diff --git a/neutron/plugins/ml2/models.py b/neutron/plugins/ml2/models.py
index 185e105..dcc15fc 100644
--- a/neutron/plugins/ml2/models.py
+++ b/neutron/plugins/ml2/models.py
@@ -58,8 +58,10 @@ class PortBinding(model_base.BASEV2):
58 port = orm.relationship( 58 port = orm.relationship(
59 models_v2.Port, 59 models_v2.Port,
60 load_on_pending=True, 60 load_on_pending=True,
61 # TODO(mlavalle) change name of the relationship to reflect that it is
62 # now an iterable
61 backref=orm.backref("port_binding", 63 backref=orm.backref("port_binding",
62 lazy='joined', uselist=False, 64 lazy='joined',
63 cascade='delete')) 65 cascade='delete'))
64 revises_on_change = ('port', ) 66 revises_on_change = ('port', )
65 67
diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py
index f1e3f6e..4a491f7 100644
--- a/neutron/plugins/ml2/plugin.py
+++ b/neutron/plugins/ml2/plugin.py
@@ -23,6 +23,7 @@ from neutron_lib.api.definitions import network_mtu_writable as mtuw_apidef
23from neutron_lib.api.definitions import port as port_def 23from neutron_lib.api.definitions import port as port_def
24from neutron_lib.api.definitions import port_security as psec 24from neutron_lib.api.definitions import port_security as psec
25from neutron_lib.api.definitions import portbindings 25from neutron_lib.api.definitions import portbindings
26from neutron_lib.api.definitions import portbindings_extended as pbe_ext
26from neutron_lib.api.definitions import subnet as subnet_def 27from neutron_lib.api.definitions import subnet as subnet_def
27from neutron_lib.api.definitions import vlantransparent as vlan_apidef 28from neutron_lib.api.definitions import vlantransparent as vlan_apidef
28from neutron_lib.api import extensions 29from neutron_lib.api import extensions
@@ -64,6 +65,7 @@ from neutron.api.rpc.handlers import metadata_rpc
64from neutron.api.rpc.handlers import resources_rpc 65from neutron.api.rpc.handlers import resources_rpc
65from neutron.api.rpc.handlers import securitygroups_rpc 66from neutron.api.rpc.handlers import securitygroups_rpc
66from neutron.common import constants as n_const 67from neutron.common import constants as n_const
68from neutron.common import exceptions as n_exc
67from neutron.common import rpc as n_rpc 69from neutron.common import rpc as n_rpc
68from neutron.common import utils 70from neutron.common import utils
69from neutron.db import _model_query as model_query 71from neutron.db import _model_query as model_query
@@ -87,6 +89,8 @@ from neutron.db import subnet_service_type_mixin
87from neutron.db import vlantransparent_db 89from neutron.db import vlantransparent_db
88from neutron.extensions import providernet as provider 90from neutron.extensions import providernet as provider
89from neutron.extensions import vlantransparent 91from neutron.extensions import vlantransparent
92from neutron.objects import base as base_obj
93from neutron.objects import ports as ports_obj
90from neutron.plugins.ml2.common import exceptions as ml2_exc 94from neutron.plugins.ml2.common import exceptions as ml2_exc
91from neutron.plugins.ml2 import db 95from neutron.plugins.ml2 import db
92from neutron.plugins.ml2 import driver_context 96from neutron.plugins.ml2 import driver_context
@@ -113,7 +117,7 @@ def _ml2_port_result_filter_hook(query, filters):
113 if not values: 117 if not values:
114 return query 118 return query
115 bind_criteria = models.PortBinding.host.in_(values) 119 bind_criteria = models.PortBinding.host.in_(values)
116 return query.filter(models_v2.Port.port_binding.has(bind_criteria)) 120 return query.filter(models_v2.Port.port_binding.any(bind_criteria))
117 121
118 122
119@resource_extend.has_resource_extenders 123@resource_extend.has_resource_extenders
@@ -161,7 +165,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
161 "ip-substring-filtering", 165 "ip-substring-filtering",
162 "port-security-groups-filtering", 166 "port-security-groups-filtering",
163 "empty-string-filtering", 167 "empty-string-filtering",
164 "port-mac-address-regenerate"] 168 "port-mac-address-regenerate",
169 "binding-extended"]
165 170
166 @property 171 @property
167 def supported_extension_aliases(self): 172 def supported_extension_aliases(self):
@@ -241,12 +246,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
241 **kwargs): 246 **kwargs):
242 port_id = object_id 247 port_id = object_id
243 port = db.get_port(context, port_id) 248 port = db.get_port(context, port_id)
244 if not port or not port.port_binding: 249 port_binding = utils.get_port_binding_by_status_and_host(
250 getattr(port, 'port_binding', []), const.ACTIVE)
251 if not port or not port_binding:
245 LOG.debug("Port %s was deleted so its status cannot be updated.", 252 LOG.debug("Port %s was deleted so its status cannot be updated.",
246 port_id) 253 port_id)
247 return 254 return
248 if port.port_binding.vif_type in (portbindings.VIF_TYPE_BINDING_FAILED, 255 if port_binding.vif_type in (portbindings.VIF_TYPE_BINDING_FAILED,
249 portbindings.VIF_TYPE_UNBOUND): 256 portbindings.VIF_TYPE_UNBOUND):
250 # NOTE(kevinbenton): we hit here when a port is created without 257 # NOTE(kevinbenton): we hit here when a port is created without
251 # a host ID and the dhcp agent notifies that its wiring is done 258 # a host ID and the dhcp agent notifies that its wiring is done
252 LOG.debug("Port %s cannot update to ACTIVE because it " 259 LOG.debug("Port %s cannot update to ACTIVE because it "
@@ -320,13 +327,15 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
320 new_mac=port['mac_address']) 327 new_mac=port['mac_address'])
321 return mac_change 328 return mac_change
322 329
323 def _process_port_binding(self, mech_context, attrs): 330 def _clear_port_binding(self, mech_context, binding, port, original_host):
324 plugin_context = mech_context._plugin_context 331 binding.vif_type = portbindings.VIF_TYPE_UNBOUND
325 binding = mech_context._binding 332 binding.vif_details = ''
326 port = mech_context.current 333 db.clear_binding_levels(mech_context._plugin_context, port['id'],
327 port_id = port['id'] 334 original_host)
328 changes = False 335 mech_context._clear_binding_levels()
329 336
337 def _process_port_binding_attributes(self, binding, attrs):
338 changes = False
330 host = const.ATTR_NOT_SPECIFIED 339 host = const.ATTR_NOT_SPECIFIED
331 if attrs and portbindings.HOST_ID in attrs: 340 if attrs and portbindings.HOST_ID in attrs:
332 host = attrs.get(portbindings.HOST_ID) or '' 341 host = attrs.get(portbindings.HOST_ID) or ''
@@ -354,23 +363,28 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
354 msg = _("binding:profile value too large") 363 msg = _("binding:profile value too large")
355 raise exc.InvalidInput(error_message=msg) 364 raise exc.InvalidInput(error_message=msg)
356 changes = True 365 changes = True
366 return changes, original_host
367
368 def _process_port_binding(self, mech_context, attrs):
369 plugin_context = mech_context._plugin_context
370 binding = mech_context._binding
371 port = mech_context.current
372 changes, original_host = self._process_port_binding_attributes(binding,
373 attrs)
357 374
358 # Unbind the port if needed. 375 # Unbind the port if needed.
359 if changes: 376 if changes:
360 binding.vif_type = portbindings.VIF_TYPE_UNBOUND 377 self._clear_port_binding(mech_context, binding, port,
361 binding.vif_details = '' 378 original_host)
362 db.clear_binding_levels(plugin_context, port_id, original_host)
363 mech_context._clear_binding_levels()
364 port['status'] = const.PORT_STATUS_DOWN 379 port['status'] = const.PORT_STATUS_DOWN
365 super(Ml2Plugin, self).update_port( 380 super(Ml2Plugin, self).update_port(
366 mech_context._plugin_context, port_id, 381 mech_context._plugin_context, port['id'],
367 {port_def.RESOURCE_NAME: {'status': const.PORT_STATUS_DOWN}}) 382 {port_def.RESOURCE_NAME:
383 {'status': const.PORT_STATUS_DOWN}})
368 384
369 if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE: 385 if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
370 binding.vif_type = portbindings.VIF_TYPE_UNBOUND 386 self._clear_port_binding(mech_context, binding, port,
371 binding.vif_details = '' 387 original_host)
372 db.clear_binding_levels(plugin_context, port_id, original_host)
373 mech_context._clear_binding_levels()
374 binding.host = '' 388 binding.host = ''
375 389
376 self._update_port_dict_binding(port, binding) 390 self._update_port_dict_binding(port, binding)
@@ -379,7 +393,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
379 393
380 @db_api.retry_db_errors 394 @db_api.retry_db_errors
381 def _bind_port_if_needed(self, context, allow_notify=False, 395 def _bind_port_if_needed(self, context, allow_notify=False,
382 need_notify=False): 396 need_notify=False, allow_commit=True):
383 if not context.network.network_segments: 397 if not context.network.network_segments:
384 LOG.debug("Network %s has no segments, skipping binding", 398 LOG.debug("Network %s has no segments, skipping binding",
385 context.network.current['id']) 399 context.network.current['id'])
@@ -399,7 +413,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
399 context, need_notify) 413 context, need_notify)
400 414
401 if count == MAX_BIND_TRIES or not try_again: 415 if count == MAX_BIND_TRIES or not try_again:
402 if self._should_bind_port(context): 416 if self._should_bind_port(context) and allow_commit:
403 # At this point, we attempted to bind a port and reached 417 # At this point, we attempted to bind a port and reached
404 # its final binding state. Binding either succeeded or 418 # its final binding state. Binding either succeeded or
405 # exhausted all attempts, thus no need to try again. 419 # exhausted all attempts, thus no need to try again.
@@ -488,7 +502,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
488 # mechanism driver update_port_*commit() calls. 502 # mechanism driver update_port_*commit() calls.
489 try: 503 try:
490 port_db = self._get_port(plugin_context, port_id) 504 port_db = self._get_port(plugin_context, port_id)
491 cur_binding = port_db.port_binding 505 cur_binding = utils.get_port_binding_by_status_and_host(
506 port_db.port_binding, const.ACTIVE)
492 except exc.PortNotFound: 507 except exc.PortNotFound:
493 port_db, cur_binding = None, None 508 port_db, cur_binding = None, None
494 if not port_db or not cur_binding: 509 if not port_db or not cur_binding:
@@ -532,8 +547,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
532 # to optimize this code to avoid fetching it. 547 # to optimize this code to avoid fetching it.
533 cur_binding = db.get_distributed_port_binding_by_host( 548 cur_binding = db.get_distributed_port_binding_by_host(
534 plugin_context, port_id, orig_binding.host) 549 plugin_context, port_id, orig_binding.host)
550 cur_context_binding = cur_binding
551 if new_binding.status == const.INACTIVE:
552 cur_context_binding = (
553 utils.get_port_binding_by_status_and_host(
554 port_db.port_binding, const.INACTIVE,
555 host=new_binding.host))
535 cur_context = driver_context.PortContext( 556 cur_context = driver_context.PortContext(
536 self, plugin_context, port, network, cur_binding, None, 557 self, plugin_context, port, network, cur_context_binding, None,
537 original_port=oport) 558 original_port=oport)
538 559
539 # Commit our binding results only if port has not been 560 # Commit our binding results only if port has not been
@@ -549,20 +570,24 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
549 if commit: 570 if commit:
550 # Update the port's binding state with our binding 571 # Update the port's binding state with our binding
551 # results. 572 # results.
552 cur_binding.vif_type = new_binding.vif_type 573 if new_binding.status == const.INACTIVE:
553 cur_binding.vif_details = new_binding.vif_details 574 cur_context_binding.status = const.ACTIVE
575 cur_binding.status = const.INACTIVE
576 else:
577 cur_context_binding.vif_type = new_binding.vif_type
578 cur_context_binding.vif_details = new_binding.vif_details
554 db.clear_binding_levels(plugin_context, port_id, 579 db.clear_binding_levels(plugin_context, port_id,
555 cur_binding.host) 580 cur_binding.host)
556 db.set_binding_levels(plugin_context, 581 db.set_binding_levels(plugin_context,
557 bind_context._binding_levels) 582 bind_context._binding_levels)
558 # refresh context with a snapshot of updated state 583 # refresh context with a snapshot of updated state
559 cur_context._binding = driver_context.InstanceSnapshot( 584 cur_context._binding = driver_context.InstanceSnapshot(
560 cur_binding) 585 cur_context_binding)
561 cur_context._binding_levels = bind_context._binding_levels 586 cur_context._binding_levels = bind_context._binding_levels
562 587
563 # Update PortContext's port dictionary to reflect the 588 # Update PortContext's port dictionary to reflect the
564 # updated binding state. 589 # updated binding state.
565 self._update_port_dict_binding(port, cur_binding) 590 self._update_port_dict_binding(port, cur_context_binding)
566 591
567 # Update the port status if requested by the bound driver. 592 # Update the port status if requested by the bound driver.
568 if (bind_context._binding_levels and 593 if (bind_context._binding_levels and
@@ -632,9 +657,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
632 @resource_extend.extends([port_def.COLLECTION_NAME]) 657 @resource_extend.extends([port_def.COLLECTION_NAME])
633 def _ml2_extend_port_dict_binding(port_res, port_db): 658 def _ml2_extend_port_dict_binding(port_res, port_db):
634 plugin = directory.get_plugin() 659 plugin = directory.get_plugin()
660 port_binding = utils.get_port_binding_by_status_and_host(
661 port_db.port_binding, const.ACTIVE)
635 # None when called during unit tests for other plugins. 662 # None when called during unit tests for other plugins.
636 if port_db.port_binding: 663 if port_binding:
637 plugin._update_port_dict_binding(port_res, port_db.port_binding) 664 plugin._update_port_dict_binding(port_res, port_binding)
638 665
639 # ML2's resource extend functions allow extension drivers that extend 666 # ML2's resource extend functions allow extension drivers that extend
640 # attributes for the resources to add those attributes to the result. 667 # attributes for the resources to add those attributes to the result.
@@ -1304,7 +1331,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1304 original_port=original_port) 1331 original_port=original_port)
1305 with db_api.context_manager.writer.using(context): 1332 with db_api.context_manager.writer.using(context):
1306 port_db = self._get_port(context, id) 1333 port_db = self._get_port(context, id)
1307 binding = port_db.port_binding 1334 binding = utils.get_port_binding_by_status_and_host(
1335 port_db.port_binding, const.ACTIVE)
1308 if not binding: 1336 if not binding:
1309 raise exc.PortNotFound(port_id=id) 1337 raise exc.PortNotFound(port_id=id)
1310 mac_address_updated = self._check_mac_update_allowed( 1338 mac_address_updated = self._check_mac_update_allowed(
@@ -1522,7 +1550,9 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1522 with db_api.context_manager.writer.using(context): 1550 with db_api.context_manager.writer.using(context):
1523 try: 1551 try:
1524 port_db = self._get_port(context, id) 1552 port_db = self._get_port(context, id)
1525 binding = port_db.port_binding 1553 binding = utils.get_port_binding_by_status_and_host(
1554 port_db.port_binding, const.ACTIVE,
1555 raise_if_not_found=True, port_id=id)
1526 except exc.PortNotFound: 1556 except exc.PortNotFound:
1527 LOG.debug("The port '%s' was deleted", id) 1557 LOG.debug("The port '%s' was deleted", id)
1528 return 1558 return
@@ -1640,14 +1670,15 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1640 # related attribute port_binding could disappear in 1670 # related attribute port_binding could disappear in
1641 # concurrent port deletion. 1671 # concurrent port deletion.
1642 # It's not an error condition. 1672 # It's not an error condition.
1643 binding = port_db.port_binding 1673 binding = utils.get_port_binding_by_status_and_host(
1674 port_db.port_binding, const.ACTIVE)
1644 if not binding: 1675 if not binding:
1645 LOG.info("Binding info for port %s was not found, " 1676 LOG.info("Binding info for port %s was not found, "
1646 "it might have been deleted already.", 1677 "it might have been deleted already.",
1647 port_id) 1678 port_id)
1648 return 1679 return
1649 levels = db.get_binding_levels(plugin_context, port_db.id, 1680 levels = db.get_binding_levels(plugin_context, port_db.id,
1650 port_db.port_binding.host) 1681 binding.host)
1651 port_context = driver_context.PortContext( 1682 port_context = driver_context.PortContext(
1652 self, plugin_context, port, network, binding, levels) 1683 self, plugin_context, port, network, binding, levels)
1653 1684
@@ -1683,7 +1714,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1683 plugin_context, port['id'], host) 1714 plugin_context, port['id'], host)
1684 bindlevelhost_match = host 1715 bindlevelhost_match = host
1685 else: 1716 else:
1686 binding = port_db.port_binding 1717 binding = utils.get_port_binding_by_status_and_host(
1718 port_db.port_binding, const.ACTIVE)
1687 bindlevelhost_match = binding.host if binding else None 1719 bindlevelhost_match = binding.host if binding else None
1688 if not binding: 1720 if not binding:
1689 LOG.info("Binding info for port %s was not found, " 1721 LOG.info("Binding info for port %s was not found, "
@@ -1768,11 +1800,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1768 # listening for db events can modify the port if necessary 1800 # listening for db events can modify the port if necessary
1769 context.session.flush() 1801 context.session.flush()
1770 updated_port = self._make_port_dict(port) 1802 updated_port = self._make_port_dict(port)
1771 levels = db.get_binding_levels(context, port.id, 1803 binding = utils.get_port_binding_by_status_and_host(
1772 port.port_binding.host) 1804 port.port_binding, const.ACTIVE, raise_if_not_found=True,
1805 port_id=port_id)
1806 levels = db.get_binding_levels(context, port.id, binding.host)
1773 mech_context = driver_context.PortContext( 1807 mech_context = driver_context.PortContext(
1774 self, context, updated_port, network, port.port_binding, 1808 self, context, updated_port, network, binding, levels,
1775 levels, original_port=original_port) 1809 original_port=original_port)
1776 self.mechanism_manager.update_port_precommit(mech_context) 1810 self.mechanism_manager.update_port_precommit(mech_context)
1777 updated = True 1811 updated = True
1778 elif port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE: 1812 elif port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
@@ -1964,3 +1998,213 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
1964 self.mechanism_manager.update_network_precommit(mech_context) 1998 self.mechanism_manager.update_network_precommit(mech_context)
1965 elif event == events.AFTER_CREATE or event == events.AFTER_DELETE: 1999 elif event == events.AFTER_CREATE or event == events.AFTER_DELETE:
1966 self.mechanism_manager.update_network_postcommit(mech_context) 2000 self.mechanism_manager.update_network_postcommit(mech_context)
2001
2002 @staticmethod
2003 def _validate_compute_port(port):
2004 if not port['device_owner'].startswith(
2005 const.DEVICE_OWNER_COMPUTE_PREFIX):
2006 msg = _('Invalid port %s. Operation only valid on compute '
2007 'ports') % port['id']
2008 raise exc.BadRequest(resource='port', msg=msg)
2009
2010 def _make_port_binding_dict(self, binding, fields=None):
2011 res = {key: binding[key] for key in (
2012 pbe_ext.HOST, pbe_ext.VIF_TYPE, pbe_ext.VNIC_TYPE,
2013 pbe_ext.STATUS)}
2014 if isinstance(binding, ports_obj.PortBinding):
2015 res[pbe_ext.PROFILE] = binding.profile or {}
2016 res[pbe_ext.VIF_DETAILS] = binding.vif_details or {}
2017 else:
2018 res[pbe_ext.PROFILE] = self._get_profile(binding)
2019 res[pbe_ext.VIF_DETAILS] = self._get_vif_details(binding)
2020 return db_utils.resource_fields(res, fields)
2021
2022 def _get_port_binding_attrs(self, binding, host=None):
2023 return {portbindings.VNIC_TYPE: binding.get(pbe_ext.VNIC_TYPE),
2024 portbindings.HOST_ID: binding.get(pbe_ext.HOST) or host,
2025 portbindings.PROFILE: binding.get(pbe_ext.PROFILE, {})}
2026
2027 def _process_active_binding_change(self, changes, mech_context, port_dict,
2028 original_host):
2029 if changes:
2030 self._clear_port_binding(mech_context,
2031 mech_context._binding, port_dict,
2032 original_host)
2033 port_dict['status'] = const.PORT_STATUS_DOWN
2034 super(Ml2Plugin, self).update_port(
2035 mech_context._plugin_context, port_dict['id'],
2036 {port_def.RESOURCE_NAME:
2037 {'status': const.PORT_STATUS_DOWN}})
2038 self._update_port_dict_binding(port_dict,
2039 mech_context._binding)
2040 mech_context._binding.persist_state_to_session(
2041 mech_context._plugin_context.session)
2042
2043 @utils.transaction_guard
2044 @db_api.retry_if_session_inactive()
2045 def create_port_binding(self, context, port_id, binding):
2046 attrs = binding[pbe_ext.RESOURCE_NAME]
2047 with db_api.context_manager.writer.using(context):
2048 port_db = self._get_port(context, port_id)
2049 self._validate_compute_port(port_db)
2050 if self._get_binding_for_host(port_db.port_binding,
2051 attrs[pbe_ext.HOST]):
2052 raise n_exc.PortBindingAlreadyExists(
2053 port_id=port_id, host=attrs[pbe_ext.HOST])
2054 status = const.ACTIVE
2055 is_active_binding = True
2056 active_binding = utils.get_port_binding_by_status_and_host(
2057 port_db.port_binding, const.ACTIVE)
2058 if active_binding:
2059 status = const.INACTIVE
2060 is_active_binding = False
2061 network = self.get_network(context, port_db['network_id'])
2062 port_dict = self._make_port_dict(port_db)
2063 new_binding = models.PortBinding(
2064 port_id=port_id,
2065 vif_type=portbindings.VIF_TYPE_UNBOUND,
2066 status=status)
2067 mech_context = driver_context.PortContext(self, context, port_dict,
2068 network, new_binding,
2069 None)
2070 changes, original_host = self._process_port_binding_attributes(
2071 mech_context._binding, self._get_port_binding_attrs(attrs))
2072 if is_active_binding:
2073 self._process_active_binding_change(changes, mech_context,
2074 port_dict, original_host)
2075 bind_context = self._bind_port_if_needed(
2076 mech_context, allow_commit=is_active_binding)
2077 if (bind_context._binding.vif_type ==
2078 portbindings.VIF_TYPE_BINDING_FAILED):
2079 raise n_exc.PortBindingError(port_id=port_id,
2080 host=attrs[pbe_ext.HOST])
2081 bind_context._binding.port_id = port_id
2082 bind_context._binding.status = status
2083 if not is_active_binding:
2084 with db_api.context_manager.writer.using(context):
2085 bind_context._binding.persist_state_to_session(context.session)
2086 db.set_binding_levels(context, bind_context._binding_levels)
2087 return self._make_port_binding_dict(bind_context._binding)
2088
2089 @utils.transaction_guard
2090 @db_api.retry_if_session_inactive()
2091 def get_port_bindings(self, context, port_id, filters=None, fields=None,
2092 sorts=None, limit=None, marker=None,
2093 page_reverse=False):
2094 port = ports_obj.Port.get_object(context, id=port_id)
2095 if not port:
2096 raise exc.PortNotFound(port_id=port_id)
2097 self._validate_compute_port(port)
2098 filters = filters or {}
2099 pager = base_obj.Pager(sorts, limit, page_reverse, marker)
2100 bindings = ports_obj.PortBinding.get_objects(
2101 context, _pager=pager, port_id=port_id, **filters)
2102
2103 return [self._make_port_binding_dict(binding, fields)
2104 for binding in bindings]
2105
2106 @utils.transaction_guard
2107 @db_api.retry_if_session_inactive()
2108 def get_port_binding(self, context, host, port_id, fields=None):
2109 port = ports_obj.Port.get_object(context, id=port_id)
2110 if not port:
2111 raise exc.PortNotFound(port_id=port_id)
2112 self._validate_compute_port(port)
2113 binding = ports_obj.PortBinding.get_object(context, host=host,
2114 port_id=port_id)
2115 if not binding:
2116 raise n_exc.PortBindingNotFound(port_id=port_id, host=host)
2117 return self._make_port_binding_dict(binding, fields)
2118
2119 def _get_binding_for_host(self, bindings, host):
2120 for binding in bindings:
2121 if binding.host == host:
2122 return binding
2123
2124 @utils.transaction_guard
2125 @db_api.retry_if_session_inactive()
2126 def update_port_binding(self, context, host, port_id, binding):
2127 attrs = binding[pbe_ext.RESOURCE_NAME]
2128 with db_api.context_manager.writer.using(context):
2129 port_db = self._get_port(context, port_id)
2130 self._validate_compute_port(port_db)
2131 original_binding = self._get_binding_for_host(port_db.port_binding,
2132 host)
2133 if not original_binding:
2134 raise n_exc.PortBindingNotFound(port_id=port_id, host=host)
2135 is_active_binding = (original_binding.status == const.ACTIVE)
2136 network = self.get_network(context, port_db['network_id'])
2137 port_dict = self._make_port_dict(port_db)
2138 mech_context = driver_context.PortContext(self, context, port_dict,
2139 network,
2140 original_binding, None)
2141 changes, original_host = self._process_port_binding_attributes(
2142 mech_context._binding, self._get_port_binding_attrs(attrs,
2143 host=host))
2144 if is_active_binding:
2145 self._process_active_binding_change(changes, mech_context,
2146 port_dict, original_host)
2147 bind_context = self._bind_port_if_needed(
2148 mech_context, allow_commit=is_active_binding)
2149 if (bind_context._binding.vif_type ==
2150 portbindings.VIF_TYPE_BINDING_FAILED):
2151 raise n_exc.PortBindingError(port_id=port_id, host=host)
2152 if not is_active_binding:
2153 with db_api.context_manager.writer.using(context):
2154 bind_context._binding.persist_state_to_session(context.session)
2155 db.set_binding_levels(context, bind_context._binding_levels)
2156 return self._make_port_binding_dict(bind_context._binding)
2157
2158 @utils.transaction_guard
2159 @db_api.retry_if_session_inactive()
2160 def activate(self, context, host, port_id):
2161 with db_api.context_manager.writer.using(context):
2162 # TODO(mlavalle) Next two lines can be removed when bug #1770267 is
2163 # fixed
2164 if isinstance(port_id, dict):
2165 port_id = port_id['port_id']
2166 port_db = self._get_port(context, port_id)
2167 self._validate_compute_port(port_db)
2168 active_binding = utils.get_port_binding_by_status_and_host(
2169 port_db.port_binding, const.ACTIVE)
2170 if host == (active_binding and active_binding.host):
2171 raise n_exc.PortBindingAlreadyActive(port_id=port_id,
2172 host=host)
2173 inactive_binding = utils.get_port_binding_by_status_and_host(
2174 port_db.port_binding, const.INACTIVE, host=host)
2175 if not inactive_binding or inactive_binding.host != host:
2176 raise n_exc.PortBindingNotFound(port_id=port_id, host=host)
2177 network = self.get_network(context, port_db['network_id'])
2178 port_dict = self._make_port_dict(port_db)
2179 levels = db.get_binding_levels(context, port_id,
2180 active_binding.host)
2181 original_context = driver_context.PortContext(self, context,
2182 port_dict, network,
2183 active_binding,
2184 levels)
2185 self._clear_port_binding(original_context, active_binding,
2186 port_dict, active_binding.host)
2187 port_dict['status'] = const.PORT_STATUS_DOWN
2188 super(Ml2Plugin, self).update_port(
2189 context, port_dict['id'],
2190 {port_def.RESOURCE_NAME:
2191 {'status': const.PORT_STATUS_DOWN}})
2192 levels = db.get_binding_levels(context, port_id,
2193 inactive_binding.host)
2194 bind_context = driver_context.PortContext(self, context, port_dict,
2195 network,
2196 inactive_binding, levels)
2197 for count in range(MAX_BIND_TRIES):
2198 cur_context, _, try_again = self._commit_port_binding(
2199 original_context, bind_context, need_notify=True,
2200 try_again=True)
2201 if not try_again:
2202 self.notifier.port_delete(context, port_id)
2203 return self._make_port_binding_dict(cur_context._binding)
2204 raise n_exc.PortBindingError(port_id=port_id, host=host)
2205
2206 @utils.transaction_guard
2207 @db_api.retry_if_session_inactive()
2208 def delete_port_binding(self, context, host, port_id):
2209 ports_obj.PortBinding.delete_objects(context, host=host,
2210 port_id=port_id)
diff --git a/neutron/services/logapi/common/validators.py b/neutron/services/logapi/common/validators.py
index 4441624..1272d75 100644
--- a/neutron/services/logapi/common/validators.py
+++ b/neutron/services/logapi/common/validators.py
@@ -14,11 +14,13 @@
14# under the License. 14# under the License.
15 15
16from neutron_lib.api.definitions import portbindings 16from neutron_lib.api.definitions import portbindings
17from neutron_lib import constants
17from neutron_lib.plugins import constants as plugin_const 18from neutron_lib.plugins import constants as plugin_const
18from neutron_lib.plugins import directory 19from neutron_lib.plugins import directory
19from oslo_log import log as logging 20from oslo_log import log as logging
20from sqlalchemy.orm import exc as orm_exc 21from sqlalchemy.orm import exc as orm_exc
21 22
23from neutron.common import utils
22from neutron.db import _utils as db_utils 24from neutron.db import _utils as db_utils
23from neutron.db.models import securitygroup as sg_db 25from neutron.db.models import securitygroup as sg_db
24from neutron.objects import ports 26from neutron.objects import ports
@@ -93,13 +95,16 @@ def validate_log_type_for_port(log_type, port):
93 95
94 log_plugin = directory.get_plugin(alias=plugin_const.LOG_API) 96 log_plugin = directory.get_plugin(alias=plugin_const.LOG_API)
95 drivers = log_plugin.driver_manager.drivers 97 drivers = log_plugin.driver_manager.drivers
98 port_binding = utils.get_port_binding_by_status_and_host(
99 port.binding, constants.ACTIVE, raise_if_not_found=True,
100 port_id=port['id'])
96 for driver in drivers: 101 for driver in drivers:
97 vif_type = port.binding.vif_type 102 vif_type = port_binding.vif_type
98 if vif_type not in SKIPPED_VIF_TYPES: 103 if vif_type not in SKIPPED_VIF_TYPES:
99 if not _validate_vif_type(driver, vif_type, port['id']): 104 if not _validate_vif_type(driver, vif_type, port['id']):
100 continue 105 continue
101 else: 106 else:
102 vnic_type = port.binding.vnic_type 107 vnic_type = port_binding.vnic_type
103 if not _validate_vnic_type(driver, vnic_type, port['id']): 108 if not _validate_vnic_type(driver, vnic_type, port['id']):
104 continue 109 continue
105 110
diff --git a/neutron/services/qos/drivers/manager.py b/neutron/services/qos/drivers/manager.py
index 0ed6036..c4503b4 100644
--- a/neutron/services/qos/drivers/manager.py
+++ b/neutron/services/qos/drivers/manager.py
@@ -13,6 +13,7 @@
13from neutron_lib.api.definitions import portbindings 13from neutron_lib.api.definitions import portbindings
14from neutron_lib.callbacks import events 14from neutron_lib.callbacks import events
15from neutron_lib.callbacks import registry 15from neutron_lib.callbacks import registry
16from neutron_lib import constants as lib_constants
16from neutron_lib.services.qos import constants as qos_consts 17from neutron_lib.services.qos import constants as qos_consts
17from oslo_log import log as logging 18from oslo_log import log as logging
18 19
@@ -22,6 +23,7 @@ from neutron.api.rpc.callbacks import resources
22from neutron.api.rpc.handlers import resources_rpc 23from neutron.api.rpc.handlers import resources_rpc
23from neutron.common import constants 24from neutron.common import constants
24from neutron.common import exceptions 25from neutron.common import exceptions
26from neutron.common import utils
25from neutron.objects.qos import policy as policy_object 27from neutron.objects.qos import policy as policy_object
26 28
27 29
@@ -134,13 +136,16 @@ class QosServiceDriverManager(object):
134 self.rpc_notifications_required |= driver.requires_rpc_notifications 136 self.rpc_notifications_required |= driver.requires_rpc_notifications
135 137
136 def validate_rule_for_port(self, rule, port): 138 def validate_rule_for_port(self, rule, port):
139 port_binding = utils.get_port_binding_by_status_and_host(
140 port.binding, lib_constants.ACTIVE, raise_if_not_found=True,
141 port_id=port['id'])
137 for driver in self._drivers: 142 for driver in self._drivers:
138 vif_type = port.binding.vif_type 143 vif_type = port_binding.vif_type
139 if vif_type not in SKIPPED_VIF_TYPES: 144 if vif_type not in SKIPPED_VIF_TYPES:
140 if not self._validate_vif_type(driver, vif_type, port['id']): 145 if not self._validate_vif_type(driver, vif_type, port['id']):
141 continue 146 continue
142 else: 147 else:
143 vnic_type = port.binding.vnic_type 148 vnic_type = port_binding.vnic_type
144 if not self._validate_vnic_type(driver, vnic_type, port['id']): 149 if not self._validate_vnic_type(driver, vnic_type, port['id']):
145 continue 150 continue
146 151
diff --git a/neutron/tests/contrib/hooks/api_all_extensions b/neutron/tests/contrib/hooks/api_all_extensions
index 301ffbf..b994eb6 100644
--- a/neutron/tests/contrib/hooks/api_all_extensions
+++ b/neutron/tests/contrib/hooks/api_all_extensions
@@ -6,6 +6,7 @@ NETWORK_API_EXTENSIONS+=",allowed-address-pairs"
6NETWORK_API_EXTENSIONS+=",auto-allocated-topology" 6NETWORK_API_EXTENSIONS+=",auto-allocated-topology"
7NETWORK_API_EXTENSIONS+=",availability_zone" 7NETWORK_API_EXTENSIONS+=",availability_zone"
8NETWORK_API_EXTENSIONS+=",binding" 8NETWORK_API_EXTENSIONS+=",binding"
9NETWORK_API_EXTENSIONS+=",binding-extended"
9NETWORK_API_EXTENSIONS+=",default-subnetpools" 10NETWORK_API_EXTENSIONS+=",default-subnetpools"
10NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler" 11NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler"
11NETWORK_API_EXTENSIONS+=",dns-integration" 12NETWORK_API_EXTENSIONS+=",dns-integration"
diff --git a/neutron/tests/unit/common/test_utils.py b/neutron/tests/unit/common/test_utils.py
index 41ead62..1d59fee 100644
--- a/neutron/tests/unit/common/test_utils.py
+++ b/neutron/tests/unit/common/test_utils.py
@@ -21,14 +21,18 @@ import ddt
21import eventlet 21import eventlet
22import mock 22import mock
23import netaddr 23import netaddr
24from neutron_lib.api.definitions import portbindings_extended as pb_ext
24from neutron_lib import constants 25from neutron_lib import constants
25from oslo_log import log as logging 26from oslo_log import log as logging
27from oslo_utils import uuidutils
26import six 28import six
27import testscenarios 29import testscenarios
28import testtools 30import testtools
29 31
30from neutron.common import constants as common_constants 32from neutron.common import constants as common_constants
33from neutron.common import exceptions
31from neutron.common import utils 34from neutron.common import utils
35from neutron.objects import ports
32from neutron.tests import base 36from neutron.tests import base
33from neutron.tests.unit import tests 37from neutron.tests.unit import tests
34 38
@@ -524,3 +528,31 @@ class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
524 expected_kilobits, 528 expected_kilobits,
525 utils.bits_to_kilobits(input_bits, self.base_unit) 529 utils.bits_to_kilobits(input_bits, self.base_unit)
526 ) 530 )
531
532
533class TestGetPortBindingByStatusAndHost(base.BaseTestCase):
534
535 def test_get_port_binding_by_status_and_host(self):
536 bindings = []
537 self.assertIsNone(utils.get_port_binding_by_status_and_host(
538 bindings, constants.INACTIVE))
539 bindings.extend([ports.PortBinding(
540 port_id=uuidutils.generate_uuid(), host='host-1',
541 status=constants.INACTIVE),
542 ports.PortBinding(
543 port_id=uuidutils.generate_uuid(), host='host-2',
544 status=constants.INACTIVE)])
545 self.assertEqual(
546 'host-1', utils.get_port_binding_by_status_and_host(
547 bindings,
548 constants.INACTIVE)[pb_ext.HOST])
549 self.assertEqual(
550 'host-2', utils.get_port_binding_by_status_and_host(
551 bindings,
552 constants.INACTIVE,
553 host='host-2')[pb_ext.HOST])
554 self.assertIsNone(utils.get_port_binding_by_status_and_host(
555 bindings, constants.ACTIVE))
556 self.assertRaises(exceptions.PortBindingNotFound,
557 utils.get_port_binding_by_status_and_host, bindings,
558 constants.ACTIVE, 'host', True, 'port_id')
diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py
index b4cea51..b205f81 100644
--- a/neutron/tests/unit/objects/test_objects.py
+++ b/neutron/tests/unit/objects/test_objects.py
@@ -62,7 +62,7 @@ object_data = {
62 'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', 62 'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
63 'NetworkRBAC': '1.0-c8a67f39809c5a3c8c7f26f2f2c620b2', 63 'NetworkRBAC': '1.0-c8a67f39809c5a3c8c7f26f2f2c620b2',
64 'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8', 64 'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
65 'Port': '1.3-4cb798ffc8b08f2657c0bd8afa708e9e', 65 'Port': '1.4-c3937b92962d5b43a09a7de2f44e0ab7',
66 'PortBinding': '1.0-3306deeaa6deb01e33af06777d48d578', 66 'PortBinding': '1.0-3306deeaa6deb01e33af06777d48d578',
67 'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5', 67 'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5',
68 'PortDataPlaneStatus': '1.0-25be74bda46c749653a10357676c0ab2', 68 'PortDataPlaneStatus': '1.0-25be74bda46c749653a10357676c0ab2',
diff --git a/neutron/tests/unit/objects/test_ports.py b/neutron/tests/unit/objects/test_ports.py
index bf32c35..ef52d84 100644
--- a/neutron/tests/unit/objects/test_ports.py
+++ b/neutron/tests/unit/objects/test_ports.py
@@ -11,6 +11,7 @@
11# under the License. 11# under the License.
12 12
13import mock 13import mock
14from neutron_lib import constants
14from oslo_utils import uuidutils 15from oslo_utils import uuidutils
15import testscenarios 16import testscenarios
16 17
@@ -418,3 +419,36 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
418 binding_data = ( 419 binding_data = (
419 port_data['distributed_binding']['versioned_object.data']) 420 port_data['distributed_binding']['versioned_object.data'])
420 self.assertEqual(binding.host, binding_data['host']) 421 self.assertEqual(binding.host, binding_data['host'])
422
423 def test_v1_4_to_v1_3_converts_binding_to_portbinding_object(self):
424 port_v1_4 = self._create_test_port()
425 port_v1_3 = port_v1_4.obj_to_primitive(target_version='1.3')
426
427 # Port has no bindings, so binding attribute should be None
428 self.assertIsNone(port_v1_3['versioned_object.data']['binding'])
429 active_binding = ports.PortBinding(self.context, port_id=port_v1_4.id,
430 host='host1', vif_type='type')
431 inactive_binding = ports.PortBinding(
432 self.context, port_id=port_v1_4.id, host='host2', vif_type='type',
433 status=constants.INACTIVE)
434 active_binding.create()
435 inactive_binding.create()
436 port_v1_4 = ports.Port.get_object(self.context, id=port_v1_4.id)
437 port_v1_3 = port_v1_4.obj_to_primitive(target_version='1.3')
438 binding = port_v1_3['versioned_object.data']['binding']
439
440 # Port has active binding, so the binding attribute should point to it
441 self.assertEqual('host1', binding['versioned_object.data']['host'])
442 active_binding.delete()
443 port_v1_4 = ports.Port.get_object(self.context, id=port_v1_4.id)
444 port_v1_3 = port_v1_4.obj_to_primitive(target_version='1.3')
445
446 # Port has no active bindings, so binding attribute should be None
447 self.assertIsNone(port_v1_3['versioned_object.data']['binding'])
448
449 # Port with no binding attribute should be handled without raising
450 # exception
451 primitive = port_v1_4.obj_to_primitive()
452 primitive['versioned_object.data'].pop('binding')
453 port_v1_4_no_binding = port_v1_4.obj_from_primitive(primitive)
454 port_v1_4_no_binding.obj_to_primitive(target_version='1.3')
diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py
index cbe2ed3..9a009a6 100644
--- a/neutron/tests/unit/plugins/ml2/test_plugin.py
+++ b/neutron/tests/unit/plugins/ml2/test_plugin.py
@@ -738,9 +738,14 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
738 plugin = directory.get_plugin() 738 plugin = directory.get_plugin()
739 ups = mock.patch.object(plugin, 'update_port_status').start() 739 ups = mock.patch.object(plugin, 'update_port_status').start()
740 port_id = 'fake_port_id' 740 port_id = 'fake_port_id'
741 binding = mock.Mock(vif_type=portbindings.VIF_TYPE_OVS) 741
742 port = mock.Mock( 742 def getitem(key):
743 id=port_id, admin_state_up=False, port_binding=binding) 743 return constants.ACTIVE
744
745 binding = mock.MagicMock(vif_type=portbindings.VIF_TYPE_OVS)
746 binding.__getitem__.side_effect = getitem
747 port = mock.MagicMock(
748 id=port_id, admin_state_up=False, port_binding=[binding])
744 with mock.patch('neutron.plugins.ml2.plugin.db.get_port', 749 with mock.patch('neutron.plugins.ml2.plugin.db.get_port',
745 return_value=port): 750 return_value=port):
746 plugin._port_provisioned('port', 'evt', 'trigger', 751 plugin._port_provisioned('port', 'evt', 'trigger',
@@ -1801,8 +1806,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1801 # create a port and delete it so we have an expired mechanism context 1806 # create a port and delete it so we have an expired mechanism context
1802 with self.port() as port: 1807 with self.port() as port:
1803 plugin = directory.get_plugin() 1808 plugin = directory.get_plugin()
1804 binding = plugin._get_port(self.context, 1809 binding = utils.get_port_binding_by_status_and_host(
1805 port['port']['id']).port_binding 1810 plugin._get_port(self.context,
1811 port['port']['id']).port_binding,
1812 constants.ACTIVE)
1806 binding['host'] = 'test' 1813 binding['host'] = 'test'
1807 mech_context = driver_context.PortContext( 1814 mech_context = driver_context.PortContext(
1808 plugin, self.context, port['port'], 1815 plugin, self.context, port['port'],
@@ -1822,8 +1829,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1822 def _create_port_and_bound_context(self, port_vif_type, bound_vif_type): 1829 def _create_port_and_bound_context(self, port_vif_type, bound_vif_type):
1823 with self.port() as port: 1830 with self.port() as port:
1824 plugin = directory.get_plugin() 1831 plugin = directory.get_plugin()
1825 binding = plugin._get_port( 1832 binding = utils.get_port_binding_by_status_and_host(
1826 self.context, port['port']['id']).port_binding 1833 plugin._get_port(self.context,
1834 port['port']['id']).port_binding,
1835 constants.ACTIVE)
1827 binding['host'] = 'fake_host' 1836 binding['host'] = 'fake_host'
1828 binding['vif_type'] = port_vif_type 1837 binding['vif_type'] = port_vif_type
1829 # Generates port context to be used before the bind. 1838 # Generates port context to be used before the bind.
@@ -1937,8 +1946,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1937 def test_update_port_binding_host_id_none(self): 1946 def test_update_port_binding_host_id_none(self):
1938 with self.port() as port: 1947 with self.port() as port:
1939 plugin = directory.get_plugin() 1948 plugin = directory.get_plugin()
1940 binding = plugin._get_port( 1949 binding = utils.get_port_binding_by_status_and_host(
1941 self.context, port['port']['id']).port_binding 1950 plugin._get_port(self.context,
1951 port['port']['id']).port_binding,
1952 constants.ACTIVE)
1942 with self.context.session.begin(subtransactions=True): 1953 with self.context.session.begin(subtransactions=True):
1943 binding.host = 'test' 1954 binding.host = 'test'
1944 mech_context = driver_context.PortContext( 1955 mech_context = driver_context.PortContext(
@@ -1957,8 +1968,10 @@ class TestMl2PortBinding(Ml2PluginV2TestCase,
1957 def test_update_port_binding_host_id_not_changed(self): 1968 def test_update_port_binding_host_id_not_changed(self):
1958 with self.port() as port: 1969 with self.port() as port:
1959 plugin = directory.get_plugin() 1970 plugin = directory.get_plugin()
1960 binding = plugin._get_port( 1971 binding = utils.get_port_binding_by_status_and_host(
1961 self.context, port['port']['id']).port_binding 1972 plugin._get_port(self.context,
1973 port['port']['id']).port_binding,
1974 constants.ACTIVE)
1962 binding['host'] = 'test' 1975 binding['host'] = 'test'
1963 mech_context = driver_context.PortContext( 1976 mech_context = driver_context.PortContext(
1964 plugin, self.context, port['port'], 1977 plugin, self.context, port['port'],
@@ -2944,8 +2957,10 @@ class TestML2Segments(Ml2PluginV2TestCase):
2944 # add writer here to make sure that the following operations are 2957 # add writer here to make sure that the following operations are
2945 # performed in the same session 2958 # performed in the same session
2946 with db_api.context_manager.writer.using(self.context): 2959 with db_api.context_manager.writer.using(self.context):
2947 binding = plugin._get_port( 2960 binding = utils.get_port_binding_by_status_and_host(
2948 self.context, port['port']['id']).port_binding 2961 plugin._get_port(self.context,
2962 port['port']['id']).port_binding,
2963 constants.ACTIVE)
2949 binding['host'] = 'host-ovs-no_filter' 2964 binding['host'] = 'host-ovs-no_filter'
2950 mech_context = driver_context.PortContext( 2965 mech_context = driver_context.PortContext(
2951 plugin, self.context, port['port'], 2966 plugin, self.context, port['port'],
diff --git a/neutron/tests/unit/plugins/ml2/test_port_binding.py b/neutron/tests/unit/plugins/ml2/test_port_binding.py
index a26ed5e..41c6027 100644
--- a/neutron/tests/unit/plugins/ml2/test_port_binding.py
+++ b/neutron/tests/unit/plugins/ml2/test_port_binding.py
@@ -15,16 +15,23 @@
15 15
16import mock 16import mock
17from neutron_lib.api.definitions import portbindings 17from neutron_lib.api.definitions import portbindings
18from neutron_lib.api.definitions import portbindings_extended as pbe_ext
18from neutron_lib import constants as const 19from neutron_lib import constants as const
19from neutron_lib import context 20from neutron_lib import context
20from neutron_lib.plugins import directory 21from neutron_lib.plugins import directory
21from oslo_config import cfg 22from oslo_config import cfg
22from oslo_serialization import jsonutils 23from oslo_serialization import jsonutils
24import webob.exc
23 25
26from neutron.common import exceptions
27from neutron.common import utils
28from neutron.conf.plugins.ml2 import config
24from neutron.conf.plugins.ml2.drivers import driver_type 29from neutron.conf.plugins.ml2.drivers import driver_type
25from neutron.plugins.ml2 import driver_context 30from neutron.plugins.ml2 import driver_context
26from neutron.plugins.ml2 import models as ml2_models 31from neutron.plugins.ml2 import models as ml2_models
32from neutron.plugins.ml2 import plugin as ml2_plugin
27from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin 33from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
34from neutron.tests.unit.plugins.ml2.drivers import mechanism_test
28 35
29 36
30class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase): 37class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
@@ -327,3 +334,309 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
327 # Get port and verify status is still DOWN. 334 # Get port and verify status is still DOWN.
328 port = self._show('ports', port_id) 335 port = self._show('ports', port_id)
329 self.assertEqual('DOWN', port['port']['status']) 336 self.assertEqual('DOWN', port['port']['status'])
337
338
339class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
340
341 host = 'host-ovs-no_filter'
342
343 def setUp(self):
344 # Enable the test mechanism driver to ensure that
345 # we can successfully call through to all mechanism
346 # driver apis.
347 config.register_ml2_plugin_opts()
348 cfg.CONF.set_override('mechanism_drivers',
349 ['logger', 'test'],
350 'ml2')
351
352 driver_type.register_ml2_drivers_vlan_opts()
353 cfg.CONF.set_override('network_vlan_ranges',
354 ['physnet1:1000:1099'],
355 group='ml2_type_vlan')
356 super(ExtendedPortBindingTestCase, self).setUp('ml2')
357 self.port_create_status = 'DOWN'
358 self.plugin = directory.get_plugin()
359 self.plugin.start_rpc_listeners()
360
361 def _create_port_binding(self, fmt, port_id, host, tenant_id=None,
362 **kwargs):
363 tenant_id = tenant_id or self._tenant_id
364 data = {'binding': {'host': host, 'tenant_id': tenant_id}}
365 if kwargs:
366 data['binding'].update(kwargs)
367 binding_resource = 'ports/%s/bindings' % port_id
368 binding_req = self.new_create_request(binding_resource, data, fmt)
369 return binding_req.get_response(self.api)
370
371 def _make_port_binding(self, fmt, port_id, host, **kwargs):
372 res = self._create_port_binding(fmt, port_id, host, **kwargs)
373 if res.status_int >= webob.exc.HTTPClientError.code:
374 raise webob.exc.HTTPClientError(code=res.status_int)
375 return self.deserialize(fmt, res)
376
377 def _update_port_binding(self, fmt, port_id, host, **kwargs):
378 data = {'binding': kwargs}
379 binding_req = self.new_update_request('ports', data, port_id, fmt,
380 subresource='bindings',
381 sub_id=host)
382 return binding_req.get_response(self.api)
383
384 def _do_update_port_binding(self, fmt, port_id, host, **kwargs):
385 res = self._update_port_binding(fmt, port_id, host, **kwargs)
386 if res.status_int >= webob.exc.HTTPClientError.code:
387 raise webob.exc.HTTPClientError(code=res.status_int)
388 return self.deserialize(fmt, res)
389
390 def _activate_port_binding(self, port_id, host, raw_response=True):
391 response = self._req('PUT', 'ports', id=port_id,
392 data={'port_id': port_id},
393 subresource='bindings', sub_id=host,
394 action='activate').get_response(self.api)
395 return self._check_code_and_serialize(response, raw_response)
396
397 def _check_code_and_serialize(self, response, raw_response):
398 if raw_response:
399 return response
400 if response.status_int >= webob.exc.HTTPClientError.code:
401 raise webob.exc.HTTPClientError(code=response.status_int)
402 return self.deserialize(self.fmt, response)
403
404 def _list_port_bindings(self, port_id, params=None, raw_response=True):
405 response = self._req(
406 'GET', 'ports', fmt=self.fmt, id=port_id, subresource='bindings',
407 params=params).get_response(self.api)
408 return self._check_code_and_serialize(response, raw_response)
409
410 def _show_port_binding(self, port_id, host, params=None,
411 raw_response=True):
412 response = self._req(
413 'GET', 'ports', fmt=self.fmt, id=port_id, subresource='bindings',
414 sub_id=host, params=params).get_response(self.api)
415 return self._check_code_and_serialize(response, raw_response)
416
417 def _delete_port_binding(self, port_id, host):
418 response = self._req(
419 'DELETE', 'ports', fmt=self.fmt, id=port_id,
420 subresource='bindings', sub_id=host).get_response(self.api)
421 return response
422
423 def _create_port_and_binding(self, **kwargs):
424 device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
425 with self.port(device_owner=device_owner) as port:
426 port_id = port['port']['id']
427 binding = self._make_port_binding(self.fmt, port_id, self.host,
428 **kwargs)['binding']
429 self._assert_bound_port_binding(binding)
430 return port['port'], binding
431
432 def _assert_bound_port_binding(self, binding):
433 self.assertEqual(self.host, binding[pbe_ext.HOST])
434 self.assertEqual(portbindings.VIF_TYPE_OVS,
435 binding[pbe_ext.VIF_TYPE])
436 self.assertEqual({'port_filter': False},
437 binding[pbe_ext.VIF_DETAILS])
438
439 def _assert_unbound_port_binding(self, binding):
440 self.assertFalse(binding[pbe_ext.HOST])
441 self.assertEqual(portbindings.VIF_TYPE_UNBOUND,
442 binding[pbe_ext.VIF_TYPE])
443 self.assertEqual({}, binding[pbe_ext.VIF_DETAILS])
444 self.assertEqual({}, binding[pbe_ext.PROFILE])
445
446 def test_create_port_binding(self):
447 profile = {'key1': 'value1'}
448 kwargs = {pbe_ext.PROFILE: profile}
449 port, binding = self._create_port_and_binding(**kwargs)
450 self._assert_bound_port_binding(binding)
451 self.assertEqual({"key1": "value1"}, binding[pbe_ext.PROFILE])
452
453 def test_create_duplicate_port_binding(self):
454 device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
455 host_arg = {portbindings.HOST_ID: self.host}
456 with self.port(device_owner=device_owner,
457 arg_list=(portbindings.HOST_ID,),
458 **host_arg) as port:
459 response = self._create_port_binding(self.fmt, port['port']['id'],
460 self.host)
461 self.assertEqual(webob.exc.HTTPConflict.code,
462 response.status_int)
463
464 def test_create_port_binding_failure(self):
465 device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
466 with self.port(device_owner=device_owner) as port:
467 port_id = port['port']['id']
468 response = self._create_port_binding(self.fmt, port_id,
469 'host-fail')
470 self.assertEqual(webob.exc.HTTPInternalServerError.code,
471 response.status_int)
472 self.assertTrue(exceptions.PortBindingError.__name__ in
473 response.text)
474
475 def test_create_port_binding_for_non_compute_owner(self):
476 with self.port() as port:
477 port_id = port['port']['id']
478 response = self._create_port_binding(self.fmt, port_id,
479 'host-ovs-no_filter')
480 self.assertEqual(webob.exc.HTTPBadRequest.code,
481 response.status_int)
482
483 def test_update_port_binding(self):
484 port, binding = self._create_port_and_binding()
485 profile = {'key1': 'value1'}
486 kwargs = {pbe_ext.PROFILE: profile}
487 binding = self._do_update_port_binding(self.fmt, port['id'], self.host,
488 **kwargs)['binding']
489 self._assert_bound_port_binding(binding)
490 self.assertEqual({"key1": "value1"}, binding[pbe_ext.PROFILE])
491
492 def test_update_non_existing_binding(self):
493 device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
494 with self.port(device_owner=device_owner) as port:
495 port_id = port['port']['id']
496 profile = {'key1': 'value1'}
497 kwargs = {pbe_ext.PROFILE: profile}
498 response = self._update_port_binding(self.fmt, port_id, 'a_host',
499 **kwargs)
500 self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
501
502 def test_update_port_binding_for_non_compute_owner(self):
503 with self.port() as port:
504 port_id = port['port']['id']
505 profile = {'key1': 'value1'}
506 kwargs = {pbe_ext.PROFILE: profile}
507 response = self._update_port_binding(self.fmt, port_id, 'a_host',
508 **kwargs)
509 self.assertEqual(webob.exc.HTTPBadRequest.code,
510 response.status_int)
511
512 def test_update_port_binding_failure(self):
513 class FakeBinding(object):
514 vif_type = portbindings.VIF_TYPE_BINDING_FAILED
515
516 class FakePortContext(object):
517 _binding = FakeBinding()
518
519 port, binding = self._create_port_and_binding()
520 profile = {'key1': 'value1'}
521 kwargs = {pbe_ext.PROFILE: profile}
522 with mock.patch.object(
523 self.plugin, '_bind_port_if_needed',
524 return_value=FakePortContext()):
525 response = self._update_port_binding(self.fmt, port['id'],
526 self.host, **kwargs)
527 self.assertEqual(webob.exc.HTTPInternalServerError.code,
528 response.status_int)
529 self.assertTrue(exceptions.PortBindingError.__name__ in
530 response.text)
531
532 def test_activate_port_binding(self):
533 port, new_binding = self._create_port_and_binding()
534 with mock.patch.object(mechanism_test.TestMechanismDriver,
535 '_check_port_context'):
536 active_binding = self._activate_port_binding(
537 port['id'], self.host, raw_response=False)
538 self._assert_bound_port_binding(active_binding)
539 updated_port = self._show('ports', port['id'])['port']
540 self.assertEqual(new_binding[pbe_ext.HOST],
541 updated_port[portbindings.HOST_ID])
542 self.assertEqual(new_binding[pbe_ext.PROFILE],
543 updated_port[portbindings.PROFILE])
544 self.assertEqual(new_binding[pbe_ext.VNIC_TYPE],
545 updated_port[portbindings.VNIC_TYPE])
546 self.assertEqual(new_binding[pbe_ext.VIF_TYPE],
547 updated_port[portbindings.VIF_TYPE])
548 self.assertEqual(new_binding[pbe_ext.VIF_DETAILS],
549 updated_port[portbindings.VIF_DETAILS])
550 retrieved_bindings = self._list_port_bindings(
551 port['id'], raw_response=False)['bindings']
552 retrieved_active_binding = utils.get_port_binding_by_status_and_host(
553 retrieved_bindings, const.ACTIVE)
554 self._assert_bound_port_binding(retrieved_active_binding)
555 retrieved_inactive_binding = utils.get_port_binding_by_status_and_host(
556 retrieved_bindings, const.INACTIVE)
557 self._assert_unbound_port_binding(retrieved_inactive_binding)
558
559 def test_activate_port_binding_for_non_compute_owner(self):
560 port, new_binding = self._create_port_and_binding()
561 data = {'port': {'device_owner': ''}}
562 self.new_update_request('ports', data, port['id'],
563 self.fmt).get_response(self.api)
564 response = self._activate_port_binding(port['id'], self.host)
565 self.assertEqual(webob.exc.HTTPBadRequest.code,
566 response.status_int)
567
568 def test_activate_port_binding_already_active(self):
569 port, new_binding = self._create_port_and_binding()
570 with mock.patch.object(mechanism_test.TestMechanismDriver,
571 '_check_port_context'):
572 self._activate_port_binding(port['id'], self.host)
573 response = self._activate_port_binding(port['id'], self.host)
574 self.assertEqual(webob.exc.HTTPConflict.code,
575 response.status_int)
576
577 def test_activate_port_binding_failure(self):
578 port, new_binding = self._create_port_and_binding()
579 with mock.patch.object(self.plugin, '_commit_port_binding',
580 return_value=(None, None, True,)) as p_mock:
581 response = self._activate_port_binding(port['id'], self.host)
582 self.assertEqual(webob.exc.HTTPInternalServerError.code,
583 response.status_int)
584 self.assertTrue(exceptions.PortBindingError.__name__ in
585 response.text)
586 self.assertEqual(ml2_plugin.MAX_BIND_TRIES, p_mock.call_count)
587
588 def test_activate_port_binding_non_existing_binding(self):
589 port, new_binding = self._create_port_and_binding()
590 response = self._activate_port_binding(port['id'], 'other-host')
591 self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
592
593 def test_list_port_bindings(self):
594 port, new_binding = self._create_port_and_binding()
595 retrieved_bindings = self._list_port_bindings(
596 port['id'], raw_response=False)['bindings']
597 self.assertEqual(2, len(retrieved_bindings))
598 status = const.ACTIVE
599 self._assert_unbound_port_binding(
600 utils.get_port_binding_by_status_and_host(retrieved_bindings,
601 status))
602 status = const.INACTIVE
603 self._assert_bound_port_binding(
604 utils.get_port_binding_by_status_and_host(retrieved_bindings,
605 status, host=self.host))
606
607 def test_list_port_bindings_with_query_parameters(self):
608 port, new_binding = self._create_port_and_binding()
609 params = '%s=%s' % (pbe_ext.STATUS, const.INACTIVE)
610 retrieved_bindings = self._list_port_bindings(
611 port['id'], params=params, raw_response=False)['bindings']
612 self.assertEqual(1, len(retrieved_bindings))
613 self._assert_bound_port_binding(retrieved_bindings[0])
614
615 def test_show_port_binding(self):
616 port, new_binding = self._create_port_and_binding()
617 retrieved_binding = self._show_port_binding(
618 port['id'], self.host, raw_response=False)['binding']
619 self._assert_bound_port_binding(retrieved_binding)
620
621 def test_show_port_binding_with_fields(self):
622 port, new_binding = self._create_port_and_binding()
623 fields = 'fields=%s' % pbe_ext.HOST
624 retrieved_binding = self._show_port_binding(
625 port['id'], self.host, raw_response=False,
626 params=fields)['binding']
627 self.assertEqual(self.host, retrieved_binding[pbe_ext.HOST])
628 for key in (pbe_ext.STATUS, pbe_ext.PROFILE, pbe_ext.VNIC_TYPE,
629 pbe_ext.VIF_TYPE, pbe_ext.VIF_DETAILS,):
630 self.assertNotIn(key, retrieved_binding)
631
632 def test_delete_port_binding(self):
633 port, new_binding = self._create_port_and_binding()
634 response = self._delete_port_binding(port['id'], self.host)
635 self.assertEqual(webob.exc.HTTPNoContent.code, response.status_int)
636 response = self._show_port_binding(port['id'], self.host)
637 self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
638
639 def test_delete_non_existing_port_binding(self):
640 port, new_binding = self._create_port_and_binding()
641 response = self._delete_port_binding(port['id'], 'other-host')
642 self.assertEqual(webob.exc.HTTPNotFound.code, response.status_int)
diff --git a/neutron/tests/unit/services/logapi/common/test_validators.py b/neutron/tests/unit/services/logapi/common/test_validators.py
index 30b6869..dabc93e 100644
--- a/neutron/tests/unit/services/logapi/common/test_validators.py
+++ b/neutron/tests/unit/services/logapi/common/test_validators.py
@@ -105,7 +105,7 @@ class TestLogDriversLoggingTypeValidations(drv_mgr.TestLogDriversManagerBase):
105 port_binding = ports.PortBinding( 105 port_binding = ports.PortBinding(
106 self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type) 106 self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type)
107 return ports.Port( 107 return ports.Port(
108 self.ctxt, id=uuidutils.generate_uuid(), binding=port_binding) 108 self.ctxt, id=uuidutils.generate_uuid(), binding=[port_binding])
109 109
110 def _test_validate_log_type_for_port(self, port, expected_result): 110 def _test_validate_log_type_for_port(self, port, expected_result):
111 driver_manager = self._create_manager_with_drivers({ 111 driver_manager = self._create_manager_with_drivers({
diff --git a/neutron/tests/unit/services/qos/drivers/test_manager.py b/neutron/tests/unit/services/qos/drivers/test_manager.py
index e239793..52cf2b7 100644
--- a/neutron/tests/unit/services/qos/drivers/test_manager.py
+++ b/neutron/tests/unit/services/qos/drivers/test_manager.py
@@ -86,7 +86,7 @@ class TestQoSDriversRulesValidations(TestQosDriversManagerBase):
86 port_binding = ports_object.PortBinding( 86 port_binding = ports_object.PortBinding(
87 self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type) 87 self.ctxt, port_id=port_id, vif_type=vif_type, vnic_type=vnic_type)
88 return ports_object.Port( 88 return ports_object.Port(
89 self.ctxt, id=uuidutils.generate_uuid(), binding=port_binding) 89 self.ctxt, id=uuidutils.generate_uuid(), binding=[port_binding])
90 90
91 def _test_validate_rule_for_port(self, port, expected_result): 91 def _test_validate_rule_for_port(self, port, expected_result):
92 driver_manager = self._create_manager_with_drivers({ 92 driver_manager = self._create_manager_with_drivers({