469 lines
17 KiB
Diff
469 lines
17 KiB
Diff
From 8a10cee0ab94b844c65d1493b3d78df5210e4e34 Mon Sep 17 00:00:00 2001
|
|
From: Vendula Poncova <vponcova@redhat.com>
|
|
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
|