Skip to content

Commit b9f588c

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Limit trunk ACTIVE state hack to OVN" into unmaintained/yoga
2 parents f74cd38 + ca0ec2f commit b9f588c

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)