Skip to content

Commit f28079a

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) (cherry picked from commit 2224c9e) (cherry picked from commit fd9d907) (cherry picked from commit 24c492c) (cherry picked from commit 52aa69d)
1 parent 7227063 commit f28079a

File tree

4 files changed

+117
-36
lines changed

4 files changed

+117
-36
lines changed

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

Lines changed: 47 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
@@ -26,6 +27,7 @@
2627
from neutron.db import db_base_plugin_common
2728
from neutron.db import ovn_revision_numbers_db as db_rev
2829
from neutron.objects import ports as port_obj
30+
from neutron.objects import trunk as trunk_objects
2931
from neutron.services.trunk.drivers import base as trunk_base
3032
from neutron.services.trunk import exceptions as trunk_exc
3133

@@ -216,6 +218,46 @@ def subport_event(self, resource, event, trunk_plugin, payload):
216218
self.subports_deleted(payload.states[0],
217219
payload.metadata['subports'])
218220

221+
def port_updated(self, resource, event, trunk_plugin, payload):
222+
'''Propagate trunk parent port ACTIVE to trunk ACTIVE
223+
224+
During a live migration with a trunk the only way we found to update
225+
the trunk to ACTIVE is to do this when the trunk's parent port gets
226+
updated to ACTIVE. This is clearly suboptimal, because the trunk's
227+
ACTIVE status should mean that all of its ports (parent and sub) are
228+
active. But in ml2/ovn the parent port's binding is not cascaded to the
229+
subports. Actually the subports' binding:host is left empty. This way
230+
here we don't know anything about the subports' state changes during a
231+
live migration. If we don't want to leave the trunk in DOWN this is
232+
what we have.
233+
234+
Please note that this affects trunk create as well. Because of this we
235+
move ml2/ovn trunks to ACTIVE way early. But at least here we don't
236+
affect other mechanism drivers and their corresponding trunk drivers.
237+
238+
See also:
239+
https://bugs.launchpad.net/neutron/+bug/1988549
240+
https://review.opendev.org/c/openstack/neutron/+/853779
241+
https://bugs.launchpad.net/neutron/+bug/2095152
242+
'''
243+
updated_port = payload.latest_state
244+
trunk_details = updated_port.get('trunk_details')
245+
# If no trunk_details, the port is not the parent of a trunk.
246+
if not trunk_details:
247+
return
248+
249+
original_port = payload.states[0]
250+
orig_status = original_port.get('status')
251+
new_status = updated_port.get('status')
252+
context = payload.context
253+
trunk_id = trunk_details['trunk_id']
254+
if (new_status == constants.PORT_STATUS_ACTIVE and
255+
new_status != orig_status):
256+
trunk = trunk_objects.Trunk.get_object(context, id=trunk_id)
257+
if trunk is None:
258+
raise trunk_exc.TrunkNotFound(trunk_id=trunk_id)
259+
trunk.update(status=trunk_consts.TRUNK_ACTIVE_STATUS)
260+
219261

220262
class OVNTrunkDriver(trunk_base.DriverBase):
221263
@property
@@ -242,6 +284,11 @@ def register(self, resource, event, trigger, payload=None):
242284
resources.SUBPORTS,
243285
_event)
244286

287+
registry.subscribe(
288+
self._handler.port_updated,
289+
resources.PORT,
290+
events.AFTER_UPDATE)
291+
245292
@classmethod
246293
def create(cls, plugin_driver):
247294
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
@@ -465,19 +464,12 @@ def _trigger_trunk_status_change(self, resource, event, trigger, payload):
465464
original_port = payload.states[0]
466465
orig_vif_type = original_port.get(portbindings.VIF_TYPE)
467466
new_vif_type = updated_port.get(portbindings.VIF_TYPE)
468-
orig_status = original_port.get('status')
469-
new_status = updated_port.get('status')
470467
vif_type_changed = orig_vif_type != new_vif_type
471-
trunk_id = trunk_details['trunk_id']
472468
if vif_type_changed and new_vif_type == portbindings.VIF_TYPE_UNBOUND:
469+
trunk_id = trunk_details['trunk_id']
473470
# NOTE(status_police) Trunk status goes to DOWN when the parent
474471
# port is unbound. This means there are no more physical resources
475472
# associated with the logical resource.
476473
self.update_trunk(
477474
context, trunk_id,
478475
{'trunk': {'status': constants.TRUNK_DOWN_STATUS}})
479-
elif new_status == const.PORT_STATUS_ACTIVE and \
480-
new_status != orig_status:
481-
self.update_trunk(
482-
context, trunk_id,
483-
{'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 TestTrunkHandler(base.BaseTestCase):
@@ -363,6 +368,70 @@ def test_subport_event_invalid(self, s_deleted, s_added):
363368
s_deleted.assert_not_called()
364369

365370

371+
class TestTrunkHandlerWithPlugin(test_plugin.Ml2PluginV2TestCase):
372+
def setUp(self):
373+
super().setUp()
374+
self.drivers_patch = mock.patch.object(drivers, 'register').start()
375+
self.compat_patch = mock.patch.object(
376+
trunk_plugin.TrunkPlugin, 'check_compatibility').start()
377+
self.trunk_plugin = trunk_plugin.TrunkPlugin()
378+
self.trunk_plugin.add_segmentation_type('vlan', lambda x: True)
379+
self.plugin_driver = mock.Mock()
380+
self.trunk_handler = trunk_driver.OVNTrunkHandler(self.plugin_driver)
381+
382+
def _create_test_trunk(self, port, subports=None):
383+
subports = subports if subports else []
384+
trunk = {'port_id': port['port']['id'],
385+
'project_id': 'test_tenant',
386+
'sub_ports': subports}
387+
response = (
388+
self.trunk_plugin.create_trunk(self.context, {'trunk': trunk}))
389+
return response
390+
391+
def _get_trunk_obj(self, trunk_id):
392+
return trunk_objects.Trunk.get_object(self.context, id=trunk_id)
393+
394+
def test_parent_active_triggers_trunk_active(self):
395+
with self.port() as new_parent:
396+
new_parent['status'] = nlib_consts.PORT_STATUS_ACTIVE
397+
old_parent = {'status': nlib_consts.PORT_STATUS_DOWN}
398+
old_trunk = self._create_test_trunk(new_parent)
399+
old_trunk = self._get_trunk_obj(old_trunk['id'])
400+
old_trunk.update(status=trunk_consts.TRUNK_DOWN_STATUS)
401+
trunk_details = {'trunk_id': old_trunk.id}
402+
new_parent['trunk_details'] = trunk_details
403+
old_parent['trunk_details'] = trunk_details
404+
self.trunk_handler.port_updated(
405+
resources.PORT,
406+
events.AFTER_UPDATE,
407+
None,
408+
payload=events.DBEventPayload(
409+
self.context, states=(old_parent, new_parent)))
410+
new_trunk = self._get_trunk_obj(old_trunk.id)
411+
self.assertEqual(
412+
trunk_consts.TRUNK_ACTIVE_STATUS, new_trunk.status)
413+
414+
def test_parent_build_does_not_trigger_trunk_active(self):
415+
with self.port() as new_parent:
416+
new_parent['status'] = nlib_consts.PORT_STATUS_BUILD
417+
old_parent = {'status': nlib_consts.PORT_STATUS_DOWN}
418+
old_trunk = self._create_test_trunk(new_parent)
419+
old_trunk = self._get_trunk_obj(old_trunk['id'])
420+
old_trunk.update(status=trunk_consts.TRUNK_DOWN_STATUS)
421+
trunk_details = {'trunk_id': old_trunk.id}
422+
new_parent['trunk_details'] = trunk_details
423+
old_parent['trunk_details'] = trunk_details
424+
self.trunk_handler.port_updated(
425+
resources.PORT,
426+
events.AFTER_UPDATE,
427+
None,
428+
payload=events.DBEventPayload(
429+
self.context, states=(old_parent, new_parent)))
430+
new_trunk = self._get_trunk_obj(old_trunk.id)
431+
self.assertNotEqual(
432+
trunk_consts.TRUNK_ACTIVE_STATUS, new_trunk.status)
433+
434+
366435
class TestTrunkDriver(base.BaseTestCase):
367436
def setUp(self):
368437
super(TestTrunkDriver, self).setUp()

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
@@ -287,32 +286,6 @@ def test_remove_subports_trunk_goes_to_down(self):
287286
{'sub_ports': [{'port_id': subport['port']['id']}]})
288287
self.assertEqual(constants.TRUNK_DOWN_STATUS, trunk['status'])
289288

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

0 commit comments

Comments
 (0)