anaconda/anaconda-40.22.3.13/pyanaconda/modules/timezone/timezone.py
2024-11-14 21:39:56 -08:00

297 lines
10 KiB
Python

#
# Kickstart module for date and time settings.
#
# Copyright (C) 2018 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import datetime
from pykickstart.errors import KickstartParseError
from pyanaconda.core.i18n import _
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import TIME_SOURCE_SERVER, TIME_SOURCE_POOL, \
TIMEZONE_PRIORITY_USER, TIMEZONE_PRIORITY_DEFAULT, TIMEZONE_PRIORITY_KICKSTART
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, GeolocationData
from pyanaconda.timezone import NTP_PACKAGE
from pyanaconda.modules.common.containers import TaskContainer
from pyanaconda.modules.common.structures.requirement import Requirement
from pyanaconda.modules.timezone.initialization import GeolocationTask
from pyanaconda.modules.timezone.installation import ConfigureHardwareClockTask, \
ConfigureNTPTask, ConfigureTimezoneTask
from pyanaconda.modules.timezone.kickstart import TimezoneKickstartSpecification
from pyanaconda.modules.timezone.timezone_interface import TimezoneInterface
from pyanaconda.timezone import get_timezone, set_system_date_time, get_all_regions_and_timezones
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
class TimezoneService(KickstartService):
"""The Timezone service."""
def __init__(self):
super().__init__()
self.timezone_changed = Signal()
self._timezone = "America/New_York"
self._priority = TIMEZONE_PRIORITY_DEFAULT
self.geolocation_result_changed = Signal()
self._geoloc_result = GeolocationData()
self.is_utc_changed = Signal()
self._is_utc = False
self.ntp_enabled_changed = Signal()
self._ntp_enabled = True
self.time_sources_changed = Signal()
self._time_sources = []
def publish(self):
"""Publish the module."""
TaskContainer.set_namespace(TIMEZONE.namespace)
DBus.publish_object(TIMEZONE.object_path, TimezoneInterface(self))
DBus.register_service(TIMEZONE.service_name)
@property
def kickstart_specification(self):
"""Return the kickstart specification."""
return TimezoneKickstartSpecification
def process_kickstart(self, data):
"""Process the kickstart data."""
self.set_timezone_with_priority(data.timezone.timezone, TIMEZONE_PRIORITY_KICKSTART)
self.set_is_utc(data.timezone.isUtc)
sources = []
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:
raise 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
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
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):
"""Return the timezone."""
return self._timezone
def set_timezone(self, timezone):
"""Set the timezone."""
self.set_timezone_with_priority(timezone, TIMEZONE_PRIORITY_USER)
def set_timezone_with_priority(self, timezone, priority):
"""Set the timezone with priority.
Sets the timezone only if the priority is higher than the previous priority.
"""
if priority < self._priority:
log.debug("Timezone did not change %s -> %s due to too low priority: %d > %d.",
self._timezone, timezone, self._priority, priority)
return
self._timezone = timezone
self._priority = priority
self.timezone_changed.emit()
log.debug("Timezone is set to %s.", timezone)
def get_all_valid_timezones(self):
"""Get all valid timezones.
:return: list of valid timezones
:rtype: list of str
"""
timezone_dict = get_all_regions_and_timezones()
# convert to a dict of lists for easier transfer over DBus
# - change the nested sets to lists
new_timezone_dict = {}
for region in timezone_dict:
new_timezone_dict[region] = list(timezone_dict[region])
return new_timezone_dict
@property
def is_utc(self):
"""Is the hardware clock set to UTC?"""
return self._is_utc
def set_is_utc(self, is_utc):
"""Set if the hardware clock is set to UTC."""
self._is_utc = is_utc
self.is_utc_changed.emit()
log.debug("UTC is set to %s.", is_utc)
@property
def ntp_enabled(self):
"""Enable automatic starting of NTP service."""
return self._ntp_enabled
def set_ntp_enabled(self, ntp_enabled):
"""Enable or disable automatic starting of NTP service."""
self._ntp_enabled = ntp_enabled
self.ntp_enabled_changed.emit()
log.debug("NTP is set to %s.", ntp_enabled)
@property
def time_sources(self):
"""Return a list of time sources."""
return self._time_sources
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.
:return: a list of requirements
"""
requirements = []
# Add ntp service requirements.
if self._ntp_enabled:
requirements.append(
Requirement.for_package(NTP_PACKAGE, reason="Needed to run NTP service.")
)
return requirements
def install_with_tasks(self):
"""Return the installation tasks of this module.
:return: list of installation tasks
"""
return [
ConfigureHardwareClockTask(
is_utc=self.is_utc
),
ConfigureTimezoneTask(
sysroot=conf.target.system_root,
timezone=self.timezone,
is_utc=self.is_utc
),
ConfigureNTPTask(
sysroot=conf.target.system_root,
ntp_enabled=self.ntp_enabled,
ntp_servers=self.time_sources
)
]
def _set_geolocation_result(self, result):
"""Set geolocation result when the task finished."""
self._geoloc_result = result
self.geolocation_result_changed.emit()
log.debug("Geolocation result is set, valid=%s", not self._geoloc_result.is_empty())
def start_geolocation_with_task(self):
"""Start geolocation.
:return: task to run for geolocation
:rtype: Task
"""
task = GeolocationTask()
task.succeeded_signal.connect(lambda: self._set_geolocation_result(task.get_result()))
return task
@property
def geolocation_result(self):
"""Get geolocation result.
:return GeolocationData: result of the lookup, empty if not ready yet
"""
return self._geoloc_result
def get_system_date_time(self):
"""Get system time as a ISO 8601 formatted string.
:return: system time as ISO 8601 formatted string
:rtype: str
"""
# convert to the expected tzinfo format via get_timezone()
return datetime.datetime.now(get_timezone(self._timezone)).isoformat()
def set_system_date_time(self, date_time_spec):
"""Set system time based on a ISO 8601 formatted string.
:param str date_time_spec: ISO 8601 time specification to use
"""
log.debug("Setting system time to: %s, with timezone: %s", date_time_spec, self._timezone)
# first convert the ISO 8601 time string to a Python date object
date = datetime.datetime.fromisoformat(date_time_spec)
# set the date to the system
set_system_date_time(
year=date.year,
month=date.month,
day=date.day,
hour=date.hour,
minute=date.minute,
tz=self._timezone
)