diff --git a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py index f0939dd60a9..432e00420c5 100644 --- a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py +++ b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py @@ -13,6 +13,7 @@ # import abc +import datetime from oslo_config import cfg from oslo_utils import timeutils @@ -43,7 +44,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 dfac36d8571..9a40b166d3f 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 4033e0a4415..bd469789d80 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 @@ -21,6 +21,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 @@ -403,6 +404,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 536898286b3..299fa5125d0 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 @@ -2080,11 +2080,15 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): def _add_chassis_agent(self, nb_cfg, agent_type, chassis_private=None, updated_at=None): - updated_at = updated_at or timeutils.utcnow(with_timezone=True) chassis_private = chassis_private or self._add_chassis(nb_cfg) - chassis_private.external_ids = { - ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY: - datetime.datetime.isoformat(updated_at)} + if hasattr(chassis_private, 'nb_cfg_timestamp') and isinstance( + chassis_private.nb_cfg_timestamp, mock.Mock): + del chassis_private.nb_cfg_timestamp + chassis_private.external_ids = {} + if updated_at: + chassis_private.external_ids[ + ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY] = \ + datetime.datetime.isoformat(updated_at) if agent_type == ovn_const.OVN_METADATA_AGENT: chassis_private.external_ids.update({ ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg, @@ -2141,6 +2145,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')