Skip to content

Commit 2224c9e

Browse files
committed
Limit trunk ACTIVE state hack to OVN
In https://review.opendev.org/c/openstack/neutron/+/853779 we started moving a trunk to ACTIVE when its parent port went to ACTIVE. The intention was to not leave the trunk in DOWN after a live migration as reported in #1988549. However this had side effects. Earlier we moved a trunk to ACTIVE when all of its ports were processed. That means we unintentionally changed the meaning of the trunk ACTIVE status. This affected all backends and not just live migrate but create too. This change moves the logic of propagating the trunk parent's ACTIVE to the trunk itself to the OVN trunk driver, so we limit the undesired effects to ml2/ovn. By that we restore the original meaning of trunk ACTIVE for all non-OVN backends. Ideally we would want to limit the effect to live migrate (so we don't affect create) but I did not find a way to do that. Change-Id: I4d2c3db355e29fffcce0f50cd12bb1e31d1be43a Closes-Bug: #2095152 Related-Bug: #1988549 Related-Change: https://review.opendev.org/c/openstack/os-vif/+/949736 Signed-off-by: Bence Romsics <[email protected]> (cherry picked from commit e69505d)
1 parent d11f0be commit 2224c9e

File tree

4 files changed

+118
-36
lines changed

4 files changed

+118
-36
lines changed

neutron/services/trunk/drivers/ovn/trunk_driver.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from neutron_lib.callbacks import events
1515
from neutron_lib.callbacks import registry
1616
from neutron_lib.callbacks import resources
17+
from neutron_lib import constants
1718
from neutron_lib import context as n_context
1819
from neutron_lib.db import api as db_api
1920
from neutron_lib import exceptions as n_exc
@@ -25,7 +26,9 @@
2526
from neutron.db import db_base_plugin_common
2627
from neutron.db import ovn_revision_numbers_db as db_rev
2728
from neutron.objects import ports as port_obj
29+
from neutron.objects import trunk as trunk_objects
2830
from neutron.services.trunk.drivers import base as trunk_base
31+
from neutron.services.trunk import exceptions as trunk_exc
2932

3033

3134
SUPPORTED_INTERFACES = (
@@ -192,6 +195,46 @@ def subports_deleted(self, resource, event, trunk_plugin, payload):
192195
self._unset_sub_ports(subports)
193196
trunk.update(status=trunk_consts.TRUNK_ACTIVE_STATUS)
194197

198+
def port_updated(self, resource, event, trunk_plugin, payload):
199+
'''Propagate trunk parent port ACTIVE to trunk ACTIVE
200+
201+
During a live migration with a trunk the only way we found to update
202+
the trunk to ACTIVE is to do this when the trunk's parent port gets
203+
updated to ACTIVE. This is clearly suboptimal, because the trunk's
204+
ACTIVE status should mean that all of its ports (parent and sub) are
205+
active. But in ml2/ovn the parent port's binding is not cascaded to the
206+
subports. Actually the subports' binding:host is left empty. This way
207+
here we don't know anything about the subports' state changes during a
208+
live migration. If we don't want to leave the trunk in DOWN this is
209+
what we have.
210+
211+
Please note that this affects trunk create as well. Because of this we
212+
move ml2/ovn trunks to ACTIVE way early. But at least here we don't
213+
affect other mechanism drivers and their corresponding trunk drivers.
214+
215+
See also:
216+
https://bugs.launchpad.net/neutron/+bug/1988549
217+
https://review.opendev.org/c/openstack/neutron/+/853779
218+
https://bugs.launchpad.net/neutron/+bug/2095152
219+
'''
220+
updated_port = payload.latest_state
221+
trunk_details = updated_port.get('trunk_details')
222+
# If no trunk_details, the port is not the parent of a trunk.
223+
if not trunk_details:
224+
return
225+
226+
original_port = payload.states[0]
227+
orig_status = original_port.get('status')
228+
new_status = updated_port.get('status')
229+
context = payload.context
230+
trunk_id = trunk_details['trunk_id']
231+
if (new_status == constants.PORT_STATUS_ACTIVE and
232+
new_status != orig_status):
233+
trunk = trunk_objects.Trunk.get_object(context, id=trunk_id)
234+
if trunk is None:
235+
raise trunk_exc.TrunkNotFound(trunk_id=trunk_id)
236+
trunk.update(status=trunk_consts.TRUNK_ACTIVE_STATUS)
237+
195238

196239
class OVNTrunkDriver(trunk_base.DriverBase):
197240
@property
@@ -222,6 +265,11 @@ def register(self, resource, event, trigger, payload=None):
222265
resources.SUBPORTS,
223266
events.AFTER_DELETE)
224267

268+
registry.subscribe(
269+
self._handler.port_updated,
270+
resources.PORT,
271+
events.AFTER_UPDATE)
272+
225273
@classmethod
226274
def create(cls, plugin_driver):
227275
cls.plugin_driver = plugin_driver

neutron/services/trunk/plugin.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from neutron_lib.callbacks import events
2222
from neutron_lib.callbacks import registry
2323
from neutron_lib.callbacks import resources
24-
from neutron_lib import constants as const
2524
from neutron_lib import context
2625
from neutron_lib.db import api as db_api
2726
from neutron_lib.db import resource_extend
@@ -471,19 +470,12 @@ def _trigger_trunk_status_change(self, resource, event, trigger, payload):
471470
original_port = payload.states[0]
472471
orig_vif_type = original_port.get(portbindings.VIF_TYPE)
473472
new_vif_type = updated_port.get(portbindings.VIF_TYPE)
474-
orig_status = original_port.get('status')
475-
new_status = updated_port.get('status')
476473
vif_type_changed = orig_vif_type != new_vif_type
477-
trunk_id = trunk_details['trunk_id']
478474
if vif_type_changed and new_vif_type == portbindings.VIF_TYPE_UNBOUND:
475+
trunk_id = trunk_details['trunk_id']
479476
# NOTE(status_police) Trunk status goes to DOWN when the parent
480477
# port is unbound. This means there are no more physical resources
481478
# associated with the logical resource.
482479
self.update_trunk(
483480
context, trunk_id,
484481
{'trunk': {'status': constants.TRUNK_DOWN_STATUS}})
485-
elif new_status == const.PORT_STATUS_ACTIVE and \
486-
new_status != orig_status:
487-
self.update_trunk(
488-
context, trunk_id,
489-
{'trunk': {'status': constants.TRUNK_ACTIVE_STATUS}})

neutron/tests/unit/services/trunk/drivers/ovn/test_trunk_driver.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@
1818
from neutron_lib.callbacks import events
1919
from neutron_lib.callbacks import registry
2020
from neutron_lib.callbacks import resources
21+
from neutron_lib import constants as nlib_consts
2122
from neutron_lib import exceptions as n_exc
2223
from neutron_lib.services.trunk import constants as trunk_consts
2324
from oslo_config import cfg
2425

2526
from neutron.common.ovn.constants import OVN_ML2_MECH_DRIVER_NAME
2627
from neutron.objects.ports import Port
2728
from neutron.objects.ports import PortBinding
29+
from neutron.objects import trunk as trunk_objects
30+
from neutron.services.trunk import drivers
2831
from neutron.services.trunk.drivers.ovn import trunk_driver
32+
from neutron.services.trunk import plugin as trunk_plugin
2933
from neutron.tests import base
3034
from neutron.tests.unit import fake_resources
35+
from neutron.tests.unit.plugins.ml2 import test_plugin
3136

3237

3338
class FakePayload:
@@ -430,6 +435,70 @@ def test_subports_deleted_no_parent(self, m__unset_sub_ports):
430435
m__unset_sub_ports.assert_not_called()
431436

432437

438+
class TestTrunkHandlerWithPlugin(test_plugin.Ml2PluginV2TestCase):
439+
def setUp(self):
440+
super().setUp()
441+
self.drivers_patch = mock.patch.object(drivers, 'register').start()
442+
self.compat_patch = mock.patch.object(
443+
trunk_plugin.TrunkPlugin, 'check_compatibility').start()
444+
self.trunk_plugin = trunk_plugin.TrunkPlugin()
445+
self.trunk_plugin.add_segmentation_type('vlan', lambda x: True)
446+
self.plugin_driver = mock.Mock()
447+
self.trunk_handler = trunk_driver.OVNTrunkHandler(self.plugin_driver)
448+
449+
def _create_test_trunk(self, port, subports=None):
450+
subports = subports if subports else []
451+
trunk = {'port_id': port['port']['id'],
452+
'project_id': 'test_tenant',
453+
'sub_ports': subports}
454+
response = (
455+
self.trunk_plugin.create_trunk(self.context, {'trunk': trunk}))
456+
return response
457+
458+
def _get_trunk_obj(self, trunk_id):
459+
return trunk_objects.Trunk.get_object(self.context, id=trunk_id)
460+
461+
def test_parent_active_triggers_trunk_active(self):
462+
with self.port() as new_parent:
463+
new_parent['status'] = nlib_consts.PORT_STATUS_ACTIVE
464+
old_parent = {'status': nlib_consts.PORT_STATUS_DOWN}
465+
old_trunk = self._create_test_trunk(new_parent)
466+
old_trunk = self._get_trunk_obj(old_trunk['id'])
467+
old_trunk.update(status=trunk_consts.TRUNK_DOWN_STATUS)
468+
trunk_details = {'trunk_id': old_trunk.id}
469+
new_parent['trunk_details'] = trunk_details
470+
old_parent['trunk_details'] = trunk_details
471+
self.trunk_handler.port_updated(
472+
resources.PORT,
473+
events.AFTER_UPDATE,
474+
None,
475+
payload=events.DBEventPayload(
476+
self.context, states=(old_parent, new_parent)))
477+
new_trunk = self._get_trunk_obj(old_trunk.id)
478+
self.assertEqual(
479+
trunk_consts.TRUNK_ACTIVE_STATUS, new_trunk.status)
480+
481+
def test_parent_build_does_not_trigger_trunk_active(self):
482+
with self.port() as new_parent:
483+
new_parent['status'] = nlib_consts.PORT_STATUS_BUILD
484+
old_parent = {'status': nlib_consts.PORT_STATUS_DOWN}
485+
old_trunk = self._create_test_trunk(new_parent)
486+
old_trunk = self._get_trunk_obj(old_trunk['id'])
487+
old_trunk.update(status=trunk_consts.TRUNK_DOWN_STATUS)
488+
trunk_details = {'trunk_id': old_trunk.id}
489+
new_parent['trunk_details'] = trunk_details
490+
old_parent['trunk_details'] = trunk_details
491+
self.trunk_handler.port_updated(
492+
resources.PORT,
493+
events.AFTER_UPDATE,
494+
None,
495+
payload=events.DBEventPayload(
496+
self.context, states=(old_parent, new_parent)))
497+
new_trunk = self._get_trunk_obj(old_trunk.id)
498+
self.assertNotEqual(
499+
trunk_consts.TRUNK_ACTIVE_STATUS, new_trunk.status)
500+
501+
433502
class TestTrunkDriver(base.BaseTestCase):
434503
def test_is_loaded(self):
435504
driver = trunk_driver.OVNTrunkDriver.create(mock.Mock())

neutron/tests/unit/services/trunk/test_plugin.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from neutron_lib.callbacks import events
2020
from neutron_lib.callbacks import registry
2121
from neutron_lib.callbacks import resources
22-
from neutron_lib import constants as neutron_const
2322
from neutron_lib.plugins import directory
2423
from neutron_lib.services.trunk import constants
2524
import testtools
@@ -286,32 +285,6 @@ def test_remove_subports_trunk_goes_to_down(self):
286285
{'sub_ports': [{'port_id': subport['port']['id']}]})
287286
self.assertEqual(constants.TRUNK_DOWN_STATUS, trunk['status'])
288287

289-
def test__trigger_trunk_status_change_parent_port_status_down(self):
290-
callback = register_mock_callback(resources.TRUNK, events.AFTER_UPDATE)
291-
with self.port() as parent:
292-
parent['status'] = neutron_const.PORT_STATUS_DOWN
293-
original_port = {'status': neutron_const.PORT_STATUS_DOWN}
294-
_, _ = (
295-
self._test__trigger_trunk_status_change(
296-
parent, original_port,
297-
constants.TRUNK_DOWN_STATUS,
298-
constants.TRUNK_DOWN_STATUS))
299-
callback.assert_not_called()
300-
301-
def test__trigger_trunk_status_change_parent_port_status_up(self):
302-
callback = register_mock_callback(resources.TRUNK, events.AFTER_UPDATE)
303-
with self.port() as parent:
304-
parent['status'] = neutron_const.PORT_STATUS_ACTIVE
305-
original_port = {'status': neutron_const.PORT_STATUS_DOWN}
306-
_, _ = (
307-
self._test__trigger_trunk_status_change(
308-
parent, original_port,
309-
constants.TRUNK_DOWN_STATUS,
310-
constants.TRUNK_ACTIVE_STATUS))
311-
callback.assert_called_once_with(
312-
resources.TRUNK, events.AFTER_UPDATE,
313-
self.trunk_plugin, payload=mock.ANY)
314-
315288
def test__trigger_trunk_status_change_vif_type_changed_unbound(self):
316289
callback = register_mock_callback(resources.TRUNK, events.AFTER_UPDATE)
317290
with self.port() as parent:

0 commit comments

Comments
 (0)