From 8a10cee0ab94b844c65d1493b3d78df5210e4e34 Mon Sep 17 00:00:00 2001 From: Vendula Poncova Date: Fri, 3 Jul 2020 14:41:37 +0200 Subject: [PATCH] Use the structure for time sources in TUI Modify TUI to work with TimeSourceData instead of strings. --- pyanaconda/ui/tui/spokes/time_spoke.py | 286 ++++++++++--------------- 1 file changed, 109 insertions(+), 177 deletions(-) diff --git a/pyanaconda/ui/tui/spokes/time_spoke.py b/pyanaconda/ui/tui/spokes/time_spoke.py index b93ab41eec..b88a17960f 100644 --- a/pyanaconda/ui/tui/spokes/time_spoke.py +++ b/pyanaconda/ui/tui/spokes/time_spoke.py @@ -16,7 +16,10 @@ # License and may only be used or replicated with the express permission of # Red Hat, Inc. # +from pyanaconda.core.constants import TIME_SOURCE_SERVER from pyanaconda.modules.common.constants.services import TIMEZONE +from pyanaconda.modules.common.structures.timezone import TimeSourceData +from pyanaconda.ntp import NTPServerStatusCache from pyanaconda.ui.categories.localization import LocalizationCategory from pyanaconda.ui.tui.spokes import NormalTUISpoke from pyanaconda.ui.common import FirstbootSpokeMixIn @@ -24,11 +27,9 @@ from pyanaconda import ntp from pyanaconda.core import constants from pyanaconda.core.i18n import N_, _, C_ -from pyanaconda.threading import threadMgr, AnacondaThread from pyanaconda.flags import flags -from collections import OrderedDict, namedtuple -from threading import RLock +from collections import namedtuple from simpleline.render.containers import ListColumnContainer from simpleline.render.screen import InputState @@ -39,22 +40,10 @@ from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) -CallbackTimezoneArgs = namedtuple("CallbackTimezoneArgs", ["region", "timezone"]) - - -def format_ntp_status_list(servers): - ntp_server_states = { - constants.NTP_SERVER_OK: _("status: working"), - constants.NTP_SERVER_NOK: _("status: not working"), - constants.NTP_SERVER_QUERY: _("checking status") - } - status_list = [] - for server, server_state in servers.items(): - status_list.append("%s (%s)" % (server, ntp_server_states[server_state])) - return status_list +__all__ = ["TimeSpoke"] -__all__ = ["TimeSpoke"] +CallbackTimezoneArgs = namedtuple("CallbackTimezoneArgs", ["region", "timezone"]) class TimeSpoke(FirstbootSpokeMixIn, NormalTUISpoke): @@ -66,10 +55,8 @@ def __init__(self, data, storage, payload): self.title = N_("Time settings") self._timezone_spoke = None self._container = None - # we use an ordered dict to keep the NTP server insertion order - self._ntp_servers = OrderedDict() - self._ntp_servers_lock = RLock() - + self._ntp_servers = [] + self._ntp_servers_states = NTPServerStatusCache() self._timezone_module = TIMEZONE.get_proxy() @property @@ -83,103 +70,24 @@ def initialize(self): # during the installation # - from config files when running in Initial Setup # after the installation - ntp_servers = [] - if constants.ANACONDA_ENVIRON in flags.environs: - ntp_servers = self._timezone_module.NTPServers + self._ntp_servers = TimeSourceData.from_structure_list( + self._timezone_module.TimeSources + ) elif constants.FIRSTBOOT_ENVIRON in flags.environs: - ntp_servers = ntp.get_servers_from_config()[1] # returns a (NPT pools, NTP servers) tupple + self._ntp_servers = ntp.get_servers_from_config() else: log.error("tui time spoke: unsupported environment configuration %s," "can't decide where to get initial NTP servers", flags.environs) - # check if the NTP servers appear to be working or not - if ntp_servers: - for server in ntp_servers: - self._ntp_servers[server] = constants.NTP_SERVER_QUERY - - # check if the newly added NTP servers work fine - self._check_ntp_servers_async(self._ntp_servers.keys()) + # check if the newly added NTP servers work fine + for server in self._ntp_servers: + self._ntp_servers_states.check_status(server) # we assume that the NTP spoke is initialized enough even if some NTP # server check threads might still be running self.initialize_done() - def _check_ntp_servers_async(self, servers): - """Asynchronously check if given NTP servers appear to be working. - - :param list servers: list of servers to check - """ - for server in servers: - threadMgr.add(AnacondaThread(prefix=constants.THREAD_NTP_SERVER_CHECK, - target=self._check_ntp_server, - args=(server,))) - - def _check_ntp_server(self, server): - """Check if an NTP server appears to be working. - - :param str server: NTP server address - :returns: True if the server appears to be working, False if not - :rtype: bool - """ - log.debug("checking NTP server %s", server) - result = ntp.ntp_server_working(server) - if result: - log.debug("NTP server %s appears to be working", server) - self.set_ntp_server_status(server, constants.NTP_SERVER_OK) - else: - log.debug("NTP server %s appears not to be working", server) - self.set_ntp_server_status(server, constants.NTP_SERVER_NOK) - - @property - def ntp_servers(self): - """Return a list of NTP servers known to the Time spoke. - - :returns: a list of NTP servers - :rtype: list of strings - """ - return self._ntp_servers - - def add_ntp_server(self, server): - """Add NTP server address to our internal NTP server tracking dictionary. - - :param str server: NTP server address to add - """ - # the add & remove operations should (at least at the moment) be never - # called from different threads at the same time, but lets just use - # a lock there when we are at it - with self._ntp_servers_lock: - if server not in self._ntp_servers: - self._ntp_servers[server] = constants.NTP_SERVER_QUERY - self._check_ntp_servers_async([server]) - - def remove_ntp_server(self, server): - """Remove NTP server address from our internal NTP server tracking dictionary. - - :param str server: NTP server address to remove - """ - # the remove-server and set-server-status operations need to be atomic, - # so that we avoid reintroducing removed servers by setting their status - with self._ntp_servers_lock: - if server in self._ntp_servers: - del self._ntp_servers[server] - - def set_ntp_server_status(self, server, status): - """Set status for an NTP server in the NTP server dict. - - The status can be "working", "not working" or "check in progress", - and is defined by three constants defined in constants.py. - - :param str server: an NTP server - :param int status: status of the NTP server - """ - - # the remove-server and set-server-status operations need to be atomic, - # so that we avoid reintroducing removed server by setting their status - with self._ntp_servers_lock: - if server in self._ntp_servers: - self._ntp_servers[server] = status - @property def timezone_spoke(self): if not self._timezone_spoke: @@ -210,6 +118,7 @@ def _summary_text(self): :rtype: str """ msg = "" + # timezone kickstart_timezone = self._timezone_module.Timezone timezone_msg = _("not set") @@ -222,12 +131,10 @@ def _summary_text(self): msg += "\n" # NTP - msg += _("NTP servers:") - if self._ntp_servers: - for status in format_ntp_status_list(self._ntp_servers): - msg += "\n%s" % status - else: - msg += _("not configured") + msg += ntp.get_ntp_servers_summary( + self._ntp_servers, + self._ntp_servers_states + ) return msg @@ -244,8 +151,15 @@ def refresh(self, args=None): self._container = ListColumnContainer(1, columns_width=78, spacing=1) - self._container.add(TextWidget(timezone_option), callback=self._timezone_callback) - self._container.add(TextWidget(_("Configure NTP servers")), callback=self._configure_ntp_server_callback) + self._container.add( + TextWidget(timezone_option), + callback=self._timezone_callback + ) + + self._container.add( + TextWidget(_("Configure NTP servers")), + callback=self._configure_ntp_server_callback + ) self.window.add_with_separator(self._container) @@ -254,7 +168,13 @@ def _timezone_callback(self, data): self.close() def _configure_ntp_server_callback(self, data): - new_spoke = NTPServersSpoke(self.data, self.storage, self.payload, self) + new_spoke = NTPServersSpoke( + self.data, + self.storage, + self.payload, + self._ntp_servers, + self._ntp_servers_states + ) ScreenHandler.push_screen_modal(new_spoke) self.apply() self.close() @@ -268,7 +188,9 @@ def input(self, args, key): def apply(self): # update the NTP server list in kickstart - self._timezone_module.SetNTPServers(list(self.ntp_servers.keys())) + self._timezone_module.SetTimeSources( + TimeSourceData.to_structure_list(self._ntp_servers) + ) class TimeZoneSpoke(NormalTUISpoke): @@ -375,49 +297,55 @@ def apply(self): class NTPServersSpoke(NormalTUISpoke): category = LocalizationCategory - def __init__(self, data, storage, payload, time_spoke): + def __init__(self, data, storage, payload, servers, states): super().__init__(data, storage, payload) self.title = N_("NTP configuration") self._container = None - self._time_spoke = time_spoke + self._servers = servers + self._states = states @property def indirect(self): return True - def _summary_text(self): - """Return summary of NTP configuration.""" - msg = _("NTP servers:") - if self._time_spoke.ntp_servers: - for status in format_ntp_status_list(self._time_spoke.ntp_servers): - msg += "\n%s" % status - else: - msg += _("no NTP servers have been configured") - return msg - def refresh(self, args=None): super().refresh(args) - summary = self._summary_text() + summary = ntp.get_ntp_servers_summary( + self._servers, + self._states + ) + self.window.add_with_separator(TextWidget(summary)) self._container = ListColumnContainer(1, columns_width=78, spacing=1) - self._container.add(TextWidget(_("Add NTP server")), self._add_ntp_server) # only add the remove option when we can remove something - if self._time_spoke.ntp_servers: + if self._servers: self._container.add(TextWidget(_("Remove NTP server")), self._remove_ntp_server) self.window.add_with_separator(self._container) def _add_ntp_server(self, data): - new_spoke = AddNTPServerSpoke(self.data, self.storage, self.payload, self._time_spoke) + new_spoke = AddNTPServerSpoke( + self.data, + self.storage, + self.payload, + self._servers, + self._states + ) ScreenHandler.push_screen_modal(new_spoke) self.redraw() def _remove_ntp_server(self, data): - new_spoke = RemoveNTPServerSpoke(self.data, self.storage, self.payload, self._time_spoke) + new_spoke = RemoveNTPServerSpoke( + self.data, + self.storage, + self.payload, + self._servers, + self._states + ) ScreenHandler.push_screen_modal(new_spoke) self.redraw() @@ -434,12 +362,12 @@ def apply(self): class AddNTPServerSpoke(NormalTUISpoke): category = LocalizationCategory - def __init__(self, data, storage, payload, time_spoke): + def __init__(self, data, storage, payload, servers, states): super().__init__(data, storage, payload) self.title = N_("Add NTP server address") - self._time_spoke = time_spoke - self._new_ntp_server = None - self.value = None + self._servers = servers + self._states = states + self._value = None @property def indirect(self): @@ -447,76 +375,80 @@ def indirect(self): def refresh(self, args=None): super().refresh(args) - self.value = None + self._value = None def prompt(self, args=None): # the title is enough, no custom prompt is needed - if self.value is None: # first run or nothing entered + if self._value is None: # first run or nothing entered return Prompt(_("Enter an NTP server address and press %s") % Prompt.ENTER) # an NTP server address has been entered - self._new_ntp_server = self.value + self._add_ntp_server(self._value) - self.apply() self.close() + def _add_ntp_server(self, server_hostname): + for server in self._servers: + if server.hostname == server_hostname: + return + + server = TimeSourceData() + server.type = TIME_SOURCE_SERVER + server.hostname = server_hostname + server.options = ["iburst"] + + self._servers.append(server) + self._states.check_status(server) + def input(self, args, key): # we accept any string as NTP server address, as we do an automatic # working/not-working check on the address later - self.value = key + self._value = key return InputState.DISCARDED def apply(self): - if self._new_ntp_server: - self._time_spoke.add_ntp_server(self._new_ntp_server) + pass class RemoveNTPServerSpoke(NormalTUISpoke): category = LocalizationCategory - def __init__(self, data, storage, payload, timezone_spoke): + def __init__(self, data, storage, payload, servers, states): super().__init__(data, storage, payload) self.title = N_("Select an NTP server to remove") - self._time_spoke = timezone_spoke - self._ntp_server_index = None + self._servers = servers + self._states = states + self._container = None @property def indirect(self): return True - def _summary_text(self): - """Return a numbered listing of NTP servers.""" - msg = "" - for index, status in enumerate(format_ntp_status_list(self._time_spoke.ntp_servers), start=1): - msg += "%d) %s" % (index, status) - if index < len(self._time_spoke.ntp_servers): - msg += "\n" - return msg - def refresh(self, args=None): super().refresh(args) - summary = self._summary_text() - self.window.add_with_separator(TextWidget(summary)) + self._container = ListColumnContainer(1) - def input(self, args, key): - try: - num = int(key) - except ValueError: - return super().input(args, key) + for server in self._servers: + description = ntp.get_ntp_server_summary( + server, self._states + ) - # we expect a number corresponding to one of the NTP servers - # in the listing - the server corresponding to the number will be - # removed from the NTP server tracking (ordered) dict - if num > 0 and num <= len(self._time_spoke.ntp_servers): - self._ntp_server_index = num - 1 - self.apply() + self._container.add( + TextWidget(description), + self._remove_ntp_server, + server + ) + + self.window.add_with_separator(self._container) + + def _remove_ntp_server(self, server): + self._servers.remove(server) + + def input(self, args, key): + if self._container.process_user_input(key): return InputState.PROCESSED_AND_CLOSE - else: - # the user enter a number that is out of range of the - # available NTP servers, ignore it and stay in spoke - return InputState.DISCARDED + + return super().input(args, key) def apply(self): - if self._ntp_server_index is not None: - ntp_server_address = list(self._time_spoke.ntp_servers.keys())[self._ntp_server_index] - self._time_spoke.remove_ntp_server(ntp_server_address) + pass -- 2.23.0