Skip to content

Commit 805ff5b

Browse files
authored
[reboot-cause] Use UTC to ensure consistent sorting (#293)
* Use UTC to ensure consistent sorting Previously reboot cause uses local timezone. This could result in the timestamp being in UTC, IDT, or any other timezone depending on the system configuration. When reboot cause history files are sorted, mixing different timezones can lead to incorrect chronological order and cause reboot cause test failure. Now change the code to use datetime.datetime.utcnow() for reboot_cause_gen_time, ensuring that all timestamps are consistently in UTC. Signed-off-by: Jianyue Wu <[email protected]>
1 parent 286827f commit 805ff5b

File tree

4 files changed

+87
-2
lines changed

4 files changed

+87
-2
lines changed

scripts/determine-reboot-cause

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ def main():
257257
previous_reboot_cause, additional_reboot_info = determine_reboot_cause()
258258

259259
# Current time
260-
reboot_cause_gen_time = str(datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S'))
260+
reboot_cause_gen_time = str(datetime.datetime.utcnow().strftime('%Y_%m_%d_%H_%M_%S'))
261261

262262
# Save the previous cause info into its history file as json format
263263
reboot_cause_dict = get_reboot_cause_dict(previous_reboot_cause, additional_reboot_info, reboot_cause_gen_time)

scripts/procdockerstatsd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class ProcDockerStats(daemon_base.DaemonBase):
245245

246246
while True:
247247
self.update_dockerstats_command()
248-
datetimeobj = datetime.now()
248+
datetimeobj = datetime.utcnow()
249249
# Adding key to store latest update time.
250250
self.update_state_db('DOCKER_STATS|LastUpdateTime', 'lastupdate', str(datetimeobj))
251251
self.update_processstats_command()

tests/determine-reboot-cause_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import shutil
44
import pytest
55
import json
6+
import datetime
7+
import re
68

79
from swsscommon import swsscommon
810
from sonic_py_common.general import load_module_from_source
@@ -271,3 +273,21 @@ def test_check_and_create_dpu_dirs(
271273
# Assert that reboot-cause.txt was created for each DPU
272274
mock_open.assert_any_call(os.path.join(REBOOT_CAUSE_MODULE_DIR, "dpu0", "reboot-cause.txt"), 'w')
273275
mock_open.assert_any_call(os.path.join(REBOOT_CAUSE_MODULE_DIR, "dpu1", "reboot-cause.txt"), 'w')
276+
277+
def test_reboot_cause_gen_time_is_utc(self):
278+
# Patch datetime.datetime.utcnow to return a known value
279+
class FixedDatetime(datetime.datetime):
280+
@classmethod
281+
def utcnow(cls):
282+
return cls(2024, 7, 1, 12, 34, 56)
283+
orig_datetime = determine_reboot_cause.datetime
284+
determine_reboot_cause.datetime = FixedDatetime
285+
286+
try:
287+
gen_time = determine_reboot_cause.datetime.utcnow().strftime('%Y_%m_%d_%H_%M_%S')
288+
# Should match the fixed UTC time
289+
assert gen_time == "2024_07_01_12_34_56"
290+
# Optionally, check the format
291+
assert re.match(r"\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}", gen_time)
292+
finally:
293+
determine_reboot_cause.datetime = orig_datetime

tests/procdockerstatsd_test.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,68 @@ def cpu_percent(self):
163163

164164
assert 1234 in pdstatsd.all_process_obj
165165
assert 9999 not in pdstatsd.all_process_obj
166+
167+
def test_datetime_utcnow_usage(self):
168+
"""Test that datetime.utcnow() is used instead of datetime.now() for consistent UTC timestamps"""
169+
pdstatsd = procdockerstatsd.ProcDockerStats(procdockerstatsd.SYSLOG_IDENTIFIER)
170+
171+
# Mock datetime.utcnow to return a fixed time for testing
172+
fixed_time = datetime(2025, 7, 1, 12, 34, 56)
173+
174+
with patch('procdockerstatsd.datetime') as mock_datetime:
175+
mock_datetime.utcnow.return_value = fixed_time
176+
177+
# Test the update_fipsstats_command method which uses datetime.utcnow()
178+
pdstatsd.update_fipsstats_command()
179+
180+
# Verify that utcnow() was called
181+
mock_datetime.utcnow.assert_called_once()
182+
183+
# Verify that now() was NOT called (ensuring we're using UTC)
184+
mock_datetime.now.assert_not_called()
185+
186+
# Test the main run loop datetime usage
187+
with patch.object(pdstatsd, 'update_dockerstats_command'):
188+
with patch.object(pdstatsd, 'update_processstats_command'):
189+
with patch.object(pdstatsd, 'update_fipsstats_command'):
190+
with patch('time.sleep'): # Prevent actual sleep
191+
# Mock the first iteration of the run loop
192+
pdstatsd.update_dockerstats_command()
193+
datetimeobj = mock_datetime.utcnow()
194+
pdstatsd.update_state_db('DOCKER_STATS|LastUpdateTime', 'lastupdate', str(datetimeobj))
195+
pdstatsd.update_processstats_command()
196+
pdstatsd.update_state_db('PROCESS_STATS|LastUpdateTime', 'lastupdate', str(datetimeobj))
197+
pdstatsd.update_fipsstats_command()
198+
pdstatsd.update_state_db('FIPS_STATS|LastUpdateTime', 'lastupdate', str(datetimeobj))
199+
200+
# Verify utcnow() was called multiple times as expected
201+
assert mock_datetime.utcnow.call_count >= 2
202+
203+
def test_run_method_executes_with_utcnow(self):
204+
"""Test that run method executes and uses datetime.utcnow()"""
205+
pdstatsd = procdockerstatsd.ProcDockerStats(procdockerstatsd.SYSLOG_IDENTIFIER)
206+
207+
# Mock all dependencies but allow datetime.utcnow() to run normally
208+
with patch.object(pdstatsd, 'update_dockerstats_command'):
209+
with patch.object(pdstatsd, 'update_processstats_command'):
210+
with patch.object(pdstatsd, 'update_fipsstats_command'):
211+
with patch('time.sleep', side_effect=Exception("Stop after first iteration")):
212+
with patch('os.getuid', return_value=0): # Mock as root
213+
with patch.object(pdstatsd, 'log_info'):
214+
with patch.object(pdstatsd, 'update_state_db') as mock_update_db:
215+
# This will actually call run() method
216+
try:
217+
pdstatsd.run()
218+
except Exception as e:
219+
if "Stop after first iteration" in str(e):
220+
# Verify that update_state_db was called
221+
assert mock_update_db.call_count >= 3
222+
# Verify that timestamps were passed
223+
for call in mock_update_db.call_args_list:
224+
args = call[0]
225+
if len(args) >= 3 and 'lastupdate' in args[1]:
226+
timestamp_str = args[2]
227+
assert isinstance(timestamp_str, str)
228+
assert len(timestamp_str) > 0
229+
else:
230+
raise

0 commit comments

Comments
 (0)