improve ntp servers to fix unkown error

This commit is contained in:
eaglegai 2020-12-04 17:45:47 +08:00
parent 1e072253d7
commit 54f882c593
11 changed files with 2578 additions and 2 deletions

View File

@ -1,7 +1,7 @@
%define _empty_manifest_terminate_build 0 %define _empty_manifest_terminate_build 0
Name: anaconda Name: anaconda
Version: 33.19 Version: 33.19
Release: 13 Release: 14
Summary: Graphical system installer Summary: Graphical system installer
License: GPLv2+ and MIT License: GPLv2+ and MIT
URL: http://fedoraproject.org/wiki/Anaconda URL: http://fedoraproject.org/wiki/Anaconda
@ -41,6 +41,16 @@ Patch9023: bugfix-add-dnf-transaction-timeout.patch
Patch6007: fix-0-storage-devices-selected.patch Patch6007: fix-0-storage-devices-selected.patch
Patch6008: fix-remove-unknow-partition-is-sda-failed.patch Patch6008: fix-remove-unknow-partition-is-sda-failed.patch
Patch6009: use-modinfo-to-check-ko-before-modprobe.patch Patch6009: use-modinfo-to-check-ko-before-modprobe.patch
Patch6010: ntp-servers-improve-001-Create-a-new-DBus-structure-for-time-sources.patch
Patch6011: ntp-servers-improve-002-Use-the-structure-for-time-sources-in-ntp-py.patch
Patch6012: ntp-servers-improve-003-Use-the-structure-for-time-sources-in-the-Timezone-module.patch
Patch6013: ntp-servers-improve-004-Use-the-structure-for-time-sources-in-anaconda-py.patch
Patch6014: ntp-servers-improve-005-Use-the-structure-for-time-sources-in-network-py.patch
Patch6015: ntp-servers-improve-006-Add-support-for-the-NTP-server-status-cache.patch
Patch6016: ntp-servers-improve-007-Add-support-for-generating-a-summary-of-the-NTP-servers.patch
Patch6017: ntp-servers-improve-008-Use-the-structure-for-time-sources-in-TUI.patch
Patch6018: ntp-servers-improve-009-Use-the-structure-for-time-sources-in-GUI.patch
Patch6019: ntp-servers-improve-010-Add-support-for-the-timesource-kickstart-command.patch
%define dbusver 1.2.3 %define dbusver 1.2.3
%define dnfver 3.6.0 %define dnfver 3.6.0
@ -57,7 +67,7 @@ Patch6009: use-modinfo-to-check-ko-before-modprobe.patch
%define libxklavierver 5.4 %define libxklavierver 5.4
%define mehver 0.23-1 %define mehver 0.23-1
%define nmver 1.0 %define nmver 1.0
%define pykickstartver 3.25-1 %define pykickstartver 3.27-1
%define pypartedver 2.5-2 %define pypartedver 2.5-2
%define rpmver 4.10.0 %define rpmver 4.10.0
%define simplelinever 1.1-1 %define simplelinever 1.1-1
@ -254,6 +264,12 @@ update-desktop-database &> /dev/null || :
%{_datadir}/gtk-doc %{_datadir}/gtk-doc
%changelog %changelog
* Fri Dec 04 2020 gaihuiying <gaihuiying1@huawei.com> - 33.19-14
- Type:bugfix
- ID:NA
- SUG:NA
- DESC:improve ntp servers to fix unkown error
* Sat Nov 28 2020 lunankun <lunankun@huawei.com> - 33.19-13 * Sat Nov 28 2020 lunankun <lunankun@huawei.com> - 33.19-13
- Type:bugfix - Type:bugfix
- ID:NA - ID:NA

View File

@ -0,0 +1,119 @@
From 554be1cfd3d09035e0370f1efc46d3fc40f8496d Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 11:11:46 +0200
Subject: [PATCH] Create a new DBus structure for time sources
Use the class TimeSourceData to represent a time source.
---
pyanaconda/core/constants.py | 4 +
.../modules/common/structures/timezone.py | 84 +++++++++++++++++++
2 files changed, 88 insertions(+)
create mode 100644 pyanaconda/modules/common/structures/timezone.py
diff --git a/pyanaconda/core/constants.py b/pyanaconda/core/constants.py
index 536529f4e0..5124f05b7f 100644
--- a/pyanaconda/core/constants.py
+++ b/pyanaconda/core/constants.py
@@ -306,6 +306,10 @@ class SecretStatus(Enum):
# Window title text
WINDOW_TITLE_TEXT = N_("Anaconda Installer")
+# Types of time sources.
+TIME_SOURCE_SERVER = "SERVER"
+TIME_SOURCE_POOL = "POOL"
+
# NTP server checking
NTP_SERVER_OK = 0
NTP_SERVER_NOK = 1
diff --git a/pyanaconda/modules/common/structures/timezone.py b/pyanaconda/modules/common/structures/timezone.py
new file mode 100644
index 0000000000..d18234f681
--- /dev/null
+++ b/pyanaconda/modules/common/structures/timezone.py
@@ -0,0 +1,84 @@
+#
+# DBus structures for the timezone data.
+#
+# Copyright (C) 2020 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+from dasbus.structure import DBusData
+from dasbus.typing import * # pylint: disable=wildcard-import
+
+from pyanaconda.core.constants import TIME_SOURCE_SERVER
+
+__all__ = ["TimeSourceData"]
+
+
+class TimeSourceData(DBusData):
+ """Data for a time source."""
+
+ def __init__(self):
+ self._type = TIME_SOURCE_SERVER
+ self._hostname = ""
+ self._options = []
+
+ @property
+ def type(self) -> Str:
+ """Type of the time source.
+
+ Supported values:
+
+ SERVER A single NTP server
+ POOL A pool of NTP servers
+
+ :return: a type of the time source
+ """
+ return self._type
+
+ @type.setter
+ def type(self, value: Str):
+ self._type = value
+
+ @property
+ def hostname(self) -> Str:
+ """Name of the time server.
+
+ For example:
+
+ ntp.cesnet.cz
+
+ :return: a host name
+ """
+ return self._hostname
+
+ @hostname.setter
+ def hostname(self, value: Str):
+ self._hostname = value
+
+ @property
+ def options(self) -> List[Str]:
+ """Options of the time source.
+
+ For example:
+
+ nts, ntsport 1234, iburst
+
+ See ``man chrony.conf``.
+
+ :return: a list of options
+ """
+ return self._options
+
+ @options.setter
+ def options(self, value):
+ self._options = value
--
2.23.0

View File

@ -0,0 +1,290 @@
From a645a1b8d17310533ef2d9232855c1852558e2b8 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 12:04:04 +0200
Subject: [PATCH] Use the structure for time sources in ntp.py
Modify ntp.py to work with TimeSourceData instead of strings and clean up
its functions a little.
---
pyanaconda/ntp.py | 174 +++++++++++++++++++---------------------------
1 file changed, 73 insertions(+), 101 deletions(-)
diff --git a/pyanaconda/ntp.py b/pyanaconda/ntp.py
index 16eece65e4..1b74ac9433 100644
--- a/pyanaconda/ntp.py
+++ b/pyanaconda/ntp.py
@@ -31,6 +31,7 @@
from pyanaconda import isys
from pyanaconda.threading import threadMgr, AnacondaThread
from pyanaconda.core.constants import THREAD_SYNC_TIME_BASENAME
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
NTP_CONFIG_FILE = "/etc/chrony.conf"
@@ -47,21 +48,18 @@ class NTPconfigError(Exception):
pass
-def ntp_server_working(server):
- """
- Tries to do an NTP request to the $server (timeout may take some time).
+def ntp_server_working(server_hostname):
+ """Tries to do an NTP request to the server (timeout may take some time).
- :param server: hostname or IP address of an NTP server
- :type server: string
+ :param server_hostname: a host name or an IP address of an NTP server
+ :type server_hostname: string
:return: True if the given server is reachable and working, False otherwise
:rtype: bool
-
"""
-
client = ntplib.NTPClient()
try:
- client.request(server)
+ client.request(server_hostname)
except ntplib.NTPException:
return False
# address related error
@@ -75,118 +73,89 @@ def ntp_server_working(server):
return True
-def pools_servers_to_internal(pools, servers):
- ret = []
- for pool in pools:
- ret.extend(SERVERS_PER_POOL * [pool])
- ret.extend(servers)
-
- return ret
-
-
-def internal_to_pools_and_servers(pools_servers):
- server_nums = dict()
- pools = []
- servers = []
-
- for item in pools_servers:
- server_nums[item] = server_nums.get(item, 0) + 1
-
- for item in server_nums.keys():
- if server_nums[item] >= SERVERS_PER_POOL:
- pools.extend((server_nums[item] // SERVERS_PER_POOL) * [item])
- servers.extend((server_nums[item] % SERVERS_PER_POOL) * [item])
- else:
- servers.extend(server_nums[item] * [item])
+def get_servers_from_config(conf_file_path=NTP_CONFIG_FILE):
+ """Get NTP servers from a configuration file.
- return (pools, servers)
-
-
-def get_servers_from_config(conf_file_path=NTP_CONFIG_FILE,
- srv_regexp=SRV_LINE_REGEXP):
- """
Goes through the chronyd's configuration file looking for lines starting
with 'server'.
+ :param conf_file_path: a path to the chronyd's configuration file
:return: servers found in the chronyd's configuration
- :rtype: list
-
+ :rtype: a list of TimeSourceData instances
"""
-
- pools = list()
- servers = list()
+ servers = []
try:
with open(conf_file_path, "r") as conf_file:
for line in conf_file:
- match = srv_regexp.match(line)
- if match:
- if match.group(1) == "pool":
- pools.append(match.group(2))
- else:
- servers.append(match.group(2))
+ match = SRV_LINE_REGEXP.match(line)
+
+ if not match:
+ continue
+
+ server = TimeSourceData()
+ server.type = match.group(1).upper()
+ server.hostname = match.group(2)
+ server.options = ["iburst"]
+ servers.append(server)
except IOError as ioerr:
- msg = "Cannot open config file %s for reading (%s)" % (conf_file_path,
- ioerr.strerror)
- raise NTPconfigError(msg)
+ msg = "Cannot open config file {} for reading ({})."
+ raise NTPconfigError(msg.format(conf_file_path, ioerr.strerror))
- return (pools, servers)
+ return servers
-def save_servers_to_config(pools, servers, conf_file_path=NTP_CONFIG_FILE,
- srv_regexp=SRV_LINE_REGEXP, out_file_path=None):
- """
+def save_servers_to_config(servers, conf_file_path=NTP_CONFIG_FILE, out_file_path=None):
+ """Save NTP servers to a configuration file.
+
Replaces the pools and servers defined in the chronyd's configuration file
with the given ones. If the out_file is not None, then it is used for the
resulting config.
- :type pools: iterable
- :type servers: iterable
- :param out_file_path: path to the file used for the resulting config
-
+ :param servers: a list of NTP servers and pools
+ :type servers: a list of TimeSourceData instances
+ :param conf_file_path: a path to the chronyd's configuration file
+ :param out_file_path: a path to the file used for the resulting config
"""
+ temp_path = None
try:
old_conf_file = open(conf_file_path, "r")
-
except IOError as ioerr:
- msg = "Cannot open config file %s for reading (%s)" % (conf_file_path,
- ioerr.strerror)
- raise NTPconfigError(msg)
+ msg = "Cannot open config file {} for reading ({})."
+ raise NTPconfigError(msg.format(conf_file_path, ioerr.strerror))
- try:
- if out_file_path:
+ if out_file_path:
+ try:
new_conf_file = open(out_file_path, "w")
- else:
- (fildes, temp_path) = tempfile.mkstemp()
- new_conf_file = os.fdopen(fildes, "w")
-
- except IOError as ioerr:
- if out_file_path:
- msg = "Cannot open new config file %s "\
- "for writing (%s)" % (out_file_path, ioerr.strerror)
- else:
- msg = "Cannot open temporary file %s "\
- "for writing (%s)" % (temp_path, ioerr.strerror)
-
- raise NTPconfigError(msg)
+ except IOError as ioerr:
+ msg = "Cannot open new config file {} for writing ({})."
+ raise NTPconfigError(msg.format(out_file_path, ioerr.strerror))
+ else:
+ try:
+ (fields, temp_path) = tempfile.mkstemp()
+ new_conf_file = os.fdopen(fields, "w")
+ except IOError as ioerr:
+ msg = "Cannot open temporary file {} for writing ({})."
+ raise NTPconfigError(msg.format(temp_path, ioerr.strerror))
heading = "# These servers were defined in the installation:\n"
- #write info about the origin of the following lines
+ # write info about the origin of the following lines
new_conf_file.write(heading)
- #write new servers and pools
- for pool in pools:
- new_conf_file.write("pool " + pool + " iburst\n")
-
+ # write new servers and pools
for server in servers:
- new_conf_file.write("server " + server + " iburst\n")
+ args = [server.type.lower(), server.hostname] + server.options
+ line = " ".join(args) + "\n"
+ new_conf_file.write(line)
- #copy non-server lines from the old config and skip our heading
+ new_conf_file.write("\n")
+
+ # copy non-server lines from the old config and skip our heading
for line in old_conf_file:
- if not srv_regexp.match(line) and line != heading:
+ if not SRV_LINE_REGEXP.match(line) and line != heading:
new_conf_file.write(line)
old_conf_file.close()
@@ -199,28 +168,27 @@ def save_servers_to_config(pools, servers, conf_file_path=NTP_CONFIG_FILE,
os.unlink(temp_path)
except OSError as oserr:
- msg = "Cannot replace the old config with "\
- "the new one (%s)" % (oserr.strerror)
+ msg = "Cannot replace the old config with the new one ({})."
+ raise NTPconfigError(msg.format(oserr.strerror))
- raise NTPconfigError(msg)
+def _one_time_sync(server, callback=None):
+ """Synchronize the system time with a given NTP server.
-def one_time_sync(server, callback=None):
- """
Synchronize the system time with a given NTP server. Note that this
function is blocking and will not return until the time gets synced or
querying server fails (may take some time before timeouting).
- :param server: NTP server
+ :param server: an NTP server
+ :type server: an instance of TimeSourceData
:param callback: callback function to run after sync or failure
:type callback: a function taking one boolean argument (success)
:return: True if the sync was successful, False otherwise
-
"""
client = ntplib.NTPClient()
try:
- results = client.request(server)
+ results = client.request(server.hostname)
isys.set_system_time(int(results.tx_time))
success = True
except ntplib.NTPException:
@@ -235,22 +203,26 @@ def one_time_sync(server, callback=None):
def one_time_sync_async(server, callback=None):
- """
+ """Asynchronously synchronize the system time with a given NTP server.
+
Asynchronously synchronize the system time with a given NTP server. This
function is non-blocking it starts a new thread for synchronization and
returns. Use callback argument to specify the function called when the
new thread finishes if needed.
- :param server: NTP server
+ :param server: an NTP server
+ :type server: an instance of TimeSourceData
:param callback: callback function to run after sync or failure
:type callback: a function taking one boolean argument (success)
-
"""
+ thread_name = "%s_%s" % (THREAD_SYNC_TIME_BASENAME, server.hostname)
- thread_name = "%s_%s" % (THREAD_SYNC_TIME_BASENAME, server)
+ # syncing with the same server running
if threadMgr.get(thread_name):
- #syncing with the same server running
return
- threadMgr.add(AnacondaThread(name=thread_name, target=one_time_sync,
- args=(server, callback)))
+ threadMgr.add(AnacondaThread(
+ name=thread_name,
+ target=_one_time_sync,
+ args=(server, callback)
+ ))
--
2.23.0

View File

@ -0,0 +1,406 @@
From 4bc1b7305199fffc78439ab1ad1cdb8272988d52 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 12:21:11 +0200
Subject: [PATCH] Use the structure for time sources in the Timezone module
Modify the Timezone module to work with the DBus structures for time sources
instead of the strings. Rename the DBus property NTPServers to TimeSources.
---
pyanaconda/modules/timezone/installation.py | 14 ++-
pyanaconda/modules/timezone/timezone.py | 44 ++++---
.../modules/timezone/timezone_interface.py | 34 +++---
.../pyanaconda_tests/module_timezone_test.py | 107 ++++++++++++++----
4 files changed, 141 insertions(+), 58 deletions(-)
diff --git a/pyanaconda/modules/timezone/installation.py b/pyanaconda/modules/timezone/installation.py
index 6383df1103..c3ea4d7179 100644
--- a/pyanaconda/modules/timezone/installation.py
+++ b/pyanaconda/modules/timezone/installation.py
@@ -145,12 +145,14 @@ def run(self):
return
chronyd_conf_path = os.path.normpath(self._sysroot + ntp.NTP_CONFIG_FILE)
- pools, servers = ntp.internal_to_pools_and_servers(self._ntp_servers)
if os.path.exists(chronyd_conf_path):
log.debug("Modifying installed chrony configuration")
try:
- ntp.save_servers_to_config(pools, servers, conf_file_path=chronyd_conf_path)
+ ntp.save_servers_to_config(
+ self._ntp_servers,
+ conf_file_path=chronyd_conf_path
+ )
except ntp.NTPconfigError as ntperr:
log.warning("Failed to save NTP configuration: %s", ntperr)
@@ -160,9 +162,11 @@ def run(self):
log.debug("Creating chrony configuration based on the "
"configuration from installation environment")
try:
- ntp.save_servers_to_config(pools, servers,
- conf_file_path=ntp.NTP_CONFIG_FILE,
- out_file_path=chronyd_conf_path)
+ ntp.save_servers_to_config(
+ self._ntp_servers,
+ conf_file_path=ntp.NTP_CONFIG_FILE,
+ out_file_path=chronyd_conf_path
+ )
except ntp.NTPconfigError as ntperr:
log.warning("Failed to save NTP configuration without chrony package: %s",
ntperr)
diff --git a/pyanaconda/modules/timezone/timezone.py b/pyanaconda/modules/timezone/timezone.py
index 0678072978..ff89d1ea77 100644
--- a/pyanaconda/modules/timezone/timezone.py
+++ b/pyanaconda/modules/timezone/timezone.py
@@ -18,10 +18,12 @@
# Red Hat, Inc.
#
from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.constants import TIME_SOURCE_SERVER
from pyanaconda.core.dbus import DBus
from pyanaconda.core.signal import Signal
from pyanaconda.modules.common.base import KickstartService
from pyanaconda.modules.common.constants.services import TIMEZONE
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
from pyanaconda.timezone import NTP_PACKAGE
from pyanaconda.modules.common.containers import TaskContainer
from pyanaconda.modules.common.structures.requirement import Requirement
@@ -48,8 +50,8 @@ def __init__(self):
self.ntp_enabled_changed = Signal()
self._ntp_enabled = True
- self.ntp_servers_changed = Signal()
- self._ntp_servers = []
+ self.time_sources_changed = Signal()
+ self._time_sources = []
# FIXME: temporary workaround until PAYLOAD module is available
self._ntp_excluded = False
@@ -70,7 +72,17 @@ def process_kickstart(self, data):
self.set_timezone(data.timezone.timezone)
self.set_is_utc(data.timezone.isUtc)
self.set_ntp_enabled(not data.timezone.nontp)
- self.set_ntp_servers(data.timezone.ntpservers)
+
+ servers = []
+
+ for hostname in data.timezone.ntpservers:
+ server = TimeSourceData()
+ server.type = TIME_SOURCE_SERVER
+ server.hostname = hostname
+ server.options = ["iburst"]
+ servers.append(server)
+
+ self.set_time_sources(servers)
def setup_kickstart(self, data):
"""Set up the kickstart data."""
@@ -78,8 +90,12 @@ def setup_kickstart(self, data):
data.timezone.isUtc = self.is_utc
data.timezone.nontp = not self.ntp_enabled
- if self.ntp_enabled:
- data.timezone.ntpservers = list(self.ntp_servers)
+ if not self.ntp_enabled:
+ return
+
+ data.timezone.ntpservers = [
+ server.hostname for server in self.time_sources
+ ]
@property
def timezone(self):
@@ -115,15 +131,15 @@ def set_ntp_enabled(self, ntp_enabled):
log.debug("NTP is set to %s.", ntp_enabled)
@property
- def ntp_servers(self):
- """Return a list of NTP servers."""
- return self._ntp_servers
+ def time_sources(self):
+ """Return a list of time sources."""
+ return self._time_sources
- def set_ntp_servers(self, servers):
- """Set NTP servers."""
- self._ntp_servers = list(servers)
- self.ntp_servers_changed.emit()
- log.debug("NTP servers are set to %s.", servers)
+ def set_time_sources(self, servers):
+ """Set time sources."""
+ self._time_sources = list(servers)
+ self.time_sources_changed.emit()
+ log.debug("Time sources are set to: %s", servers)
def collect_requirements(self):
"""Return installation requirements for this module.
@@ -168,6 +184,6 @@ def install_with_tasks(self):
ConfigureNTPTask(
sysroot=conf.target.system_root,
ntp_enabled=self.ntp_enabled,
- ntp_servers=self.ntp_servers
+ ntp_servers=self.time_sources
)
]
diff --git a/pyanaconda/modules/timezone/timezone_interface.py b/pyanaconda/modules/timezone/timezone_interface.py
index 03c5003f1e..f36e0b3723 100644
--- a/pyanaconda/modules/timezone/timezone_interface.py
+++ b/pyanaconda/modules/timezone/timezone_interface.py
@@ -17,13 +17,15 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
-from pyanaconda.modules.common.constants.services import TIMEZONE
-from pyanaconda.modules.common.containers import TaskContainer
from dasbus.server.property import emits_properties_changed
from dasbus.typing import * # pylint: disable=wildcard-import
-from pyanaconda.modules.common.base import KickstartModuleInterface
from dasbus.server.interface import dbus_interface
+from pyanaconda.modules.common.base import KickstartModuleInterface
+from pyanaconda.modules.common.constants.services import TIMEZONE
+from pyanaconda.modules.common.containers import TaskContainer
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
+
@dbus_interface(TIMEZONE.interface_name)
class TimezoneInterface(KickstartModuleInterface):
@@ -34,7 +36,7 @@ def connect_signals(self):
self.watch_property("Timezone", self.implementation.timezone_changed)
self.watch_property("IsUTC", self.implementation.is_utc_changed)
self.watch_property("NTPEnabled", self.implementation.ntp_enabled_changed)
- self.watch_property("NTPServers", self.implementation.ntp_servers_changed)
+ self.watch_property("TimeSources", self.implementation.time_sources_changed)
@property
def Timezone(self) -> Str:
@@ -91,22 +93,26 @@ def SetNTPEnabled(self, ntp_enabled: Bool):
self.implementation.set_ntp_enabled(ntp_enabled)
@property
- def NTPServers(self) -> List[Str]:
- """A list of NTP servers.
+ def TimeSources(self) -> List[Structure]:
+ """A list of time sources.
- :return: a list of servers
+ :return: a list of time source data
+ :rtype: a list of structures of the type TimeSourceData
"""
- return self.implementation.ntp_servers
+ return TimeSourceData.to_structure_list(
+ self.implementation.time_sources
+ )
@emits_properties_changed
- def SetNTPServers(self, servers: List[Str]):
- """Set the NTP servers.
+ def SetTimeSources(self, sources: List[Structure]):
+ """Set the time sources.
- Example: [ntp.cesnet.cz]
-
- :param servers: a list of servers
+ :param sources: a list of time sources
+ :type sources: a list of structures of the type TimeSourceData
"""
- self.implementation.set_ntp_servers(servers)
+ self.implementation.set_time_sources(
+ TimeSourceData.from_structure_list(sources)
+ )
def ConfigureNTPServiceEnablementWithTask(self, ntp_excluded: Bool) -> ObjPath:
"""Enable or disable NTP service.
diff --git a/tests/nosetests/pyanaconda_tests/module_timezone_test.py b/tests/nosetests/pyanaconda_tests/module_timezone_test.py
index f991f1e992..bb751d6f4b 100644
--- a/tests/nosetests/pyanaconda_tests/module_timezone_test.py
+++ b/tests/nosetests/pyanaconda_tests/module_timezone_test.py
@@ -23,8 +23,13 @@
from shutil import copytree, copyfile
from unittest.mock import Mock, patch
+from dasbus.structure import compare_data
+from dasbus.typing import * # pylint: disable=wildcard-import
+
+from pyanaconda.core.constants import TIME_SOURCE_SERVER, TIME_SOURCE_POOL
from pyanaconda.modules.common.constants.services import TIMEZONE
from pyanaconda.modules.common.errors.installation import TimezoneConfigurationError
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
from pyanaconda.modules.timezone.installation import ConfigureNTPTask, ConfigureTimezoneTask, \
ConfigureNTPServiceEnablementTask
from pyanaconda.modules.common.structures.kickstart import KickstartReport
@@ -33,7 +38,7 @@
from pyanaconda.ntp import NTP_CONFIG_FILE, NTPconfigError
from tests.nosetests.pyanaconda_tests import check_kickstart_interface, \
patch_dbus_publish_object, PropertiesChangedCallback, check_task_creation, \
- patch_dbus_get_proxy, check_task_creation_list
+ patch_dbus_get_proxy, check_task_creation_list, check_dbus_property
from pyanaconda.timezone import NTP_SERVICE
@@ -50,6 +55,14 @@ def setUp(self):
self.callback = PropertiesChangedCallback()
self.timezone_interface.PropertiesChanged.connect(self.callback)
+ def _check_dbus_property(self, *args, **kwargs):
+ check_dbus_property(
+ self,
+ TIMEZONE,
+ self.timezone_interface,
+ *args, **kwargs
+ )
+
def kickstart_properties_test(self):
"""Test kickstart properties."""
self.assertEqual(self.timezone_interface.KickstartCommands, ["timezone"])
@@ -76,12 +89,24 @@ def ntp_property_test(self):
self.assertEqual(self.timezone_interface.NTPEnabled, False)
self.callback.assert_called_once_with(TIMEZONE.interface_name, {'NTPEnabled': False}, [])
- def ntp_servers_property_test(self):
- """Test the NTPServers property."""
- self.timezone_interface.SetNTPServers(["ntp.cesnet.cz"])
- self.assertEqual(self.timezone_interface.NTPServers, ["ntp.cesnet.cz"])
- self.callback.assert_called_once_with(
- TIMEZONE.interface_name, {'NTPServers': ["ntp.cesnet.cz"]}, [])
+ def time_sources_property_test(self):
+ """Test the TimeSources property."""
+ server = {
+ "type": get_variant(Str, TIME_SOURCE_SERVER),
+ "hostname": get_variant(Str, "ntp.cesnet.cz"),
+ "options": get_variant(List[Str], ["iburst"]),
+ }
+
+ pool = {
+ "type": get_variant(Str, TIME_SOURCE_POOL),
+ "hostname": get_variant(Str, "0.fedora.pool.ntp.org"),
+ "options": get_variant(List[Str], []),
+ }
+
+ self._check_dbus_property(
+ "TimeSources",
+ [server, pool]
+ )
def _test_kickstart(self, ks_in, ks_out):
check_kickstart_interface(self, self.timezone_interface, ks_in, ks_out)
@@ -162,10 +187,19 @@ def install_with_tasks_configured_test(self, publisher):
self.timezone_interface.SetNTPEnabled(False)
# --nontp and --ntpservers are mutually exclusive in kicstart but
# there is no such enforcement in the module so for testing this is ok
- self.timezone_interface.SetNTPServers([
- "clock1.example.com",
- "clock2.example.com",
- ])
+
+ server = TimeSourceData()
+ server.type = TIME_SOURCE_SERVER
+ server.hostname = "clock1.example.com"
+ server.options = ["iburst"]
+
+ pool = TimeSourceData()
+ pool.type = TIME_SOURCE_POOL
+ pool.hostname = "clock2.example.com"
+
+ self.timezone_interface.SetTimeSources(
+ TimeSourceData.to_structure_list([server, pool])
+ )
task_classes = [
ConfigureTimezoneTask,
@@ -182,10 +216,9 @@ def install_with_tasks_configured_test(self, publisher):
# ConfigureNTPTask
obj = task_objs[1]
self.assertEqual(obj.implementation._ntp_enabled, False)
- self.assertEqual(obj.implementation._ntp_servers, [
- "clock1.example.com",
- "clock2.example.com",
- ])
+ self.assertEqual(len(obj.implementation._ntp_servers), 2)
+ self.assertTrue(compare_data(obj.implementation._ntp_servers[0], server))
+ self.assertTrue(compare_data(obj.implementation._ntp_servers[1], pool))
@patch_dbus_publish_object
def configure_ntp_service_enablement_default_test(self, publisher):
@@ -354,13 +387,13 @@ class NTPTasksTestCase(unittest.TestCase):
def ntp_task_success_test(self):
"""Test the success cases for NTP setup D-Bus task."""
- self._test_ntp_inputs(False, False, ["unique.ntp.server", "another.unique.server"])
- self._test_ntp_inputs(False, True, ["unique.ntp.server", "another.unique.server"])
+ self._test_ntp_inputs(False, False)
+ self._test_ntp_inputs(False, True)
def ntp_overwrite_test(self):
"""Test overwriting existing config for NTP setup D-Bus task."""
- self._test_ntp_inputs(True, True, ["unique.ntp.server", "another.unique.server"])
- self._test_ntp_inputs(True, False, ["unique.ntp.server", "another.unique.server"])
+ self._test_ntp_inputs(True, True)
+ self._test_ntp_inputs(True, False)
def ntp_save_failure_test(self):
"""Test failure when saving NTP config in D-Bus task."""
@@ -368,6 +401,25 @@ def ntp_save_failure_test(self):
self._test_ntp_exception(True)
self._test_ntp_exception(False)
+ def _get_test_sources(self):
+ """Get a list of sources"""
+ server = TimeSourceData()
+ server.type = TIME_SOURCE_SERVER
+ server.hostname = "unique.ntp.server"
+ server.options = ["iburst"]
+
+ pool = TimeSourceData()
+ pool.type = TIME_SOURCE_POOL
+ pool.hostname = "another.unique.server"
+
+ return [server, pool]
+
+ def _get_expected_lines(self):
+ return [
+ "server unique.ntp.server iburst\n",
+ "pool another.unique.server\n"
+ ]
+
@patch("pyanaconda.modules.timezone.installation.ntp.save_servers_to_config",
side_effect=NTPconfigError)
def _test_ntp_exception(self, make_chronyd, mock_save):
@@ -376,11 +428,14 @@ def _test_ntp_exception(self, make_chronyd, mock_save):
with self.assertLogs("anaconda.modules.timezone.installation", level="WARNING"):
self._execute_task(sysroot, True, ["ntp.example.com"])
- def _test_ntp_inputs(self, make_chronyd, ntp_enabled, ntp_servers):
+ def _test_ntp_inputs(self, make_chronyd, ntp_enabled):
+ ntp_servers = self._get_test_sources()
+ expected_lines = self._get_expected_lines()
+
with tempfile.TemporaryDirectory() as sysroot:
self._setup_environment(sysroot, make_chronyd)
self._execute_task(sysroot, ntp_enabled, ntp_servers)
- self._validate_ntp_config(sysroot, make_chronyd, ntp_enabled, ntp_servers)
+ self._validate_ntp_config(sysroot, make_chronyd, ntp_enabled, expected_lines)
def _setup_environment(self, sysroot, make_chronyd):
os.mkdir(sysroot + "/etc")
@@ -395,12 +450,14 @@ def _execute_task(self, sysroot, ntp_enabled, ntp_servers):
)
task.run()
- def _validate_ntp_config(self, sysroot, was_present, was_enabled, expected_servers):
+ def _validate_ntp_config(self, sysroot, was_present, was_enabled, expected_lines):
if was_enabled:
with open(sysroot + NTP_CONFIG_FILE) as fobj:
- all_lines = "\n".join(fobj.readlines())
- for server in expected_servers:
- self.assertIn(server, all_lines)
+ all_lines = fobj.readlines()
+
+ for line in expected_lines:
+ self.assertIn(line, all_lines)
+
elif not was_present:
self.assertFalse(os.path.exists(sysroot + NTP_CONFIG_FILE))
--
2.23.0

View File

@ -0,0 +1,113 @@
From 19dea71f13d55d49e9dfbcc5d941afd5eb5d9e6d Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 12:08:05 +0200
Subject: [PATCH] Use the structure for time sources in anaconda.py
Modify anaconda.py to work with TimeSourceData instead of strings.
---
anaconda.py | 11 +--------
pyanaconda/startup_utils.py | 45 +++++++++++++++++++++++++++----------
2 files changed, 34 insertions(+), 22 deletions(-)
diff --git a/anaconda.py b/anaconda.py
index 1abdeb2e1a..d6bb57190c 100755
--- a/anaconda.py
+++ b/anaconda.py
@@ -325,7 +325,6 @@ def setup_environment():
from pyanaconda import vnc
from pyanaconda import kickstart
- from pyanaconda import ntp
from pyanaconda import keyboard
# we are past the --version and --help shortcut so we can import display &
# startup_utils, which import Blivet, without slowing down anything critical
@@ -714,15 +713,7 @@ def _earlyExceptionHandler(ty, value, traceback):
geoloc.geoloc.refresh()
# setup ntp servers and start NTP daemon if not requested otherwise
- if conf.system.can_set_time_synchronization:
- kickstart_ntpservers = timezone_proxy.NTPServers
-
- if kickstart_ntpservers:
- pools, servers = ntp.internal_to_pools_and_servers(kickstart_ntpservers)
- ntp.save_servers_to_config(pools, servers)
-
- if timezone_proxy.NTPEnabled:
- util.start_service("chronyd")
+ startup_utils.start_chronyd()
# Finish the initialization of the setup on boot action.
# This should be done sooner and somewhere else once it is possible.
diff --git a/pyanaconda/startup_utils.py b/pyanaconda/startup_utils.py
index f08b19e11a..e53d5491c1 100644
--- a/pyanaconda/startup_utils.py
+++ b/pyanaconda/startup_utils.py
@@ -17,29 +17,28 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
-from pyanaconda.core.configuration.anaconda import conf
-from pyanaconda.core.i18n import _
-
-from pyanaconda.anaconda_loggers import get_stdout_logger, get_storage_logger, get_packaging_logger
-stdout_log = get_stdout_logger()
-
-from pyanaconda.anaconda_loggers import get_module_logger
-log = get_module_logger(__name__)
-
import sys
import time
import os
+import blivet
-from pyanaconda.core import util, constants
-from pyanaconda import product
+from pyanaconda import product, ntp
from pyanaconda import anaconda_logging
from pyanaconda import network
from pyanaconda import safe_dbus
from pyanaconda import kickstart
+from pyanaconda.anaconda_loggers import get_stdout_logger, get_storage_logger, \
+ get_packaging_logger, get_module_logger
+from pyanaconda.core import util, constants
+from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.i18n import _
from pyanaconda.flags import flags
from pyanaconda.screensaver import inhibit_screensaver
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
+from pyanaconda.modules.common.constants.services import TIMEZONE
-import blivet
+stdout_log = get_stdout_logger()
+log = get_module_logger(__name__)
def gtk_warning(title, reason):
@@ -373,3 +372,25 @@ def parse_kickstart(ks, addon_paths, strict_mode=False):
kickstart.parseKickstart(ksdata, ks, strict_mode=strict_mode, pass_to_boss=True)
return ksdata
+
+
+def start_chronyd():
+ """Start the NTP daemon chronyd.
+
+ Set up NTP servers and start NTP daemon if not requested otherwise.
+ """
+ if not conf.system.can_set_time_synchronization:
+ log.debug("Skip the time synchronization.")
+ return
+
+ timezone_proxy = TIMEZONE.get_proxy()
+ enabled = timezone_proxy.NTPEnabled
+ servers = TimeSourceData.from_structure_list(
+ timezone_proxy.TimeSources
+ )
+
+ if servers:
+ ntp.save_servers_to_config(servers)
+
+ if enabled:
+ util.start_service("chronyd")
--
2.23.0

View File

@ -0,0 +1,80 @@
From 4635e846a98182901777ab6de492020082f313cb Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 12:12:33 +0200
Subject: [PATCH] Use the structure for time sources in network.py
Modify network.py to work with TimeSourceData instead of strings.
---
pyanaconda/network.py | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/pyanaconda/network.py b/pyanaconda/network.py
index f8e9b19a15..bce57354b1 100644
--- a/pyanaconda/network.py
+++ b/pyanaconda/network.py
@@ -16,14 +16,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import gi
-gi.require_version("NM", "1.0")
-
-from gi.repository import NM
-
import shutil
-from pyanaconda.core import util, constants
import socket
import itertools
import os
@@ -34,17 +27,24 @@
from dasbus.typing import get_native
+from pyanaconda.anaconda_loggers import get_module_logger
+from pyanaconda.core import util, constants
from pyanaconda.core.i18n import _
from pyanaconda.core.kernel import kernel_arguments
from pyanaconda.core.regexes import HOSTNAME_PATTERN_WITHOUT_ANCHORS, \
IPV6_ADDRESS_IN_DRACUT_IP_OPTION
from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.constants import TIME_SOURCE_SERVER
from pyanaconda.modules.common.constants.services import NETWORK, TIMEZONE, STORAGE
from pyanaconda.modules.common.constants.objects import FCOE
from pyanaconda.modules.common.task import sync_run_task
from pyanaconda.modules.common.structures.network import NetworkDeviceInfo
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
+
+import gi
+gi.require_version("NM", "1.0")
+from gi.repository import NM
-from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
DEFAULT_HOSTNAME = "localhost.localdomain"
@@ -347,9 +347,20 @@ def _set_ntp_servers_from_dhcp():
hostnames.append(hostname)
# check if some NTP servers were specified from kickstart
- if not timezone_proxy.NTPServers and conf.target.is_hardware:
+ if not timezone_proxy.TimeSources and conf.target.is_hardware:
# no NTP servers were specified, add those from DHCP
- timezone_proxy.SetNTPServers(hostnames)
+ servers = []
+
+ for hostname in hostnames:
+ server = TimeSourceData()
+ server.type = TIME_SOURCE_SERVER
+ server.hostname = hostname
+ server.options = ["iburst"]
+ servers.append(server)
+
+ timezone_proxy.SetTimeSources(
+ TimeSourceData.to_structure_list(servers)
+ )
def wait_for_connected_NM(timeout=constants.NETWORK_CONNECTION_TIMEOUT, only_connecting=False):
--
2.23.0

View File

@ -0,0 +1,120 @@
From 06ed7b6cee7baf64cf83411645bfa52a05767b92 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Mon, 6 Jul 2020 14:16:40 +0200
Subject: [PATCH] Add support for the NTP server status cache
Use the class NTPServerStatusCache to check the status of the given NTP server.
The cache remembers results of all checked host names.
---
pyanaconda/ntp.py | 84 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 82 insertions(+), 2 deletions(-)
diff --git a/pyanaconda/ntp.py b/pyanaconda/ntp.py
index 1b74ac9433..637d31f63e 100644
--- a/pyanaconda/ntp.py
+++ b/pyanaconda/ntp.py
@@ -29,9 +29,12 @@
import socket
from pyanaconda import isys
-from pyanaconda.threading import threadMgr, AnacondaThread
-from pyanaconda.core.constants import THREAD_SYNC_TIME_BASENAME
+from pyanaconda.anaconda_loggers import get_module_logger
+from pyanaconda.core.i18n import N_, _
+from pyanaconda.core.constants import THREAD_SYNC_TIME_BASENAME, NTP_SERVER_QUERY, \
+ THREAD_NTP_SERVER_CHECK, NTP_SERVER_OK, NTP_SERVER_NOK
from pyanaconda.modules.common.structures.timezone import TimeSourceData
+from pyanaconda.threading import threadMgr, AnacondaThread
NTP_CONFIG_FILE = "/etc/chrony.conf"
@@ -42,6 +45,15 @@
#treat pools as four servers with the same name
SERVERS_PER_POOL = 4
+# Description of an NTP server status.
+NTP_SERVER_STATUS_DESCRIPTIONS = {
+ NTP_SERVER_OK: N_("status: working"),
+ NTP_SERVER_NOK: N_("status: not working"),
+ NTP_SERVER_QUERY: N_("checking status")
+}
+
+log = get_module_logger(__name__)
+
class NTPconfigError(Exception):
"""Exception class for NTP related problems"""
@@ -226,3 +238,71 @@ def one_time_sync_async(server, callback=None):
target=_one_time_sync,
args=(server, callback)
))
+
+
+class NTPServerStatusCache(object):
+ """The cache of NTP server states."""
+
+ def __init__(self):
+ self._cache = {}
+
+ def get_status(self, server):
+ """Get the status of the given NTP server.
+
+ :param TimeSourceData server: an NTP server
+ :return int: a status of the NTP server
+ """
+ return self._cache.get(
+ server.hostname,
+ NTP_SERVER_QUERY
+ )
+
+ def get_status_description(self, server):
+ """Get the status description of the given NTP server.
+
+ :param TimeSourceData server: an NTP server
+ :return str: a status description of the NTP server
+ """
+ status = self.get_status(server)
+ return _(NTP_SERVER_STATUS_DESCRIPTIONS[status])
+
+ def check_status(self, server):
+ """Asynchronously check if given NTP servers appear to be working.
+
+ :param TimeSourceData server: an NTP server
+ """
+ # Get a hostname.
+ hostname = server.hostname
+
+ # Reset the current status.
+ self._set_status(hostname, NTP_SERVER_QUERY)
+
+ # Start the check.
+ threadMgr.add(AnacondaThread(
+ prefix=THREAD_NTP_SERVER_CHECK,
+ target=self._check_status,
+ args=(hostname, ))
+ )
+
+ def _set_status(self, hostname, status):
+ """Set the status of the given NTP server.
+
+ :param str hostname: a hostname of an NTP server
+ :return int: a status of the NTP server
+ """
+ self._cache[hostname] = status
+
+ def _check_status(self, hostname):
+ """Check if an NTP server appears to be working.
+
+ :param str hostname: a hostname of an NTP server
+ """
+ log.debug("Checking NTP server %s", hostname)
+ result = ntp_server_working(hostname)
+
+ if result:
+ log.debug("NTP server %s appears to be working.", hostname)
+ self._set_status(hostname, NTP_SERVER_OK)
+ else:
+ log.debug("NTP server %s appears not to be working.", hostname)
+ self._set_status(hostname, NTP_SERVER_NOK)
--
2.23.0

View File

@ -0,0 +1,60 @@
From 716db242314b710b881c073d290b6d1ad8670d36 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Mon, 6 Jul 2020 14:19:46 +0200
Subject: [PATCH] Add support for generating a summary of the NTP servers
Call the functions get_ntp_server_summary and get_ntp_servers_summary to
generate a string with a summary of the specified NTP servers and their
states.
---
pyanaconda/ntp.py | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/pyanaconda/ntp.py b/pyanaconda/ntp.py
index 637d31f63e..eed4b34307 100644
--- a/pyanaconda/ntp.py
+++ b/pyanaconda/ntp.py
@@ -60,6 +60,41 @@ class NTPconfigError(Exception):
pass
+def get_ntp_server_summary(server, states):
+ """Generate a summary of an NTP server and its status.
+
+ :param server: an NTP server
+ :type server: an instance of TimeSourceData
+ :param states: a cache of NTP server states
+ :type states: an instance of NTPServerStatusCache
+ :return: a string with a summary
+ """
+ return "{} ({})".format(
+ server.hostname,
+ states.get_status_description(server)
+ )
+
+
+def get_ntp_servers_summary(servers, states):
+ """Generate a summary of NTP servers and their states.
+
+ :param servers: a list of NTP servers
+ :type servers: a list of TimeSourceData
+ :param states: a cache of NTP server states
+ :type states: an instance of NTPServerStatusCache
+ :return: a string with a summary
+ """
+ summary = _("NTP servers:")
+
+ for server in servers:
+ summary += "\n" + get_ntp_server_summary(server, states)
+
+ if not servers:
+ summary += " " + _("not configured")
+
+ return summary
+
+
def ntp_server_working(server_hostname):
"""Tries to do an NTP request to the server (timeout may take some time).
--
2.23.0

View File

@ -0,0 +1,468 @@
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

View File

@ -0,0 +1,620 @@
From 15d2b2fb568df2c1a77cfb2baa703ae9f3da0f30 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 13:38:10 +0200
Subject: [PATCH] Use the structure for time sources in GUI
Modify GUI to work with TimeSourceData instead of strings.
---
pyanaconda/ui/gui/spokes/datetime_spoke.glade | 4 +
pyanaconda/ui/gui/spokes/datetime_spoke.py | 385 +++++++++---------
2 files changed, 187 insertions(+), 202 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.glade b/pyanaconda/ui/gui/spokes/datetime_spoke.glade
index 37c7c6edc0..49e33776f5 100644
--- a/pyanaconda/ui/gui/spokes/datetime_spoke.glade
+++ b/pyanaconda/ui/gui/spokes/datetime_spoke.glade
@@ -87,6 +87,8 @@
<column type="gint"/>
<!-- column-name use -->
<column type="gboolean"/>
+ <!-- column-name object -->
+ <column type="PyObject"/>
</columns>
</object>
<object class="GtkDialog" id="ntpConfigDialog">
@@ -242,6 +244,8 @@
<object class="GtkCellRendererText" id="hostnameRenderer">
<property name="editable">True</property>
<signal name="edited" handler="on_server_edited" swapped="no"/>
+ <signal name="editing-started" handler="on_server_editing_started" swapped="no"/>
+ <signal name="editing-canceled" handler="on_server_editing_canceled" swapped="no"/>
</object>
<attributes>
<attribute name="text">0</attribute>
diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.py b/pyanaconda/ui/gui/spokes/datetime_spoke.py
index 00b1bd9d56..ea121e7e4d 100644
--- a/pyanaconda/ui/gui/spokes/datetime_spoke.py
+++ b/pyanaconda/ui/gui/spokes/datetime_spoke.py
@@ -16,47 +16,48 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
+import datetime
+import re
+import time
+import locale as locale_mod
+import functools
+import copy
+from pyanaconda import isys
+from pyanaconda import network
+from pyanaconda import ntp
+from pyanaconda import flags
from pyanaconda.anaconda_loggers import get_module_logger
-log = get_module_logger(__name__)
-
-import gi
-gi.require_version("Gdk", "3.0")
-gi.require_version("Gtk", "3.0")
-gi.require_version("TimezoneMap", "1.0")
-
-from gi.repository import Gdk, Gtk, TimezoneMap
-
+from pyanaconda.core import util, constants
+from pyanaconda.core.async_utils import async_action_wait, async_action_nowait
+from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.constants import TIME_SOURCE_POOL, TIME_SOURCE_SERVER
+from pyanaconda.core.i18n import _, CN_
+from pyanaconda.core.timer import Timer
+from pyanaconda.localization import get_xlated_timezone, resolve_date_format
+from pyanaconda.modules.common.structures.timezone import TimeSourceData
+from pyanaconda.modules.common.constants.services import TIMEZONE, NETWORK
+from pyanaconda.ntp import NTPServerStatusCache
from pyanaconda.ui.communication import hubQ
from pyanaconda.ui.common import FirstbootSpokeMixIn
from pyanaconda.ui.gui import GUIObject
from pyanaconda.ui.gui.spokes import NormalSpoke
from pyanaconda.ui.categories.localization import LocalizationCategory
-from pyanaconda.ui.gui.utils import gtk_call_once, override_cell_property
+from pyanaconda.ui.gui.utils import override_cell_property
from pyanaconda.ui.gui.utils import blockedHandler
from pyanaconda.ui.gui.helpers import GUIDialogInputCheckHandler
from pyanaconda.ui.helpers import InputCheck
-
-from pyanaconda.core import util, constants
-from pyanaconda.core.configuration.anaconda import conf
-from pyanaconda import isys
-from pyanaconda import network
-from pyanaconda import ntp
-from pyanaconda import flags
-from pyanaconda.modules.common.constants.services import TIMEZONE, NETWORK
-from pyanaconda.threading import threadMgr, AnacondaThread
-from pyanaconda.core.i18n import _, CN_
-from pyanaconda.core.async_utils import async_action_wait, async_action_nowait
from pyanaconda.timezone import NTP_SERVICE, get_all_regions_and_timezones, get_timezone, is_valid_timezone
-from pyanaconda.localization import get_xlated_timezone, resolve_date_format
-from pyanaconda.core.timer import Timer
+from pyanaconda.threading import threadMgr, AnacondaThread
-import datetime
-import re
-import threading
-import time
-import locale as locale_mod
-import functools
+import gi
+gi.require_version("Gdk", "3.0")
+gi.require_version("Gtk", "3.0")
+gi.require_version("TimezoneMap", "1.0")
+
+from gi.repository import Gdk, Gtk, TimezoneMap
+
+log = get_module_logger(__name__)
__all__ = ["DatetimeSpoke"]
@@ -64,6 +65,7 @@
SERVER_POOL = 1
SERVER_WORKING = 2
SERVER_USE = 3
+SERVER_OBJECT = 4
DEFAULT_TZ = "Asia/Shanghai"
@@ -156,97 +158,49 @@ def _new_date_field_box(store):
return (box, combo, suffix_label)
-class NTPconfigDialog(GUIObject, GUIDialogInputCheckHandler):
+class NTPConfigDialog(GUIObject, GUIDialogInputCheckHandler):
builderObjects = ["ntpConfigDialog", "addImage", "serversStore"]
mainWidgetName = "ntpConfigDialog"
uiFile = "spokes/datetime_spoke.glade"
- def __init__(self, data, timezone_module):
+ def __init__(self, data, servers, states):
GUIObject.__init__(self, data)
+ self._servers = servers
+ self._active_server = None
+ self._states = states
# Use GUIDIalogInputCheckHandler to manipulate the sensitivity of the
# add button, and check for valid input in on_entry_activated
add_button = self.builder.get_object("addButton")
GUIDialogInputCheckHandler.__init__(self, add_button)
- #epoch is increased when serversStore is repopulated
- self._epoch = 0
- self._epoch_lock = threading.Lock()
- self._timezone_module = timezone_module
-
- @property
- def working_server(self):
- for row in self._serversStore:
- if row[SERVER_WORKING] == constants.NTP_SERVER_OK and row[SERVER_USE]:
- #server is checked and working
- return row[SERVER_HOSTNAME]
-
- return None
-
- @property
- def pools_servers(self):
- pools = list()
- servers = list()
-
- for used_row in (row for row in self._serversStore if row[SERVER_USE]):
- if used_row[SERVER_POOL]:
- pools.append(used_row[SERVER_HOSTNAME])
- else:
- servers.append(used_row[SERVER_HOSTNAME])
-
- return (pools, servers)
-
- def _render_working(self, column, renderer, model, itr, user_data=None):
- value = model[itr][SERVER_WORKING]
-
- if value == constants.NTP_SERVER_QUERY:
- return "dialog-question"
- elif value == constants.NTP_SERVER_OK:
- return "emblem-default"
- else:
- return "dialog-error"
-
- def initialize(self):
self.window.set_size_request(500, 400)
- workingColumn = self.builder.get_object("workingColumn")
- workingRenderer = self.builder.get_object("workingRenderer")
- override_cell_property(workingColumn, workingRenderer, "icon-name",
- self._render_working)
+ working_column = self.builder.get_object("workingColumn")
+ working_renderer = self.builder.get_object("workingRenderer")
+ override_cell_property(working_column, working_renderer, "icon-name", self._render_working)
self._serverEntry = self.builder.get_object("serverEntry")
self._serversStore = self.builder.get_object("serversStore")
-
self._addButton = self.builder.get_object("addButton")
-
self._poolCheckButton = self.builder.get_object("poolCheckButton")
- # Validate the server entry box
- self._serverCheck = self.add_check(self._serverEntry, self._validateServer)
+ self._serverCheck = self.add_check(self._serverEntry, self._validate_server)
self._serverCheck.update_check_status()
- self._initialize_store_from_config()
-
- def _initialize_store_from_config(self):
- self._serversStore.clear()
+ self._update_timer = Timer()
- kickstart_ntp_servers = self._timezone_module.NTPServers
+ def _render_working(self, column, renderer, model, itr, user_data=None):
+ value = self._serversStore[itr][SERVER_WORKING]
- if kickstart_ntp_servers:
- pools, servers = ntp.internal_to_pools_and_servers(kickstart_ntp_servers)
+ if value == constants.NTP_SERVER_QUERY:
+ return "dialog-question"
+ elif value == constants.NTP_SERVER_OK:
+ return "emblem-default"
else:
- try:
- pools, servers = ntp.get_servers_from_config()
- except ntp.NTPconfigError:
- log.warning("Failed to load NTP servers configuration")
- return
-
- for pool in pools:
- self._add_server(pool, True)
- for server in servers:
- self._add_server(server, False)
+ return "dialog-error"
- def _validateServer(self, inputcheck):
+ def _validate_server(self, inputcheck):
server = self.get_input(inputcheck.input_obj)
# If not set, fail the check to keep the button insensitive, but don't
@@ -261,108 +215,97 @@ def _validateServer(self, inputcheck):
return InputCheck.CHECK_OK
def refresh(self):
- self._initialize_store_from_config()
- self._serverEntry.grab_focus()
+ # Update the store.
+ self._serversStore.clear()
- def refresh_servers_state(self):
- itr = self._serversStore.get_iter_first()
- while itr:
- self._refresh_server_working(itr)
- itr = self._serversStore.iter_next(itr)
+ for server in self._servers:
+ self._add_row(server)
+
+ # Start to update the status.
+ self._update_timer.timeout_sec(1, self._update_rows)
+
+ # Focus on the server entry.
+ self._serverEntry.grab_focus()
def run(self):
self.window.show()
rc = self.window.run()
self.window.hide()
- #OK clicked
+ # OK clicked
if rc == 1:
- new_pools, new_servers = self.pools_servers
+ # Remove servers.
+ for row in self._serversStore:
+ if not row[SERVER_USE]:
+ server = row[SERVER_OBJECT]
+ self._servers.remove(server)
+ # Restart the NTP service.
if conf.system.can_set_time_synchronization:
- ntp.save_servers_to_config(new_pools, new_servers)
+ ntp.save_servers_to_config(self._servers)
util.restart_service(NTP_SERVICE)
- #Cancel clicked, window destroyed...
- else:
- self._epoch_lock.acquire()
- self._epoch += 1
- self._epoch_lock.release()
-
return rc
- def _set_server_ok_nok(self, itr, epoch_started):
- """
- If the server is working, set its data to NTP_SERVER_OK, otherwise set its
- data to NTP_SERVER_NOK.
-
- :param itr: iterator of the $server's row in the self._serversStore
+ def _add_row(self, server):
+ """Add a new row for the given NTP server.
+ :param server: an NTP server
+ :type server: an instance of TimeSourceData
"""
+ itr = self._serversStore.append([
+ "",
+ False,
+ constants.NTP_SERVER_QUERY,
+ True,
+ server
+ ])
+
+ self._refresh_row(itr)
+
+ def _refresh_row(self, itr):
+ """Refresh the given row."""
+ server = self._serversStore[itr][SERVER_OBJECT]
+ self._serversStore.set_value(itr, SERVER_HOSTNAME, server.hostname)
+ self._serversStore.set_value(itr, SERVER_POOL, server.type == TIME_SOURCE_POOL)
+
+ def _update_rows(self):
+ """Periodically update the status of all rows.
+
+ :return: True to repeat, otherwise False
+ """
+ for row in self._serversStore:
+ server = row[SERVER_OBJECT]
- @async_action_nowait
- def set_store_value(arg_tuple):
- """
- We need a function for this, because this way it can be added to
- the MainLoop with thread-safe async_action_nowait (but only with one
- argument).
-
- :param arg_tuple: (store, itr, column, value)
-
- """
-
- (store, itr, column, value) = arg_tuple
- store.set_value(itr, column, value)
-
- orig_hostname = self._serversStore[itr][SERVER_HOSTNAME]
- server_working = ntp.ntp_server_working(self._serversStore[itr][SERVER_HOSTNAME])
-
- #do not let dialog change epoch while we are modifying data
- self._epoch_lock.acquire()
-
- #check if we are in the same epoch as the dialog (and the serversStore)
- #and if the server wasn't changed meanwhile
- if epoch_started == self._epoch:
- actual_hostname = self._serversStore[itr][SERVER_HOSTNAME]
+ if server is self._active_server:
+ continue
- if orig_hostname == actual_hostname:
- if server_working:
- set_store_value((self._serversStore,
- itr, SERVER_WORKING, constants.NTP_SERVER_OK))
- else:
- set_store_value((self._serversStore,
- itr, SERVER_WORKING, constants.NTP_SERVER_NOK))
- self._epoch_lock.release()
+ status = self._states.get_status(server)
+ row[SERVER_WORKING] = status
- @async_action_nowait
- def _refresh_server_working(self, itr):
- """ Runs a new thread with _set_server_ok_nok(itr) as a taget. """
-
- self._serversStore.set_value(itr, SERVER_WORKING, constants.NTP_SERVER_QUERY)
- threadMgr.add(AnacondaThread(prefix=constants.THREAD_NTP_SERVER_CHECK,
- target=self._set_server_ok_nok,
- args=(itr, self._epoch)))
+ return True
- def _add_server(self, server, pool=False):
- """
- Checks if a given server is a valid hostname and if yes, adds it
- to the list of servers.
+ def on_entry_activated(self, entry, *args):
+ # Check that the input check has passed
+ if self._serverCheck.check_status != InputCheck.CHECK_OK:
+ return
- :param server: string containing hostname
+ server = TimeSourceData()
- """
+ if self._poolCheckButton.get_active():
+ server.type = TIME_SOURCE_POOL
+ else:
+ server.type = TIME_SOURCE_SERVER
- itr = self._serversStore.append([server, pool, constants.NTP_SERVER_QUERY, True])
+ server.hostname = entry.get_text()
+ server.options = ["iburst"]
- #do not block UI while starting thread (may take some time)
- self._refresh_server_working(itr)
+ self._servers.append(server)
+ self._states.check_status(server)
+ self._add_row(server)
- def on_entry_activated(self, entry, *args):
- # Check that the input check has passed
- if self._serverCheck.check_status == InputCheck.CHECK_OK:
- self._add_server(entry.get_text(), self._poolCheckButton.get_active())
- entry.set_text("")
- self._poolCheckButton.set_active(False)
+ entry.set_text("")
+ self._poolCheckButton.set_active(False)
def on_add_clicked(self, *args):
self._serverEntry.emit("activate")
@@ -370,16 +313,29 @@ def on_add_clicked(self, *args):
def on_use_server_toggled(self, renderer, path, *args):
itr = self._serversStore.get_iter(path)
old_value = self._serversStore[itr][SERVER_USE]
-
self._serversStore.set_value(itr, SERVER_USE, not old_value)
def on_pool_toggled(self, renderer, path, *args):
itr = self._serversStore.get_iter(path)
- old_value = self._serversStore[itr][SERVER_POOL]
+ server = self._serversStore[itr][SERVER_OBJECT]
+
+ if server.type == TIME_SOURCE_SERVER:
+ server.type = TIME_SOURCE_POOL
+ else:
+ server.type = TIME_SOURCE_SERVER
+
+ self._refresh_row(itr)
+
+ def on_server_editing_started(self, renderer, editable, path):
+ itr = self._serversStore.get_iter(path)
+ self._active_server = self._serversStore[itr][SERVER_OBJECT]
- self._serversStore.set_value(itr, SERVER_POOL, not old_value)
+ def on_server_editing_canceled(self, renderer):
+ self._active_server = None
def on_server_edited(self, renderer, path, new_text, *args):
+ self._active_server = None
+
if not path:
return
@@ -389,14 +345,14 @@ def on_server_edited(self, renderer, path, new_text, *args):
return
itr = self._serversStore.get_iter(path)
+ server = self._serversStore[itr][SERVER_OBJECT]
- if self._serversStore[itr][SERVER_HOSTNAME] == new_text:
+ if server.hostname == new_text:
return
- self._serversStore.set_value(itr, SERVER_HOSTNAME, new_text)
- self._serversStore.set_value(itr, SERVER_WORKING, constants.NTP_SERVER_QUERY)
-
- self._refresh_server_working(itr)
+ server.hostname = new_text
+ self._states.check_status(server)
+ self._refresh_row(itr)
class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
@@ -440,6 +396,9 @@ def __init__(self, *args):
self._timezone_module = TIMEZONE.get_proxy()
self._network_module = NETWORK.get_proxy()
+ self._ntp_servers = []
+ self._ntp_servers_states = NTPServerStatusCache()
+
def initialize(self):
NormalSpoke.initialize(self)
self.initialize_start()
@@ -512,9 +471,6 @@ def initialize(self):
if not conf.system.can_set_system_clock:
self._hide_date_time_setting()
- self._config_dialog = NTPconfigDialog(self.data, self._timezone_module)
- self._config_dialog.initialize()
-
threadMgr.add(AnacondaThread(name=constants.THREAD_DATE_TIME,
target=self._initialize))
@@ -634,12 +590,27 @@ def refresh(self):
self._update_datetime()
+ # update the ntp configuration
+ self._ntp_servers = TimeSourceData.from_structure_list(
+ self._timezone_module.TimeSources
+ )
+
+ if not self._ntp_servers:
+ try:
+ self._ntp_servers = ntp.get_servers_from_config()
+ except ntp.NTPconfigError:
+ log.warning("Failed to load NTP servers configuration")
+
+ self._ntp_servers_states = NTPServerStatusCache()
has_active_network = self._network_module.Connected
+
if not has_active_network:
self._show_no_network_warning()
else:
self.clear_info()
- gtk_call_once(self._config_dialog.refresh_servers_state)
+
+ for server in self._ntp_servers:
+ self._ntp_servers_states.check_status(server)
if conf.system.can_set_time_synchronization:
ntp_working = has_active_network and util.service_running(NTP_SERVICE)
@@ -867,13 +838,10 @@ def _set_combo_selection(self, combo, item):
return False
def _get_combo_selection(self, combo):
- """
- Get the selected item of the combobox.
+ """Get the selected item of the combobox.
:return: selected item or None
-
"""
-
model = combo.get_model()
itr = combo.get_active_iter()
if not itr or not model:
@@ -946,9 +914,7 @@ def on_updown_ampm_clicked(self, *args):
def on_region_changed(self, combo, *args):
"""
:see: on_city_changed
-
"""
-
region = self._get_active_region()
if not region or region == self._old_region:
@@ -974,9 +940,7 @@ def on_city_changed(self, combo, *args):
hit etc.; 'London' chosen in the expanded combobox => update timezone
map and do all necessary actions). Fortunately when entry is being
edited, self._get_active_city returns None.
-
"""
-
timezone = None
region = self._get_active_region()
@@ -1107,8 +1071,17 @@ def _set_date_time_setting_sensitive(self, sensitive):
footer_alignment = self.builder.get_object("footerAlignment")
footer_alignment.set_sensitive(sensitive)
+ def _get_working_server(self):
+ """Get a working NTP server."""
+ for server in self._ntp_servers:
+ status = self._ntp_servers_states.get_status(server)
+ if status == constants.NTP_SERVER_OK:
+ return server
+
+ return None
+
def _show_no_network_warning(self):
- self.set_warning(_("You need to set up networking first if you "\
+ self.set_warning(_("You need to set up networking first if you "
"want to use NTP"))
def _show_no_ntp_server_warning(self):
@@ -1127,13 +1100,13 @@ def on_ntp_switched(self, switch, *args):
return
else:
self.clear_info()
+ working_server = self._get_working_server()
- working_server = self._config_dialog.working_server
if working_server is None:
self._show_no_ntp_server_warning()
else:
- #we need a one-time sync here, because chronyd would not change
- #the time as drastically as we need
+ # We need a one-time sync here, because chronyd would
+ # not change the time as drastically as we need.
ntp.one_time_sync_async(working_server)
ret = util.start_service(NTP_SERVICE)
@@ -1161,16 +1134,24 @@ def on_ntp_switched(self, switch, *args):
self.clear_info()
def on_ntp_config_clicked(self, *args):
- self._config_dialog.refresh()
+ servers = copy.deepcopy(self._ntp_servers)
+ states = self._ntp_servers_states
- with self.main_window.enlightbox(self._config_dialog.window):
- response = self._config_dialog.run()
+ dialog = NTPConfigDialog(self.data, servers, states)
+ dialog.refresh()
+
+ with self.main_window.enlightbox(dialog.window):
+ response = dialog.run()
if response == 1:
- pools, servers = self._config_dialog.pools_servers
- self._timezone_module.SetNTPServers(ntp.pools_servers_to_internal(pools, servers))
+ self._timezone_module.SetTimeSources(
+ TimeSourceData.to_structure_list(servers)
+ )
+
+ self._ntp_servers = servers
+ working_server = self._get_working_server()
- if self._config_dialog.working_server is None:
+ if working_server is None:
self._show_no_ntp_server_warning()
else:
self.clear_info()
--
2.23.0

View File

@ -0,0 +1,284 @@
From 61fe3f12215bceebde71c35dc7ef14dbc17bb4d7 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Fri, 3 Jul 2020 18:29:33 +0200
Subject: [PATCH] Add support for the timesource kickstart command
The Timezone module should handle the timesource kickstart command.
---
anaconda.spec.in | 2 +-
pyanaconda/core/kickstart/commands.py | 4 +-
pyanaconda/kickstart.py | 1 +
pyanaconda/modules/timezone/kickstart.py | 5 ++
pyanaconda/modules/timezone/timezone.py | 69 +++++++++++++---
.../pyanaconda_tests/module_timezone_test.py | 79 ++++++++++++++++++-
6 files changed, 141 insertions(+), 19 deletions(-)
diff --git a/anaconda.spec.in b/anaconda.spec.in
index 83adeb9089..c76181d363 100644
--- a/anaconda.spec.in
+++ b/anaconda.spec.in
@@ -33,7 +33,7 @@ Source0: %{name}-%{version}.tar.bz2
%define libxklavierver 5.4
%define mehver 0.23-1
%define nmver 1.0
-%define pykickstartver 3.25-1
+%define pykickstartver 3.27-1
%define pypartedver 2.5-2
%define rpmver 4.10.0
%define simplelinever 1.1-1
diff --git a/pyanaconda/core/kickstart/commands.py b/pyanaconda/core/kickstart/commands.py
index 590027dd33..3c3eed03e2 100644
--- a/pyanaconda/core/kickstart/commands.py
+++ b/pyanaconda/core/kickstart/commands.py
@@ -76,7 +76,8 @@
from pykickstart.commands.sshpw import F24_SshPw as SshPw
from pykickstart.commands.sshkey import F22_SshKey as SshKey
from pykickstart.commands.syspurpose import RHEL8_Syspurpose as Syspurpose
-from pykickstart.commands.timezone import F32_Timezone as Timezone
+from pykickstart.commands.timezone import F33_Timezone as Timezone
+from pykickstart.commands.timesource import F33_Timesource as Timesource
from pykickstart.commands.updates import F7_Updates as Updates
from pykickstart.commands.url import F30_Url as Url
from pykickstart.commands.user import F24_User as User
@@ -107,6 +108,7 @@
from pykickstart.commands.snapshot import F26_SnapshotData as SnapshotData
from pykickstart.commands.sshpw import F24_SshPwData as SshPwData
from pykickstart.commands.sshkey import F22_SshKeyData as SshKeyData
+from pykickstart.commands.timesource import F33_TimesourceData as TimesourceData
from pykickstart.commands.user import F19_UserData as UserData
from pykickstart.commands.volgroup import F21_VolGroupData as VolGroupData
from pykickstart.commands.zfcp import F14_ZFCPData as ZFCPData
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index d2fcaab44d..946da8bc95 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -372,6 +372,7 @@ def finalize(self):
"sshkey" : UselessCommand,
"skipx": UselessCommand,
"snapshot": UselessCommand,
+ "timesource": UselessCommand,
"timezone": UselessCommand,
"url": UselessCommand,
"user": UselessCommand,
diff --git a/pyanaconda/modules/timezone/kickstart.py b/pyanaconda/modules/timezone/kickstart.py
index 7115322677..b94e4129c3 100644
--- a/pyanaconda/modules/timezone/kickstart.py
+++ b/pyanaconda/modules/timezone/kickstart.py
@@ -24,4 +24,9 @@ class TimezoneKickstartSpecification(KickstartSpecification):
commands = {
"timezone": COMMANDS.Timezone,
+ "timesource": COMMANDS.Timesource,
+ }
+
+ commands_data = {
+ "TimesourceData": COMMANDS.TimesourceData,
}
diff --git a/pyanaconda/modules/timezone/timezone.py b/pyanaconda/modules/timezone/timezone.py
index ff89d1ea77..b7fd5b6430 100644
--- a/pyanaconda/modules/timezone/timezone.py
+++ b/pyanaconda/modules/timezone/timezone.py
@@ -17,8 +17,11 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
+from pykickstart.errors import KickstartParseError
+
+from pyanaconda.core.i18n import _
from pyanaconda.core.configuration.anaconda import conf
-from pyanaconda.core.constants import TIME_SOURCE_SERVER
+from pyanaconda.core.constants import TIME_SOURCE_SERVER, TIME_SOURCE_POOL
from pyanaconda.core.dbus import DBus
from pyanaconda.core.signal import Signal
from pyanaconda.modules.common.base import KickstartService
@@ -73,29 +76,69 @@ def process_kickstart(self, data):
self.set_is_utc(data.timezone.isUtc)
self.set_ntp_enabled(not data.timezone.nontp)
- servers = []
+ sources = []
for hostname in data.timezone.ntpservers:
- server = TimeSourceData()
- server.type = TIME_SOURCE_SERVER
- server.hostname = hostname
- server.options = ["iburst"]
- servers.append(server)
-
- self.set_time_sources(servers)
+ source = TimeSourceData()
+ source.type = TIME_SOURCE_SERVER
+ source.hostname = hostname
+ source.options = ["iburst"]
+ sources.append(source)
+
+ for source_data in data.timesource.dataList():
+ if source_data.ntp_disable:
+ self.set_ntp_enabled(False)
+ continue
+
+ source = TimeSourceData()
+ source.options = ["iburst"]
+
+ if source_data.ntp_server:
+ source.type = TIME_SOURCE_SERVER
+ source.hostname = source_data.ntp_server
+ elif source_data.ntp_pool:
+ source.type = TIME_SOURCE_POOL
+ source.hostname = source_data.ntp_pool
+ else:
+ KickstartParseError(
+ _("Invalid time source."),
+ lineno=source_data.lineno
+ )
+
+ if source_data.nts:
+ source.options.append("nts")
+
+ sources.append(source)
+
+ self.set_time_sources(sources)
def setup_kickstart(self, data):
"""Set up the kickstart data."""
data.timezone.timezone = self.timezone
data.timezone.isUtc = self.is_utc
- data.timezone.nontp = not self.ntp_enabled
+ source_data_list = data.timesource.dataList()
if not self.ntp_enabled:
+ source_data = data.TimesourceData()
+ source_data.ntp_disable = True
+ source_data_list.append(source_data)
return
- data.timezone.ntpservers = [
- server.hostname for server in self.time_sources
- ]
+ for source in self.time_sources:
+ source_data = data.TimesourceData()
+
+ if source.type == TIME_SOURCE_SERVER:
+ source_data.ntp_server = source.hostname
+ elif source.type == TIME_SOURCE_POOL:
+ source_data.ntp_pool = source.hostname
+ else:
+ log.warning("Skipping %s.", source)
+ continue
+
+ if "nts" in source.options:
+ source_data.nts = True
+
+ source_data_list.append(source_data)
@property
def timezone(self):
diff --git a/tests/nosetests/pyanaconda_tests/module_timezone_test.py b/tests/nosetests/pyanaconda_tests/module_timezone_test.py
index bb751d6f4b..dab857e034 100644
--- a/tests/nosetests/pyanaconda_tests/module_timezone_test.py
+++ b/tests/nosetests/pyanaconda_tests/module_timezone_test.py
@@ -65,7 +65,7 @@ def _check_dbus_property(self, *args, **kwargs):
def kickstart_properties_test(self):
"""Test kickstart properties."""
- self.assertEqual(self.timezone_interface.KickstartCommands, ["timezone"])
+ self.assertEqual(self.timezone_interface.KickstartCommands, ["timezone", "timesource"])
self.assertEqual(self.timezone_interface.KickstartSections, [])
self.assertEqual(self.timezone_interface.KickstartAddons, [])
self.callback.assert_not_called()
@@ -143,19 +143,90 @@ def kickstart2_test(self):
timezone --utc --nontp Europe/Prague
"""
ks_out = """
+ timesource --ntp-disable
# System timezone
- timezone Europe/Prague --utc --nontp
+ timezone Europe/Prague --utc
"""
self._test_kickstart(ks_in, ks_out)
def kickstart3_test(self):
- """Test the timezone command with ntp servers.."""
+ """Test the timezone command with ntp servers."""
ks_in = """
timezone --ntpservers ntp.cesnet.cz Europe/Prague
"""
ks_out = """
+ timesource --ntp-server=ntp.cesnet.cz
# System timezone
- timezone Europe/Prague --ntpservers=ntp.cesnet.cz
+ timezone Europe/Prague
+ """
+ self._test_kickstart(ks_in, ks_out)
+
+ def kickstart_timesource_ntp_disabled_test(self):
+ """Test the timesource command with ntp disabled."""
+ ks_in = """
+ timesource --ntp-disable
+ """
+ ks_out = """
+ timesource --ntp-disable
+ """
+ self._test_kickstart(ks_in, ks_out)
+
+ def kickstart_timesource_ntp_server_test(self):
+ """Test the timesource command with ntp servers."""
+ ks_in = """
+ timesource --ntp-server ntp.cesnet.cz
+ """
+ ks_out = """
+ timesource --ntp-server=ntp.cesnet.cz
+ """
+ self._test_kickstart(ks_in, ks_out)
+
+ def kickstart_timesource_ntp_pool_test(self):
+ """Test the timesource command with ntp pools."""
+ ks_in = """
+ timesource --ntp-pool ntp.cesnet.cz
+ """
+ ks_out = """
+ timesource --ntp-pool=ntp.cesnet.cz
+ """
+ self._test_kickstart(ks_in, ks_out)
+
+ def kickstart_timesource_nts_test(self):
+ """Test the timesource command with the nts option."""
+ ks_in = """
+ timesource --ntp-pool ntp.cesnet.cz --nts
+ """
+ ks_out = """
+ timesource --ntp-pool=ntp.cesnet.cz --nts
+ """
+ self._test_kickstart(ks_in, ks_out)
+
+ def kickstart_timesource_all_test(self):
+ """Test the timesource commands."""
+ ks_in = """
+ timesource --ntp-server ntp.cesnet.cz
+ timesource --ntp-pool 0.fedora.pool.ntp.org
+ """
+ ks_out = """
+ timesource --ntp-server=ntp.cesnet.cz
+ timesource --ntp-pool=0.fedora.pool.ntp.org
+ """
+ self._test_kickstart(ks_in, ks_out)
+
+ def kickstart_timezone_timesource_test(self):
+ """Test the combination of timezone and timesource commands."""
+ ks_in = """
+ timezone --ntpservers ntp.cesnet.cz,0.fedora.pool.ntp.org Europe/Prague
+ timesource --ntp-server ntp.cesnet.cz --nts
+ timesource --ntp-pool 0.fedora.pool.ntp.org
+ """
+ ks_out = """
+ timesource --ntp-server=ntp.cesnet.cz
+ timesource --ntp-server=0.fedora.pool.ntp.org
+ timesource --ntp-server=ntp.cesnet.cz --nts
+ timesource --ntp-pool=0.fedora.pool.ntp.org
+ # System timezone
+ timezone Europe/Prague
"""
self._test_kickstart(ks_in, ks_out)
--
2.23.0