Skip to content

Commit 840279f

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Limit trunk ACTIVE state hack to OVN" into stable/2025.1
2 parents 8124581 + 2224c9e commit 840279f

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)