From 47cf965c7cd66f0444737e876d7484b506ae7c35 Mon Sep 17 00:00:00 2001 From: Martin Kolman Date: Mon, 13 Mar 2023 15:06:24 +0100 Subject: [PATCH] Extend the Timezone DBus module Extend the Timezone DBus module API to provide new APIs needed by the Date and time Screen in the Web UI. The new API will make it possible to monitor NTP service status, query and set local system time and get a listing of valid timezones. --- pyanaconda/modules/timezone/timezone.py | 45 +++++++++++++ .../modules/timezone/timezone_interface.py | 28 +++++++++ .../modules/timezone/test_module_timezone.py | 63 +++++++++++++++++++ ui/webui/test/reference | 2 +- 4 files changed, 137 insertions(+), 1 deletion(-) diff --git a/pyanaconda/modules/timezone/timezone.py b/pyanaconda/modules/timezone/timezone.py index b514455e6cee..4c7ec64cd572 100644 --- a/pyanaconda/modules/timezone/timezone.py +++ b/pyanaconda/modules/timezone/timezone.py @@ -17,6 +17,9 @@ # License and may only be used or replicated with the express permission of # Red Hat, Inc. # + +import datetime + from pykickstart.errors import KickstartParseError from pyanaconda.core.i18n import _ @@ -36,6 +39,7 @@ ConfigureNTPTask, ConfigureTimezoneTask from pyanaconda.modules.timezone.kickstart import TimezoneKickstartSpecification from pyanaconda.modules.timezone.timezone_interface import TimezoneInterface +from pyanaconda.timezone import get_timezone, set_system_date_time, get_all_regions_and_timezones from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) @@ -167,6 +171,20 @@ def set_timezone_with_priority(self, timezone, priority): self.timezone_changed.emit() log.debug("Timezone is set to %s.", timezone) + def get_all_valid_timezones(self): + """Get all valid timezones. + + :return: list of valid timezones + :rtype: list of str + """ + timezone_dict = get_all_regions_and_timezones() + # convert to a dict of lists for easier transfer over DBus + # - change the nested sets to lists + new_timezone_dict = {} + for region in timezone_dict: + new_timezone_dict[region] = list(timezone_dict[region]) + return new_timezone_dict + @property def is_utc(self): """Is the hardware clock set to UTC?""" @@ -259,3 +277,30 @@ def geolocation_result(self): :return GeolocationData: result of the lookup, empty if not ready yet """ return self._geoloc_result + + def get_system_date_time(self): + """Get system time as a ISO 8601 formatted string. + + :return: system time as ISO 8601 formatted string + :rtype: str + """ + # convert to the expected tzinfo format via get_timezone() + return datetime.datetime.now(get_timezone(self._timezone)).isoformat() + + def set_system_date_time(self, date_time_spec): + """Set system time based on a ISO 8601 formatted string. + + :param str date_time_spec: ISO 8601 time specification to use + """ + log.debug("Setting system time to: %s, with timezone: %s", date_time_spec, self._timezone) + # first convert the ISO 8601 time string to a Python date object + date = datetime.datetime.fromisoformat(date_time_spec) + # set the date to the system + set_system_date_time( + year=date.year, + month=date.month, + day=date.day, + hour=date.hour, + minute=date.minute, + tz=self._timezone + ) diff --git a/pyanaconda/modules/timezone/timezone_interface.py b/pyanaconda/modules/timezone/timezone_interface.py index 25d7b16226eb..506e48b8b1a7 100644 --- a/pyanaconda/modules/timezone/timezone_interface.py +++ b/pyanaconda/modules/timezone/timezone_interface.py @@ -79,6 +79,16 @@ def SetTimezoneWithPriority(self, timezone: Str, priority: UInt16): """ self.implementation.set_timezone_with_priority(timezone, priority) + def GetTimezones(self) -> Dict[Str, List[Str]]: + """Get valid timezones. + + Return a dictionary, where keys are region ids and values are lists + of timezone names in the region. + + :return: a dictionary of timezone lists per region + """ + return self.implementation.get_all_valid_timezones() + @property def IsUTC(self) -> Bool: """Is the hardware clock set to UTC? @@ -157,3 +167,21 @@ def GeolocationResult(self) -> Structure: return GeolocationData.to_structure( self.implementation.geolocation_result ) + + def GetSystemDateTime(self) -> Str: + """Get the current local date and time of the system. + + The timezone set via the Timezone property affects the returned data. + + :return: a string representing the date and time in ISO 8601 format + """ + return self.implementation.get_system_date_time() + + def SetSystemDateTime(self, date_time_spec: Str): + """Set the current local date and time of the system. + + The timezone set via the Timezone property will be applied to the received data. + + :param date_time_spec: a string representing the date and time in ISO 8601 format + """ + self.implementation.set_system_date_time(date_time_spec) diff --git a/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py b/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py index 462e2b2898cf..92b366440ba4 100644 --- a/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py +++ b/tests/unit_tests/pyanaconda_tests/modules/timezone/test_module_timezone.py @@ -18,6 +18,9 @@ # Red Hat Author(s): Vendula Poncova # import unittest +from unittest.mock import patch, MagicMock + +from collections import OrderedDict from dasbus.structure import compare_data from dasbus.typing import * # pylint: disable=wildcard-import @@ -25,6 +28,7 @@ from pyanaconda.core.constants import TIME_SOURCE_SERVER, TIME_SOURCE_POOL, \ TIMEZONE_PRIORITY_DEFAULT, TIMEZONE_PRIORITY_LANGUAGE, TIMEZONE_PRIORITY_GEOLOCATION, \ TIMEZONE_PRIORITY_KICKSTART, TIMEZONE_PRIORITY_USER +from pyanaconda.timezone import get_timezone from pyanaconda.modules.common.constants.services import TIMEZONE from pyanaconda.modules.common.structures.requirement import Requirement from pyanaconda.modules.common.structures.timezone import TimeSourceData @@ -378,3 +382,62 @@ def test_geoloc_result_callback(self): result = GeolocationData.from_values(territory="", timezone="") self.timezone_module._set_geolocation_result(result) assert self.timezone_module.geolocation_result == result + + @patch("pyanaconda.modules.timezone.timezone.get_all_regions_and_timezones") + def test_get_timezones(self, get_all_tz): + """Test getting a listing of all valid timezones.""" + get_all_tz.return_value = OrderedDict([("foo", {"bar", "baz"})]) + # as the timezones for a region are listed as a set, we need to be a bit careful + # when comparing the results + result = self.timezone_interface.GetTimezones() + assert list(result.keys()) == ["foo"] + assert sorted(result["foo"]) == ['bar', 'baz'] + get_all_tz.assert_called_once() + + @patch("pyanaconda.modules.timezone.timezone.datetime") + def test_get_system_date_time(self, fake_datetime): + """Test getting system date and time.""" + # use a non-default timezone + self.timezone_module._timezone = "Antarctica/South_Pole" + # fake date object returned by now() call + fake_date = MagicMock() + fake_date.isoformat.return_value = "2023-06-22T18:49:36.878200" + + def fake_now(value): + assert value == get_timezone("Antarctica/South_Pole") + return fake_date + + fake_datetime.datetime.now.side_effect = fake_now + assert self.timezone_module.get_system_date_time() == "2023-06-22T18:49:36.878200" + fake_date.isoformat.assert_called_once() + + @patch("pyanaconda.modules.timezone.timezone.set_system_date_time") + def test_set_system_date_time(self, fake_set_time): + """Test setting system date and time.""" + self.timezone_module.set_system_date_time("2023-06-22T18:49:36.878200") + fake_set_time.assert_called_once_with(year=2023, + month=6, + day=22, + hour=18, + minute=49, + tz="America/New_York") + + def test_get_timezones_interface(self): + """Test the GetTimezones interface method.""" + self.timezone_module.get_all_valid_timezones = MagicMock() + self.timezone_module.get_all_valid_timezones.return_value = {"foo": ["bar", "baz"]} + assert self.timezone_interface.GetTimezones() == {"foo": ["bar", "baz"]} + + def test_get_system_date_time_interface(self): + """Test the GetSystemDateTime interface method.""" + self.timezone_module.get_system_date_time = MagicMock() + self.timezone_module.get_system_date_time.return_value = "2023-06-22T18:49:36.878200" + assert self.timezone_interface.GetSystemDateTime() == "2023-06-22T18:49:36.878200" + + def test_set_system_date_time_interface(self): + """Test the SetSystemDateTime interface method.""" + self.timezone_module.set_system_date_time = MagicMock() + self.timezone_interface.SetSystemDateTime("2023-06-22T18:49:36.878200") + self.timezone_module.set_system_date_time.assert_called_once_with( + '2023-06-22T18:49:36.878200' + ) diff --git a/ui/webui/test/reference b/ui/webui/test/reference index 094d24845711..2d8a30a4cf42 160000 --- a/ui/webui/test/reference +++ b/ui/webui/test/reference @@ -1 +1 @@ -Subproject commit 094d248457119ead6c5c07f833e7b985789b27df +Subproject commit 2d8a30a4cf4291d6225d9ce80e1260558eb85246