!69 improve ntp servers to fix unkown error
From: @eaglegai Reviewed-by: @orange-snn Signed-off-by: @orange-snn
This commit is contained in:
commit
6cd7ea0594
@ -1,7 +1,7 @@
|
||||
%define _empty_manifest_terminate_build 0
|
||||
Name: anaconda
|
||||
Version: 33.19
|
||||
Release: 13
|
||||
Release: 14
|
||||
Summary: Graphical system installer
|
||||
License: GPLv2+ and MIT
|
||||
URL: http://fedoraproject.org/wiki/Anaconda
|
||||
@ -41,6 +41,16 @@ Patch9023: bugfix-add-dnf-transaction-timeout.patch
|
||||
Patch6007: fix-0-storage-devices-selected.patch
|
||||
Patch6008: fix-remove-unknow-partition-is-sda-failed.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 dnfver 3.6.0
|
||||
@ -57,7 +67,7 @@ Patch6009: use-modinfo-to-check-ko-before-modprobe.patch
|
||||
%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
|
||||
@ -254,6 +264,12 @@ update-desktop-database &> /dev/null || :
|
||||
%{_datadir}/gtk-doc
|
||||
|
||||
%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
|
||||
- Type:bugfix
|
||||
- ID:NA
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user