diff --git a/lower-constraints.txt b/lower-constraints.txt index 42ca837b1e1..63aa52b1689 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -79,7 +79,7 @@ oslo.versionedobjects==1.35.1 oslotest==3.2.0 osprofiler==2.3.0 ovs==2.10.0 -ovsdbapp==1.6.0 +ovsdbapp==1.7.0 packaging==20.4 Paste==2.0.2 PasteDeploy==1.5.0 diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py index 9ecd00abb1b..7f101216d38 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py @@ -546,8 +546,30 @@ class OvnIdlDistributedLock(BaseOvnIdl): self._hash_ring = hash_ring_manager.HashRingManager( self.driver.hash_ring_group) self._last_touch = None + # This is a map of tables that may be new after OVN database is updated + self._tables_to_register = { + 'OVN_Southbound': ['Chassis_Private'], + } + + def handle_db_schema_changes(self, event, row): + if (event == row_event.RowEvent.ROW_CREATE and + row._table.name == 'Database'): + try: + tables = self._tables_to_register[row.name] + except KeyError: + return + + self.update_tables(tables, row.schema[0]) + + if 'Chassis_Private' == self.driver.agent_chassis_table: + if 'Chassis_Private' not in self.tables: + self.driver.agent_chassis_table = 'Chassis' + else: + if 'Chassis_Private' in self.tables: + self.driver.agent_chassis_table = 'Chassis_Private' def notify(self, event, row, updates=None): + self.handle_db_schema_changes(event, row) self.notify_handler.notify(event, row, updates, global_=True) try: target_node = self._hash_ring.get_node(str(row.uuid)) diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py index ddbc7d9e600..b9609ec4d92 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py @@ -219,6 +219,8 @@ class TestOvnIdlDistributedLock(base.BaseTestCase): self.mock_get_node = mock.patch.object( hash_ring_manager.HashRingManager, 'get_node', return_value=self.node_uuid).start() + self.mock_update_tables = mock.patch.object( + self.idl, 'update_tables').start() def _assert_has_notify_calls(self): self.idl.notify_handler.notify.assert_has_calls([ @@ -279,6 +281,69 @@ class TestOvnIdlDistributedLock(base.BaseTestCase): self.idl.notify_handler.notify.assert_called_once_with( self.fake_event, self.fake_row, None, global_=True) + @staticmethod + def _create_fake_row(table_name): + # name is a parameter in Mock() so it can't be passed to constructor + table = mock.Mock() + table.name = table_name + return fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'_table': table, 'schema': ['foo']}) + + def test_handle_db_schema_changes_no_match_events(self): + other_table_row = self._create_fake_row('other') + database_table_row = self._create_fake_row('Database') + + self.idl.handle_db_schema_changes( + ovsdb_monitor.BaseEvent.ROW_UPDATE, other_table_row) + self.idl.handle_db_schema_changes( + ovsdb_monitor.BaseEvent.ROW_CREATE, other_table_row) + self.idl.handle_db_schema_changes( + ovsdb_monitor.BaseEvent.ROW_UPDATE, database_table_row) + + self.assertFalse(self.mock_update_tables.called) + + def _test_handle_db_schema(self, agent_table, chassis_private_present): + database_table_row = self._create_fake_row('Database') + self.idl._tables_to_register[database_table_row.name] = 'foo' + + self.fake_driver.agent_chassis_table = agent_table + if chassis_private_present: + self.idl.tables['Chassis_Private'] = 'foo' + else: + try: + del self.idl.tables['Chassis_Private'] + except KeyError: + pass + + self.idl.handle_db_schema_changes( + ovsdb_monitor.BaseEvent.ROW_CREATE, database_table_row) + + def test_handle_db_schema_changes_old_schema_to_old_schema(self): + """Agents use Chassis and should keep using Chassis table""" + self._test_handle_db_schema('Chassis', chassis_private_present=False) + self.assertEqual('Chassis', self.fake_driver.agent_chassis_table) + + def test_handle_db_schema_changes_old_schema_to_new_schema(self): + """Agents use Chassis and should start using Chassis_Private table""" + self._test_handle_db_schema('Chassis', chassis_private_present=True) + self.assertEqual('Chassis_Private', + self.fake_driver.agent_chassis_table) + + def test_handle_db_schema_changes_new_schema_to_old_schema(self): + """Agents use Chassis_Private and should start using Chassis table""" + self._test_handle_db_schema('Chassis_Private', + chassis_private_present=False) + self.assertEqual('Chassis', self.fake_driver.agent_chassis_table) + + def test_handle_db_schema_changes_new_schema_to_new_schema(self): + """Agents use Chassis_Private and should keep using Chassis_Private + table. + """ + self._test_handle_db_schema('Chassis_Private', + chassis_private_present=True) + self.assertEqual('Chassis_Private', + self.fake_driver.agent_chassis_table) + class TestPortBindingChassisUpdateEvent(base.BaseTestCase): def setUp(self): @@ -319,6 +384,7 @@ class TestOvnNbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase): self.lp_table = self.idl.tables.get('Logical_Switch_Port') self.driver.set_port_status_up = mock.Mock() self.driver.set_port_status_down = mock.Mock() + mock.patch.object(self.idl, 'handle_db_schema_changes').start() def _test_lsp_helper(self, event, new_row_json, old_row_json=None, table=None): diff --git a/requirements.txt b/requirements.txt index a4cd1fba946..e7554eb2c74 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,7 +45,7 @@ oslo.versionedobjects>=1.35.1 # Apache-2.0 osprofiler>=2.3.0 # Apache-2.0 os-ken >= 0.3.0 # Apache-2.0 ovs>=2.10.0 # Apache-2.0 -ovsdbapp>=1.6.0 # Apache-2.0 +ovsdbapp>=1.7.0 # Apache-2.0 packaging>=20.4 # Apache-2.0 psutil>=5.3.0 # BSD pyroute2>=0.5.13;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)