diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index b0412464662..3fa8d9bf9ef 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -1422,6 +1422,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, need_port_update_notify |= self._update_extra_dhcp_opts_on_port( context, id, port, updated_port) levels = db.get_binding_levels(session, id, binding.host) + # one of the operations above may have altered the model call + # _make_port_dict again to ensure latest state is reflected so mech + # drivers, callback handlers, and the API caller see latest state. + # We expire here to reflect changed relationships on the obj. + # Repeatable read will ensure we still get the state from this + # transaction in spite of concurrent updates/deletes. + context.session.expire(port_db) + updated_port.update(self._make_port_dict(port_db)) mech_context = driver_context.PortContext( self, context, updated_port, network, binding, levels, original_port=original_port) @@ -1719,6 +1727,9 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE): original_port = self._make_port_dict(port) port.status = status + # explicit flush before _make_port_dict to ensure extensions + # listening for db events can modify the port if necessary + context.session.flush() updated_port = self._make_port_dict(port) network = network or self.get_network( context, original_port['network_id']) diff --git a/neutron/tests/tempest/api/test_allowed_address_pair.py b/neutron/tests/tempest/api/test_allowed_address_pair.py index e4c499cf9f6..531378505ac 100644 --- a/neutron/tests/tempest/api/test_allowed_address_pair.py +++ b/neutron/tests/tempest/api/test_allowed_address_pair.py @@ -83,7 +83,7 @@ class AllowedAddressPairTestJSON(base.BaseNetworkTest): body = self.client.update_port( port_id, allowed_address_pairs=allowed_address_pairs) allowed_address_pair = body['port']['allowed_address_pairs'] - self.assertEqual(allowed_address_pair, allowed_address_pairs) + self.assertItemsEqual(allowed_address_pair, allowed_address_pairs) @test.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4') def test_update_port_with_address_pair(self): diff --git a/neutron/tests/tempest/api/test_revisions.py b/neutron/tests/tempest/api/test_revisions.py index 10438b72ce0..2d5b965d147 100644 --- a/neutron/tests/tempest/api/test_revisions.py +++ b/neutron/tests/tempest/api/test_revisions.py @@ -90,12 +90,7 @@ class TestRevisions(base.BaseAdminNetworkTest, bsg.BaseSecGroupTest): self.client.update_port( port['id'], security_groups=[sg['security_group']['id']]) updated = self.client.show_port(port['id']) - self.client.update_port(port['id'], security_groups=[]) - # TODO(kevinbenton): these extra shows after after the update are - # to work around the fact that ML2 creates the result dict before - # commit happens if the port is unbound. The update response should - # be usable directly once that is fixed. - updated2 = self.client.show_port(port['id']) + updated2 = self.client.update_port(port['id'], security_groups=[]) self.assertGreater(updated['port']['revision_number'], port['revision_number']) self.assertGreater(updated2['port']['revision_number'], diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index b5cf900dd02..402e7c7349f 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -60,6 +60,7 @@ from neutron.plugins.ml2 import models from neutron.plugins.ml2 import plugin as ml2_plugin from neutron.services.l3_router import l3_router_plugin from neutron.services.qos import qos_consts +from neutron.services.revisions import revision_plugin from neutron.services.segments import db as segments_plugin_db from neutron.services.segments import plugin as segments_plugin from neutron.tests import base @@ -1119,6 +1120,30 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase): self.assertTrue(listener.except_raised) +class TestMl2PortsV2WithRevisionPlugin(Ml2PluginV2TestCase): + + def setUp(self): + super(TestMl2PortsV2WithRevisionPlugin, self).setUp() + self.revision_plugin = revision_plugin.RevisionPlugin() + + def test_update_port_status_bumps_revision(self): + ctx = context.get_admin_context() + plugin = manager.NeutronManager.get_plugin() + host_arg = {portbindings.HOST_ID: HOST} + with self.port(arg_list=(portbindings.HOST_ID,), + **host_arg) as port: + port = plugin.get_port(ctx, port['port']['id']) + updated_ports = [] + receiver = lambda *a, **k: updated_ports.append(k['port']) + registry.subscribe(receiver, resources.PORT, + events.AFTER_UPDATE) + plugin.update_port_status( + ctx, port['id'], + constants.PORT_STATUS_ACTIVE, host=HOST) + self.assertGreater(updated_ports[0]['revision_number'], + port['revision_number']) + + class TestMl2PortsV2WithL3(test_plugin.TestPortsV2, Ml2PluginV2TestCase): """For testing methods that require the L3 service plugin."""