diff --git a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py index 0e5d57d9eb0..6f0d69eec76 100644 --- a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py +++ b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py @@ -14,6 +14,7 @@ import abc import copy +import datetime from oslo_config import cfg from oslo_utils import timeutils @@ -44,7 +45,21 @@ class NeutronAgent(abc.ABC): def update(self, chassis_private, updated_at=None, clear_down=False): self.chassis_private = chassis_private - self.updated_at = updated_at or timeutils.utcnow(with_timezone=True) + if not updated_at: + # When use the Chassis_Private table for agents health check, + # chassis_private has attribute nb_cfg_timestamp. + # nb_cfg_timestamp: the timestamp when ovn-controller finishes + # processing the change corresponding to nb_cfg( + # https://www.ovn.org/support/dist-docs/ovn-sb.5.html). + # it can better reflect the status of chassis. + # nb_cfg_timestamp is milliseconds, need to convert to datetime. + if hasattr(chassis_private, 'nb_cfg_timestamp'): + updated_at = datetime.datetime.fromtimestamp( + chassis_private.nb_cfg_timestamp / 1000, + datetime.timezone.utc) + else: + updated_at = timeutils.utcnow(with_timezone=True) + self.updated_at = updated_at if clear_down: self.set_down = False diff --git a/neutron/tests/functional/base.py b/neutron/tests/functional/base.py index f50ef4cfe70..dc1c70af7b8 100644 --- a/neutron/tests/functional/base.py +++ b/neutron/tests/functional/base.py @@ -29,6 +29,7 @@ from oslo_config import cfg from oslo_db import exception as os_db_exc from oslo_db.sqlalchemy import provision from oslo_log import log +from oslo_utils import timeutils from oslo_utils import uuidutils from neutron.agent.linux import utils @@ -450,9 +451,11 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase, name, ['geneve'], '172.24.4.%d' % self._counter, external_ids=external_ids, hostname=host).execute(check_error=True) if self.sb_api.is_table_present('Chassis_Private'): + nb_cfg_timestamp = timeutils.utcnow_ts() * 1000 self.sb_api.db_create( 'Chassis_Private', name=name, external_ids=external_ids, - chassis=chassis.uuid).execute(check_error=True) + chassis=chassis.uuid, nb_cfg_timestamp=nb_cfg_timestamp + ).execute(check_error=True) return name def del_fake_chassis(self, chassis, if_exists=True): diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py index 08d955c8c6b..3e184fdf898 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py @@ -11,7 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +import datetime import functools from unittest import mock @@ -22,6 +22,7 @@ from neutron_lib.api.definitions import portbindings from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from oslo_concurrency import processutils +from oslo_utils import timeutils from oslo_utils import uuidutils from ovsdbapp.backend.ovs_idl import event from ovsdbapp.backend.ovs_idl import idlutils @@ -454,6 +455,21 @@ class TestAgentMonitor(base.TestOVNFunctionalBase): self.assertEqual(neutron_agent.ControllerAgent, type(neutron_agent.AgentCache()[self.chassis_name])) + def test_agent_updated_at_use_nb_cfg_timestamp(self): + if not self.sb_api.is_table_present('Chassis_Private'): + self.skipTest('Ovn sb not support Chassis_Private') + timestamp = timeutils.utcnow_ts() + nb_cfg_timestamp = timestamp * 1000 + updated_at = datetime.datetime.fromtimestamp( + timestamp, datetime.timezone.utc) + self.sb_api.db_set('Chassis_Private', self.chassis_name, ( + 'nb_cfg_timestamp', nb_cfg_timestamp)).execute(check_error=True) + n_utils.wait_until_true(lambda: + neutron_agent.AgentCache()[self.chassis_name]. + chassis_private.nb_cfg_timestamp == nb_cfg_timestamp) + agent = neutron_agent.AgentCache()[self.chassis_name] + self.assertEqual(updated_at, agent.updated_at) + class TestOvnIdlProbeInterval(base.TestOVNFunctionalBase): def setUp(self): diff --git a/neutron/tests/unit/agent/l2/extensions/dhcp/test_ipv6.py b/neutron/tests/unit/agent/l2/extensions/dhcp/test_ipv6.py index 5ec0ca83439..6181d053ede 100644 --- a/neutron/tests/unit/agent/l2/extensions/dhcp/test_ipv6.py +++ b/neutron/tests/unit/agent/l2/extensions/dhcp/test_ipv6.py @@ -95,7 +95,10 @@ class DHCPIPv6ResponderTestCase(dhcp_test_base.DHCPResponderBaseTestCase): expect_ident = ( b'\x00\x01\x00\x01\x00\x00\x00\x01\x00\x01\x02\x03\x04\x05') - time.time = mock.Mock(return_value=ONE_SEC_AFTER_2000) + time_p = mock.patch.object(time, 'time', + return_value=ONE_SEC_AFTER_2000) + time_p.start() + self.addCleanup(time_p.stop) client_ident = client_ident = ( self.dhcp6_responer.get_dhcpv6_client_ident( self.port_info['mac_address'], [])) @@ -164,7 +167,10 @@ class DHCPIPv6ResponderTestCase(dhcp_test_base.DHCPResponderBaseTestCase): option_list=option_list, options_len=0) - time.time = mock.Mock(return_value=ONE_SEC_AFTER_2000) + time_p = mock.patch.object(time, 'time', + return_value=ONE_SEC_AFTER_2000) + time_p.start() + self.addCleanup(time_p.stop) packet_in = self._create_test_dhcp6_packet(zero_time=zero_time) pkt_dhcp = packet_in.get_protocol(dhcp6.dhcp6) dhcp_req_state = dhcp_ipv6.DHCPV6_TYPE_MAP.get(pkt_dhcp.msg_type) @@ -192,7 +198,10 @@ class DHCPIPv6ResponderTestCase(dhcp_test_base.DHCPResponderBaseTestCase): mac = '00:01:02:03:04:05' packet_in = self._create_test_dhcp6_packet() header_dhcp = packet_in.get_protocol(dhcp6.dhcp6) - time.time = mock.Mock(return_value=ONE_SEC_AFTER_2000) + time_p = mock.patch.object(time, 'time', + return_value=ONE_SEC_AFTER_2000) + time_p.start() + self.addCleanup(time_p.stop) dhcp_options = self.dhcp6_responer.get_reply_dhcp_options( mac, message="all addresses still on link", req_options=header_dhcp.options.option_list) diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index 04808f8d53b..9b3fcac8f0d 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -2217,6 +2217,33 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): self.assertFalse(agent.alive, "Agent of type %s alive=%s" % (agent.agent_type, agent.alive)) + def test_agent_with_nb_cfg_timestamp_timeout(self): + nb_cfg = 3 + chassis_private = self._add_chassis(nb_cfg) + + self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2 + updated_at = (timeutils.utcnow_ts() - cfg.CONF.agent_down_time - 1 + ) * 1000 + chassis_private.nb_cfg_timestamp = updated_at + agent_type = ovn_const.OVN_CONTROLLER_AGENT + agent = self._add_chassis_agent(nb_cfg, agent_type, + chassis_private) + self.assertFalse(agent.alive, "Agent of type %s alive=%s" % + (agent.agent_type, agent.alive)) + + def test_agent_with_nb_cfg_timestamp_not_timeout(self): + nb_cfg = 3 + chassis_private = self._add_chassis(nb_cfg) + + self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2 + updated_at = timeutils.utcnow_ts() * 1000 + chassis_private.nb_cfg_timestamp = updated_at + agent_type = ovn_const.OVN_CONTROLLER_AGENT + agent = self._add_chassis_agent(nb_cfg, agent_type, + chassis_private) + self.assertTrue(agent.alive, "Agent of type %s alive=%s" % ( + agent.agent_type, agent.alive)) + def _test__update_dnat_entry_if_needed(self, up=True): ovn_conf.cfg.CONF.set_override( 'enable_distributed_floating_ip', True, group='ovn')