Skip to content

Commit 1514fbe

Browse files
committed
[OVN] Set the Neutron port status based on "lsp.up" and "lsp.enabled"
The Neutron "port.status" field ("ACTIVE", "DOWN") is set depending on the Logical Switch Port "up" and "enabled" flags. Before this patch, the "port.status" depended only on the "up" flag. However, the user can set the "port.admin_state_up" field that will modify the "lsp.enabled" flag. If the "port.admin_state_up" is DOWN, the port does not transmit and the status should be "DOWN". The OVN backend is correctly disabling the port transmission; what was incorrect in the Neutron API is only the "port.status" field. This is currently working in other mechanism drivers like LB or OVS. NOTE: this backport also includes [1], that is updating the release note section to "fixes". [1]https://review.opendev.org/c/openstack/neutron/+/903863 Closes-Bug: #2036705 Change-Id: I5b7b55b0b365df7246a571cea97a392cdf89bdb6 (cherry picked from commit aead4aa) (cherry picked from commit d084f27)
1 parent 98a3eae commit 1514fbe

File tree

4 files changed

+120
-65
lines changed

4 files changed

+120
-65
lines changed

neutron/common/ovn/utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,33 @@ def get_lsp_security_groups(port, skip_trusted_port=True):
299299
) else port.get('security_groups', [])
300300

301301

302+
def is_lsp_enabled(lsp):
303+
"""Return if a Logical Switch Port is enabled
304+
305+
This method mimics the OVN northd method ``lsp_is_enabled``
306+
https://shorturl.at/rBSZ7. The "enabled" field can have three values:
307+
* []: from older OVN versions, in this case the LSP is enabled by default.
308+
* [True]: the LSP is enabled.
309+
* [False]: the LSP is disabled.
310+
311+
:param lsp: ``ovs.db.Row`` with a Logical Switch Port register.
312+
:return: True if the port is enabled, False if not.
313+
"""
314+
return not lsp.enabled or lsp.enabled == [True]
315+
316+
317+
def is_lsp_up(lsp):
318+
"""Return if a Logical Switch Port is UP
319+
320+
This method mimics the OVN northd method ``lsp_is_up``
321+
https://shorturl.at/aoKR6
322+
323+
:param lsp: ``ovs.db.Row`` with a Logical Switch Port register.
324+
:return: True if the port is UP, False if not.
325+
"""
326+
return lsp.up == [True]
327+
328+
302329
def is_snat_enabled(router):
303330
return router.get(l3.EXTERNAL_GW_INFO, {}).get('enable_snat', True)
304331

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ def match_fn(self, event, row, old=None):
286286
# ``LogicalSwitchPortUpdateUpEvent`` events.
287287
return False
288288

289-
return bool(lsp.up)
289+
return utils.is_lsp_enabled(lsp) and utils.is_lsp_up(lsp)
290290

291291
def run(self, event, row, old=None):
292292
self.driver.set_port_status_up(row.logical_port)
@@ -461,81 +461,84 @@ def run(self, event, row, old):
461461
router, host)
462462

463463

464-
class LogicalSwitchPortCreateUpEvent(row_event.RowEvent):
465-
"""Row create event - Logical_Switch_Port 'up' = True.
464+
class LogicalSwitchPortCreateEvent(row_event.RowEvent):
465+
"""Row create event - Checks Logical_Switch_Port is UP and enabled.
466466
467467
On connection, we get a dump of all ports, so if there is a neutron
468-
port that is down that has since been activated, we'll catch it here.
469-
This event will not be generated for new ports getting created.
468+
port that has been activated or deactivated, we'll catch it here.
470469
"""
471470

472471
def __init__(self, driver):
473472
self.driver = driver
474473
table = 'Logical_Switch_Port'
475474
events = (self.ROW_CREATE,)
476-
super(LogicalSwitchPortCreateUpEvent, self).__init__(
477-
events, table, (('up', '=', True),))
478-
self.event_name = 'LogicalSwitchPortCreateUpEvent'
475+
super().__init__(events, table, [])
476+
self.event_name = 'LogicalSwitchPortCreateEvent'
479477

480478
def run(self, event, row, old):
481-
self.driver.set_port_status_up(row.name)
482-
483-
484-
class LogicalSwitchPortCreateDownEvent(row_event.RowEvent):
485-
"""Row create event - Logical_Switch_Port 'up' = False
486-
487-
On connection, we get a dump of all ports, so if there is a neutron
488-
port that is up that has since been deactivated, we'll catch it here.
489-
This event will not be generated for new ports getting created.
490-
"""
491-
def __init__(self, driver):
492-
self.driver = driver
493-
table = 'Logical_Switch_Port'
494-
events = (self.ROW_CREATE,)
495-
super(LogicalSwitchPortCreateDownEvent, self).__init__(
496-
events, table, (('up', '=', False),))
497-
self.event_name = 'LogicalSwitchPortCreateDownEvent'
498-
499-
def run(self, event, row, old):
500-
self.driver.set_port_status_down(row.name)
479+
if utils.is_lsp_up(row) and utils.is_lsp_enabled(row):
480+
self.driver.set_port_status_up(row.name)
481+
else:
482+
self.driver.set_port_status_down(row.name)
501483

502484

503485
class LogicalSwitchPortUpdateUpEvent(row_event.RowEvent):
504-
"""Row update event - Logical_Switch_Port 'up' going from False to True
486+
"""Row update event - Logical_Switch_Port UP or enabled going True
505487
506488
This happens when the VM goes up.
507-
New value of Logical_Switch_Port 'up' will be True and the old value will
508-
be False.
509489
"""
510490
def __init__(self, driver):
511491
self.driver = driver
512492
table = 'Logical_Switch_Port'
513493
events = (self.ROW_UPDATE,)
514494
super(LogicalSwitchPortUpdateUpEvent, self).__init__(
515-
events, table, (('up', '=', True),),
516-
old_conditions=(('up', '!=', True),))
495+
events, table, None)
517496
self.event_name = 'LogicalSwitchPortUpdateUpEvent'
518497

498+
def match_fn(self, event, row, old):
499+
if not (utils.is_lsp_up(row) and utils.is_lsp_enabled(row)):
500+
return False
501+
502+
if hasattr(old, 'up') and not utils.is_lsp_up(old):
503+
# The port has transitioned from DOWN to UP, and the admin state
504+
# is UP (lsp.enabled=True)
505+
return True
506+
if hasattr(old, 'enabled') and not utils.is_lsp_enabled(old):
507+
# The user has set the admin state to UP and the port is UP too.
508+
return True
509+
return False
510+
519511
def run(self, event, row, old):
520512
self.driver.set_port_status_up(row.name)
521513

522514

523515
class LogicalSwitchPortUpdateDownEvent(row_event.RowEvent):
524-
"""Row update event - Logical_Switch_Port 'up' going from True to False
516+
"""Row update event - Logical_Switch_Port UP or enabled going to False
525517
526-
This happens when the VM goes down.
527-
New value of Logical_Switch_Port 'up' will be False and the old value will
528-
be True.
518+
This happens when the VM goes down or the port is disabled.
529519
"""
530520
def __init__(self, driver):
531521
self.driver = driver
532522
table = 'Logical_Switch_Port'
533523
events = (self.ROW_UPDATE,)
534524
super(LogicalSwitchPortUpdateDownEvent, self).__init__(
535-
events, table, (('up', '=', False),),
536-
old_conditions=(('up', '=', True),))
525+
events, table, None)
537526
self.event_name = 'LogicalSwitchPortUpdateDownEvent'
538527

528+
def match_fn(self, event, row, old):
529+
if (hasattr(old, 'up') and
530+
utils.is_lsp_up(old) and
531+
not utils.is_lsp_up(row)):
532+
# If the port goes DOWN, update the port status to DOWN.
533+
return True
534+
if (hasattr(old, 'enabled') and
535+
utils.is_lsp_enabled(old) and
536+
not utils.is_lsp_enabled(row)):
537+
# If the port is disabled by the user, update the port status to
538+
# DOWN.
539+
return True
540+
return False
541+
539542
def run(self, event, row, old):
540543
self.driver.set_port_status_down(row.name)
541544

@@ -779,12 +782,10 @@ def __init__(self, driver, remote, schema):
779782
super(OvnNbIdl, self).__init__(driver, remote, schema)
780783
self._lsp_update_up_event = LogicalSwitchPortUpdateUpEvent(driver)
781784
self._lsp_update_down_event = LogicalSwitchPortUpdateDownEvent(driver)
782-
self._lsp_create_up_event = LogicalSwitchPortCreateUpEvent(driver)
783-
self._lsp_create_down_event = LogicalSwitchPortCreateDownEvent(driver)
785+
self._lsp_create_event = LogicalSwitchPortCreateEvent(driver)
784786
self._fip_create_delete_event = FIPAddDeleteEvent(driver)
785787

786-
self.notify_handler.watch_events([self._lsp_create_up_event,
787-
self._lsp_create_down_event,
788+
self.notify_handler.watch_events([self._lsp_create_event,
788789
self._lsp_update_up_event,
789790
self._lsp_update_down_event,
790791
self._fip_create_delete_event])
@@ -804,10 +805,8 @@ def unwatch_logical_switch_port_create_events(self):
804805
After the startup, there is no need to watch these events.
805806
So unwatch these events.
806807
"""
807-
self.notify_handler.unwatch_events([self._lsp_create_up_event,
808-
self._lsp_create_down_event])
809-
self._lsp_create_up_event = None
810-
self._lsp_create_down_event = None
808+
self.notify_handler.unwatch_events([self._lsp_create_event])
809+
del self._lsp_create_event
811810

812811
def post_connect(self):
813812
self.unwatch_logical_switch_port_create_events()

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@
6060
"port_security": {"type": {"key": "string",
6161
"min": 0,
6262
"max": "unlimited"}},
63-
"up": {"type": {"key": "boolean", "min": 0, "max": 1}}},
63+
"up": {"type": {"key": "boolean", "min": 0, "max": 1}},
64+
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
65+
},
6466
"indexes": [["name"]],
6567
"isRoot": False,
6668
},
@@ -366,7 +368,8 @@ def test_event_matches(self):
366368
pbtable = fakes.FakeOvsdbTable.create_one_ovsdb_table(
367369
attrs={'name': 'Port_Binding'})
368370
ovsdb_row = fakes.FakeOvsdbRow.create_one_ovsdb_row
369-
self.driver.nb_ovn.lookup.return_value = ovsdb_row(attrs={'up': True})
371+
self.driver.nb_ovn.lookup.return_value = ovsdb_row(
372+
attrs={'up': True, 'enabled': True})
370373

371374
# Port binding change.
372375
self._test_event(
@@ -422,28 +425,46 @@ def _test_lsp_helper(self, event, new_row_json, old_row_json=None,
422425
# Execute the notifications queued
423426
self.idl.notify_handler.notify_loop()
424427

425-
def test_lsp_up_create_event(self):
426-
row_data = {"up": True, "name": "foo-name"}
428+
def test_lsp_create_event(self):
429+
row_data = {'name': 'foo'}
430+
431+
# up and enabled
432+
row_data.update({'up': True, 'enabled': True})
427433
self._test_lsp_helper('create', row_data)
428-
self.mech_driver.set_port_status_up.assert_called_once_with("foo-name")
434+
self.mech_driver.set_port_status_up.assert_called_once_with('foo')
429435
self.assertFalse(self.mech_driver.set_port_status_down.called)
436+
self.mech_driver.set_port_status_up.reset_mock()
430437

431-
def test_lsp_down_create_event(self):
432-
row_data = {"up": False, "name": "foo-name"}
438+
# up and disabled
439+
row_data.update({'up': True, 'enabled': False})
433440
self._test_lsp_helper('create', row_data)
434-
self.mech_driver.set_port_status_down.assert_called_once_with(
435-
"foo-name")
436441
self.assertFalse(self.mech_driver.set_port_status_up.called)
442+
self.mech_driver.set_port_status_down.assert_called_once_with('foo')
443+
self.mech_driver.set_port_status_down.reset_mock()
437444

438-
def test_lsp_up_not_set_event(self):
439-
row_data = {"up": ['set', []], "name": "foo-name"}
445+
# down and enabled
446+
row_data.update({'up': False, 'enabled': True})
440447
self._test_lsp_helper('create', row_data)
441448
self.assertFalse(self.mech_driver.set_port_status_up.called)
442-
self.assertFalse(self.mech_driver.set_port_status_down.called)
449+
self.mech_driver.set_port_status_down.assert_called_once_with('foo')
450+
self.mech_driver.set_port_status_down.reset_mock()
451+
452+
# down and disabled
453+
row_data.update({'up': False, 'enabled': False})
454+
self._test_lsp_helper('create', row_data)
455+
self.assertFalse(self.mech_driver.set_port_status_up.called)
456+
self.mech_driver.set_port_status_down.assert_called_once_with('foo')
457+
self.mech_driver.set_port_status_down.reset_mock()
458+
459+
# Not set to up
460+
row_data.update({'up': ['set', []], 'enabled': True})
461+
self._test_lsp_helper('create', row_data)
462+
self.assertFalse(self.mech_driver.set_port_status_up.called)
463+
self.mech_driver.set_port_status_down.assert_called_once_with('foo')
443464

444465
def test_unwatch_logical_switch_port_create_events(self):
445466
self.idl.unwatch_logical_switch_port_create_events()
446-
row_data = {"up": True, "name": "foo-name"}
467+
row_data = {'up': False, 'name': 'foo-name'}
447468
self._test_lsp_helper('create', row_data)
448469
self.assertFalse(self.mech_driver.set_port_status_up.called)
449470
self.assertFalse(self.mech_driver.set_port_status_down.called)
@@ -455,19 +476,18 @@ def test_unwatch_logical_switch_port_create_events(self):
455476

456477
def test_post_connect(self):
457478
self.idl.post_connect()
458-
self.assertIsNone(self.idl._lsp_create_up_event)
459-
self.assertIsNone(self.idl._lsp_create_down_event)
479+
self.assertIsNone(getattr(self.idl, '_lsp_create_event', None))
460480

461481
def test_lsp_up_update_event(self):
462-
new_row_json = {"up": True, "name": "foo-name"}
482+
new_row_json = {'up': True, 'enabled': True, 'name': 'foo-name'}
463483
old_row_json = {"up": False}
464484
self._test_lsp_helper('update', new_row_json,
465485
old_row_json=old_row_json)
466486
self.mech_driver.set_port_status_up.assert_called_once_with("foo-name")
467487
self.assertFalse(self.mech_driver.set_port_status_down.called)
468488

469489
def test_lsp_down_update_event(self):
470-
new_row_json = {"up": False, "name": "foo-name"}
490+
new_row_json = {'up': False, 'enabled': False, 'name': 'foo-name'}
471491
old_row_json = {"up": True}
472492
self._test_lsp_helper('update', new_row_json,
473493
old_row_json=old_row_json)
@@ -476,7 +496,7 @@ def test_lsp_down_update_event(self):
476496
self.assertFalse(self.mech_driver.set_port_status_up.called)
477497

478498
def test_lsp_up_update_event_no_old_data(self):
479-
new_row_json = {"up": True, "name": "foo-name"}
499+
new_row_json = {'up': True, 'enabled': True, 'name': 'foo-name'}
480500
self._test_lsp_helper('update', new_row_json,
481501
old_row_json=None)
482502
self.assertFalse(self.mech_driver.set_port_status_up.called)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
fixes:
3+
- |
4+
[`bug 2036705 <https://bugs.launchpad.net/neutron/+bug/2036705>`_]
5+
The Neutron ``port.status`` field ("ACTIVE", "DOWN") is now set based on
6+
the ML2/OVN Logical Switch Port ``up`` and ``enabled`` flags. The user can
7+
now set the ``port.admin_state_up``, that is replicated in the
8+
``lsp.enabled`` flag, to enable or disable the port. If the port is
9+
disabled, the traffic is stopped and the ``port.status`` is set to "DOWN".

0 commit comments

Comments
 (0)