Skip to content

Commit b8e53c2

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Limit trunk ACTIVE state hack to OVN" into unmaintained/2023.1
2 parents bfd82b1 + 52aa69d commit b8e53c2

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)