Skip to content

Commit 52aa69d

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)
1 parent 59c9ad4 commit 52aa69d

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 = (
@@ -200,6 +203,46 @@ def subport_event(self, resource, event, trunk_plugin, payload):
200203
self.subports_deleted(payload.states[0],
201204
payload.metadata['subports'])
202205

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

204247
class OVNTrunkDriver(trunk_base.DriverBase):
205248
@property
@@ -225,6 +268,11 @@ def register(self, resource, event, trigger, payload=None):
225268
resources.SUBPORTS,
226269
_event)
227270

271+
registry.subscribe(
272+
self._handler.port_updated,
273+
resources.PORT,
274+
events.AFTER_UPDATE)
275+
228276
@classmethod
229277
def create(cls, plugin_driver):
230278
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
@@ -464,19 +463,12 @@ def _trigger_trunk_status_change(self, resource, event, trigger, payload):
464463
original_port = payload.states[0]
465464
orig_vif_type = original_port.get(portbindings.VIF_TYPE)
466465
new_vif_type = updated_port.get(portbindings.VIF_TYPE)
467-
orig_status = original_port.get('status')
468-
new_status = updated_port.get('status')
469466
vif_type_changed = orig_vif_type != new_vif_type
470-
trunk_id = trunk_details['trunk_id']
471467
if vif_type_changed and new_vif_type == portbindings.VIF_TYPE_UNBOUND:
468+
trunk_id = trunk_details['trunk_id']
472469
# NOTE(status_police) Trunk status goes to DOWN when the parent
473470
# port is unbound. This means there are no more physical resources
474471
# associated with the logical resource.
475472
self.update_trunk(
476473
context, trunk_id,
477474
{'trunk': {'status': constants.TRUNK_DOWN_STATUS}})
478-
elif new_status == const.PORT_STATUS_ACTIVE and \
479-
new_status != orig_status:
480-
self.update_trunk(
481-
context, trunk_id,
482-
{'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
@@ -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)