Current version (0.5.0) of python-augeas is missing copy() method. Use
dkupka/python-augeas copr repo before new version it's build and
available in the official repos.
--
David Kupka
From 5f9419f9a0e56119fc80f4cf1f7c2ffde15268a0 Mon Sep 17 00:00:00 2001
From: David Kupka <[email protected]>
Date: Thu, 10 Mar 2016 22:33:15 +0100
Subject: [PATCH 1/4] augeas: add wrapper around python binding
python-augeas binding is mostly 1-1 mapping of C functions. Put a layer of
list- and dict-like classes to allow a slightly more comfortable work.
https://fedorahosted.org/freeipa/ticket/4920
---
freeipa.spec.in | 1 +
ipaplatform/base/paths.py | 1 +
ipapython/ipaaugeas.py | 269 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 271 insertions(+)
create mode 100644 ipapython/ipaaugeas.py
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 9e277020d70215e052ab6c905b1c6a29ae6cdd4d..a7cc91cec3aa660bc76b7873723d5f3241cdf1d9 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -171,6 +171,7 @@ Requires: systemd-python
Requires: %{etc_systemd_dir}
Requires: gzip
Requires: oddjob
+Requires: python-augeas > 0.5.0
Provides: %{alt_name}-server = %{version}
Conflicts: %{alt_name}-server
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index bdff4f3934f3250bdfef3f913631b98d55d759b6..c0200122246e83edab2905ecf6c353b928ce88cf 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -345,5 +345,6 @@ class BasePathNamespace(object):
IPA_CUSTODIA_SOCKET = '/run/httpd/ipa-custodia.sock'
IPA_CUSTODIA_AUDIT_LOG = '/var/log/ipa-custodia.audit.log'
IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab'
+ IPA_CUSTOM_AUGEAS_LENSES_DIR = '/usr/share/augeas/lenses/ipa/'
path_namespace = BasePathNamespace
diff --git a/ipapython/ipaaugeas.py b/ipapython/ipaaugeas.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e67ada447467c08e73e55c1576ac33233e4b64f
--- /dev/null
+++ b/ipapython/ipaaugeas.py
@@ -0,0 +1,269 @@
+#!/usr/bin/python
+
+import os
+import shutil
+import tempfile
+import collections
+from augeas import Augeas
+from ipaplatform.paths import paths
+from ipapython.ipa_log_manager import root_logger
+
+
+class _tmp_path(object):
+ """Create a unique temporary path.
+
+ Uniquenes depends on tempfile.mkdtemp ability to create unique directory
+ path.
+ The path is cleared when the object is deleted.
+ """
+ def __init__(self, aug, label):
+ self._aug = aug
+ self._tmp = tempfile.mkdtemp()
+ if label:
+ self.path = os.path.join(self._tmp, label)
+ else:
+ self.path = self._tmp
+
+ def __del__(self):
+ self._aug.remove(self._tmp)
+ shutil.rmtree(self._tmp)
+
+
+class aug_obj(object):
+ """python-augeas higher-level wrapper.
+ Load single augeas lens and related configuration file.
+ Allows working with augeas tree like dict(key:list(dict(...), ...), ...))
+
+ Can be used in with statement in the same way as file does.
+ """
+
+ _instance = None
+ _loaded = False
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(aug_obj, cls).__new__(cls, *args, **kwargs)
+ return cls._instance
+
+ def __init__(self):
+ if not hasattr(self, '_aug'):
+ self._aug = Augeas(loadpath=paths.IPA_CUSTOM_AUGEAS_LENSES_DIR,
+ flags=Augeas.NO_MODL_AUTOLOAD | Augeas.NO_LOAD)
+ self.lenses = []
+ self.tree = {}
+
+ def add_lens(self, lens, conf_files):
+ if self._loaded:
+ raise RuntimeError('Augeas lenses was loaded. Could not add more'
+ 'lenses.')
+ if lens in self.lenses:
+ raise RuntimeError('Lens %s already added.' % lens)
+ self.lenses.append(lens)
+ load_path = '/augeas/load/{0}'.format(lens)
+
+ self._aug.set(os.path.join(load_path, 'lens'), lens)
+ for conf_file in conf_files:
+ self._aug.set(os.path.join(load_path, 'incl[0]'), conf_file)
+ self.tree['old'] = self.tree.get(conf_file, None)
+ self.tree[conf_file] = aug_node(self._aug,
+ os.path.join('/files',
+ conf_file[1:]))
+
+ def load(self):
+ if self._loaded:
+ raise RuntimeError('Augeas lenses was loaded. Could not add more'
+ 'lenses.')
+ self._aug.load()
+ errors = self._aug.match(os.path.join('//error'))
+ if errors:
+ err_msg = ""
+ for error in errors:
+ err_msg += ("{}: {}".format(error, self._aug.get(error)))
+ raise RuntimeError(err_msg)
+ self._loaded = True
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.flush()
+ self.close()
+
+ def flush(self):
+ try:
+ self._aug.save()
+ except IOError as e:
+ for err_path in self._aug.match('//error'):
+ root_logger.error('{}: {}'.format(err_path, self._aug.get(
+ os.path.join(err_path, 'message'))))
+
+ def close(self):
+ self._aug.close()
+ del self._aug
+ self._instance = None
+ self._loaded = False
+
+
+class aug_node(collections.MutableMapping):
+ """ Single augeas node.
+ Can be handled as python dict().
+ """
+ def __init__(self, aug, path):
+ self._aug = aug
+ if path and os.path.isabs(path):
+ self._path = path
+ else:
+ self._tmp = _tmp_path(aug, path)
+ self._path = self._tmp.path
+
+ @property
+ def value(self):
+ return self._aug.get(self._path)
+
+ @value.setter
+ def value(self, value):
+ if value is None:
+ self._aug.set(self._path, '')
+ else:
+ self._aug.set(self._path, value)
+
+ def __getitem__(self, key):
+ return aug_list(self._aug, os.path.join(self._path, key))
+
+ def __setitem__(self, key, node):
+ target = os.path.join(self._path, key)
+ end = '{0}[0]'.format(os.path.join(self._path, key))
+ if self._aug.match(target):
+ self._aug.remove(target)
+ target_list = aug_list(self._aug, target)
+ for src_node in aug_list(node._aug, node._path):
+ target_list.append(src_node)
+
+ def __delitem__(self, key):
+ target = os.path.join(self._path, key)
+ self._aug.remove(target)
+
+ def __len__(self):
+ return len(self._aug.match('{0}/*'.format(self._path)))
+
+ def __iter__(self):
+ for key in self._aug.match('{0}/*'.format(self._path)):
+ yield self._aug.label(key)
+ raise StopIteration()
+
+ def __bool__(self):
+ return (bool(len(self)) or bool(self.value))
+
+ __nonzero__ = __bool__ # for Python 2
+
+
+class aug_list(collections.MutableSequence):
+ """Augeas NODESET.
+ Can be handled as a list().
+ """
+ def __init__(self, aug, path):
+ self._aug = aug
+ if path and os.path.isabs(path):
+ self._path = path
+ else:
+ self._tmp = _tmp_path(aug, path)
+ self._path = self._tmp.path
+
+ def _idx_pos(self, index):
+ if index < 0:
+ return len(self)+index
+ return index
+
+ def _idx_aug(self, index):
+ return self._idx_pos(index)+1
+
+ def _idx_to_path(self, index):
+ target = "{0}[{1}]".format(self._path, self._idx_aug(index))
+ if not self._aug.match(target):
+ raise IndexError()
+ return target
+
+ def __getitem__(self, index):
+ if isinstance(index, int):
+ return aug_node(self._aug, self._idx_to_path(index))
+ elif isinstance(index, slice):
+ label = self._path.split('/')[-1]
+ res = aug_list(self._aug, label)
+ for idx in range(*index.indices(len(self))):
+ res.append(self[idx])
+ return res
+ else:
+ raise TypeError()
+
+ def __setitem__(self, index, node):
+ if isinstance(index, int):
+ if not isinstance(node, aug_node):
+ raise TypeError()
+ self._aug.copy(node._path, self._idx_to_path(index))
+ elif isinstance(index, slice):
+ range_target = list(range(*index.indices(len(self))))
+
+ if len(range_target) != len(node):
+ if index.step not in [None, 1]:
+ raise ValueError()
+
+ replace = range_target[:len(node)]
+ delete = create = []
+ if len(range_target) > len(node):
+ delete = range_target[len(node):]
+ elif len(range_target) < len(node):
+ try:
+ create_start = range_target[-1]+1
+ except IndexError:
+ create_start = self._idx_pos(index.start)
+ create_stop = create_start+len(node)-len(replace)
+ create = list(range(create_start, create_stop))
+
+ for i in range(len(replace)):
+ self[replace[i]] = node[i]
+ for i in reversed(delete):
+ del(self[i])
+ for i in range(len(create)):
+ self.insert(create[i], node[i+len(replace)])
+
+ def __delitem__(self, index):
+ if isinstance(index, int):
+ self._aug.remove(self._idx_to_path(index))
+ elif isinstance(index, slice):
+ for idx in sorted(range(*index.indices(len(self))), reverse=True):
+ del(self[idx])
+ else:
+ raise TypeError()
+
+ def __len__(self):
+ return len(self._aug.match(self._path))
+
+ def __bool__(self):
+ return bool(len(self))
+
+ __nonzero__ = __bool__ # for Python 2
+
+ def insert(self, index, node):
+ if len(self):
+ try:
+ target = self._idx_to_path(index)
+ except IndexError():
+ target = '{0}[0]'.format(self._path)
+
+ self._aug.insert(target, self._aug.label(target))
+ else:
+ target = self._path
+ self._aug.set(target, None)
+
+ if node:
+ self._aug.copy(node._path, target)
+
+ def append(self, node):
+ target = '{0}[0]'.format(self._path)
+ self._aug.copy(node._path, target)
+
+ def pop(self, index=-1):
+ label = self._aug.label(self[index]._path)
+ dst = aug_node(self._aug, label)
+ self._aug.move(self[index]._path, dst._path)
+ return dst
--
2.5.0
From 37245ef90dc8a491782a49afa25c3e6f9571dd0b Mon Sep 17 00:00:00 2001
From: David Kupka <[email protected]>
Date: Thu, 10 Mar 2016 22:36:06 +0100
Subject: [PATCH 2/4] ntp: Add module for NTP configuration
Use augeas to modify NTP services configuration. Also only and needed
directives instead of rewriting whole configuration file.
https://fedorahosted.org/freeipa/ticket/4920
https://fedorahosted.org/freeipa/ticket/4001
---
ipaplatform/base/services.py | 2 +
ipaplatform/fedora/paths.py | 4 +-
ipaplatform/fedora/services.py | 2 +
ipaplatform/redhat/services.py | 2 +
ipaplatform/rhel/services.py | 2 +
ipapython/ntp.py | 390 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 401 insertions(+), 1 deletion(-)
create mode 100644 ipapython/ntp.py
diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py
index 2ec84cdb21607cb51df6ad5fcd2ae515898bee44..0df73ea4e86e7a04683f85cc494ef30bbc71680f 100644
--- a/ipaplatform/base/services.py
+++ b/ipaplatform/base/services.py
@@ -505,3 +505,5 @@ knownservices = None
# System may support more time&date services. FreeIPA supports ntpd only, other
# services will be disabled during IPA installation
timedate_services = ['ntpd', 'chronyd']
+ntp_service = 'ntpd'
+conflicting_ntp_services = ['chronyd', 'systemd-timesyncd']
diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py
index 49a904f2f271097d75be235eb30d614dc1bb41ac..24a37820a93a250f0e76c22bd0b42761d7f07a74 100644
--- a/ipaplatform/fedora/paths.py
+++ b/ipaplatform/fedora/paths.py
@@ -27,7 +27,9 @@ from ipaplatform.redhat.paths import RedHatPathNamespace
class FedoraPathNamespace(RedHatPathNamespace):
- pass
+ CHRONYD = "/usr/sbin/chronyd"
+ CHRONY_CONF = "/etc/chrony.conf"
+ CHRONYD_PID_FILE = '/var/run/chronyd.pid'
paths = FedoraPathNamespace()
diff --git a/ipaplatform/fedora/services.py b/ipaplatform/fedora/services.py
index 5b1dfc824b042b5d162fb23d9f6621b484099745..4bddc2b9d5b39f81c1c95b536df380a17421cd02 100644
--- a/ipaplatform/fedora/services.py
+++ b/ipaplatform/fedora/services.py
@@ -57,5 +57,7 @@ class FedoraServices(redhat_services.RedHatServices):
# Objects below are expected to be exported by platform module
timedate_services = redhat_services.timedate_services
+ntp_service = 'chronyd'
+conflicting_ntp_services = ['ntpd', 'systemd-timesyncd']
service = fedora_service_class_factory
knownservices = FedoraServices()
diff --git a/ipaplatform/redhat/services.py b/ipaplatform/redhat/services.py
index 3c18dbc3c1274ef3852abef5f054b4e37e6b32fa..7315da4f3b99458cbb91df8443d1bde9b9ae0eba 100644
--- a/ipaplatform/redhat/services.py
+++ b/ipaplatform/redhat/services.py
@@ -281,5 +281,7 @@ class RedHatServices(base_services.KnownServices):
# Objects below are expected to be exported by platform module
timedate_services = base_services.timedate_services
+ntp_service = base_services.ntp_service
+conflicting_ntp_services = base_services.conflicting_ntp_services
service = redhat_service_class_factory
knownservices = RedHatServices()
diff --git a/ipaplatform/rhel/services.py b/ipaplatform/rhel/services.py
index 980c84a9653187bdd26e26efe9d120a5637c4595..c36ea5b43b4ad56111ce41d8c6a9c6ce60a1f36a 100644
--- a/ipaplatform/rhel/services.py
+++ b/ipaplatform/rhel/services.py
@@ -57,5 +57,7 @@ class RHELServices(redhat_services.RedHatServices):
# Objects below are expected to be exported by platform module
timedate_services = redhat_services.timedate_services
+ntp_service = redhat_services.ntp_service
+conflicting_ntp_services = redhat_services.conflicting_ntp_services
service = rhel_service_class_factory
knownservices = RHELServices()
diff --git a/ipapython/ntp.py b/ipapython/ntp.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f1e87dbf74dc6ca336550334fcdc3ff70717e99
--- /dev/null
+++ b/ipapython/ntp.py
@@ -0,0 +1,390 @@
+# Authors: Karl MacMillan <[email protected]>
+# David Kupka <[email protected]>
+#
+# Copyright (C) 2007 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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/>.
+#
+
+import os
+from ipapython import ipautil
+from ipapython.ipa_log_manager import root_logger
+from ipaplatform import services
+from ipaplatform.paths import paths
+from ipapython import ipaaugeas
+import ipautil
+
+
+class NTPConfigurationError(Exception):
+ pass
+
+
+class NTPConflictingService(NTPConfigurationError):
+ def __init__(self, message='', conflicting_service=None):
+ super(NTPConflictingService, self).__init__(self, message)
+ self.conflicting_service = conflicting_service
+
+ def __str__(self):
+ return ("Conflicting Time&Date synchroniztion service '%s' is "
+ "currently enabled and/or running on the system."
+ % self.conflicting_service)
+
+
+class NTPService(object):
+ """
+ Base Time&Date service.
+ """
+ def __init__(self):
+ self.service_name = ''
+ self.conf_file = ''
+ self.lens = ''
+ self.source_keywords = ['server']
+ self.server_options = {}
+ self.global_options = {}
+
+ def _store_service_state(self, sstore=None, fstore=None):
+ """
+ Backup state of service before first configuration.
+
+ Do not override state and file when there are already there.
+ We relay on a fact that before first installation state and
+ file store are clean and therefore presence such entries
+ means that service was configured.
+ """
+ service = services.service(self.service_name)
+ if sstore:
+ if sstore.get_state('ntp', 'enabled') is None:
+ sstore.backup_state('ntp', 'enabled', service.is_enabled())
+
+ if fstore:
+ if not fstore.has_file(self.conf_file):
+ fstore.backup_file(self.conf_file)
+
+ def configure_client(self, ntp_servers=[], sstore=None, fstore=None):
+ service = services.service(self.service_name)
+
+ self._store_service_state(sstore, fstore)
+ if sstore:
+ sstore.backup_state('ntp', "enabled", service.is_enabled())
+
+ if fstore:
+ fstore.backup_file(self.conf_file)
+
+ with ipaaugeas.aug_obj() as aug:
+ try:
+ aug.add_lens(self.lens, [self.conf_file])
+ aug.load()
+ except RuntimeError as e:
+ raise NTPConfigurationError(e)
+
+ timesync = aug.tree[self.conf_file]
+
+ # search for time source in configuration
+ for source_kw in self.source_keywords:
+ sources = timesync[source_kw]
+ if sources:
+ break
+ else:
+ # check if source server is specified
+ if not ntp_servers:
+ raise NTPConfigurationError(
+ "No time source specified in configuration nor "
+ "provided")
+
+ for ntp_server in ntp_servers:
+ new_server = ipaaugeas.aug_node(aug._aug, 'server')
+ new_server.value = ntp_server
+ for o in self.server_options:
+ option = ipaaugeas.aug_node(aug._aug, o)
+ option.value = self.server_options[o]
+ new_server[o] = option
+ del option # delete before augeas context is closed
+ timesync['server'].insert(0, new_server)
+ del new_server # delete before augeas context is closed
+
+ for server in timesync['server']:
+ for o in self.server_options:
+ if not server[o]:
+ option = ipaaugeas.aug_node(aug._aug, o)
+ option.value = self.server_options[o]
+ server[o] = option
+ del option # delete before augeas context is closed
+
+ for o in self.global_options:
+ if not timesync[o]:
+ option = ipaaugeas.aug_node(aug._aug, o)
+ option.value = self.server_options[o]
+ timesync[o] = option
+ del option # delete before augeas context is closed
+
+ service.enable()
+ service.restart()
+
+ def synconce(self, servers):
+ """
+ Perform one-shot time synchronization with provided server.
+
+ !! This method should be implemented by all inherited classes. !!
+ """
+ raise NotImplementedError()
+
+ def check_services(self):
+ """
+ System may contain conflicting services used for time&date
+ synchronization. As IPA server/client supports only ntpd, make sure
+ that other services are not enabled to prevent conflicts. For example
+ when both chronyd and ntpd are enabled, systemd would always start only
+ chronyd to manage system time&date which would make IPA configuration
+ of ntpd ineffective.
+
+ Reference links:
+ https://fedorahosted.org/freeipa/ticket/2974
+ http://fedoraproject.org/wiki/Features/ChronyDefaultNTP
+ """
+ for service in services.conflicting_ntp_services:
+ # Make sure that the service is not enabled
+ instance = services.service(service)
+ if instance.is_enabled() or instance.is_running():
+ raise NTPConflictingService(
+ conflicting_service=instance.service_name)
+
+ def force(self, sstore):
+ """
+ Force ntpd configuration and disable and stop any other conflicting
+ time&date service
+ """
+ for service in services.conflicting_ntp_services:
+ instance = services.service(service)
+ enabled = instance.is_enabled()
+ running = instance.is_running()
+
+ if enabled or running:
+ sstore.backup_state(instance.service_name, 'enabled', enabled)
+ sstore.backup_state(instance.service_name, 'running', running)
+
+ if running:
+ instance.stop()
+
+ if enabled:
+ instance.disable()
+
+ def restore(self, sstore, fstore):
+ ntp = services.service(self.service_name)
+ ntp_enabled = sstore.restore_state('ntp', 'enabled')
+ restored = False
+
+ # Restore might fail due to file missing in backup
+ # the reason for it might be that freeipa-client was updated
+ # to this version but not unenrolled/enrolled again
+ # In such case it is OK to fail
+ try:
+ restored = fstore.restore_file(self.conf_file)
+ except Exception:
+ pass
+
+ if not ntp_enabled:
+ ntp.stop()
+ ntp.disable()
+ else:
+ if restored:
+ ntp.restart()
+
+ def restore_forced(self, sstore):
+ """
+ Restore from --force-ntpd installation and enable/start service that
+ were disabled/stopped during installation
+ """
+ for service in services.conflicting_ntp_services:
+ if sstore.has_state(service):
+ instance = services.service(service)
+ enabled = sstore.restore_state(instance.service_name,
+ 'enabled')
+ running = sstore.restore_state(instance.service_name,
+ 'running')
+ if enabled:
+ instance.enable()
+ if running:
+ try:
+ instance.start()
+ except ipautil.CalledProcessError as e:
+ root_logger.error('Failed to start %s: %s'
+ % (service, e))
+
+ def configure_server(self):
+ raise NotImplementedError()
+
+
+class Ntp(NTPService):
+
+ def __init__(self):
+ super(Ntp, self).__init__()
+ self.service_name = 'ntpd'
+ self.conf_file = paths.NTP_CONF
+ self.lens = 'Ntp.lns'
+
+ def configure_client(self, ntp_servers=[], sstore=None, fstore=None):
+ self.server_options['burst'] = None
+ self.server_options['iburst'] = None
+ super(Ntp, self).configure_client(ntp_servers, sstore, fstore)
+
+ def synconce(self, servers):
+ """
+ Syncs time with specified server using ntpd.
+ Primarily designed to be used before Kerberos setup
+ to get time following the KDC time
+ """
+ ntpd = paths.NTPD
+ for server_fqdn in servers:
+ tmp_ntpd_conf = ipautil.write_tmp_file('server %s' % server_fqdn)
+ try:
+ ipautil.run([ntpd, '-qgc', tmp_ntpd_conf.name], timeout=60)
+ except (ipautil.CalledProcessError, OSError) as e:
+ root_logger.warning('Time synchronization with server %s'
+ 'failed: %s ' % (server_fqdn, e))
+ break
+ else:
+ raise RuntimeError('Time synchronization failed')
+
+ def configure_server(self, sstore=None, fstore=None):
+ self._store_service_state(sstore, fstore)
+
+ with ipaaugeas.aug_obj() as aug:
+ try:
+ aug.add_lens(self.lens, [self.conf_file])
+ aug.load()
+ except RuntimeError as e:
+ raise NTPConfigurationError(e)
+
+ timesync = aug.tree[self.conf_file]
+
+ # add local server if not already in configuration
+ for server in timesync['server']:
+ if server.value == '127.127.1.0':
+ break
+ else:
+ server = ipaaugeas.aug_node(aug._aug, 'server')
+ server.value = '127.127.1.0'
+ timesync['server'].append(server)
+ del server # delete before augeas context is closed
+
+ for f in timesync['fudge']:
+ if f.value == '127.127.1.0':
+ break
+ else:
+ stratum = ipaaugeas.aug_node(aug._aug, 'stratum')
+ stratum.value = '8'
+ fudge = ipaaugeas.aug_node(aug._aug, 'fudge')
+ fudge.value = '127.127.1.0'
+ fudge['stratum'] = stratum
+ timesync['fudge'].append(fudge)
+ del stratum # delete before augeas context is closed
+ del fudge # delete before augeas context is closed
+
+ # allow clients only to sync
+ for restrict in timesync['restrict']:
+ if restrict.value == 'default':
+ break
+ else:
+ restrict = ipaaugeas.aug_node(aug._aug, 'restrict')
+ restrict.value = 'default'
+ timesync['restrict'].append(restrict)
+ del restrict # delete before augeas context is closed
+
+ flags = ['nomodify', 'notrap', 'nopeer', 'noquery']
+ for restrict in timesync['restrict']:
+ if restrict.value == 'default':
+ for flag in flags:
+ if not restrict[flag]:
+ restrict[flag] = ipaaugeas.aug_node(aug._aug, flag)
+
+ if restrict['ignore']:
+ del restrict['ignore']
+ break
+
+ # allow unrestricted access from localhost
+ restrict = ipaaugeas.aug_node(aug._aug, 'restrict')
+ restrict.value = 'localhost'
+ timesync['restrict'].append(restrict)
+ del restrict # delete before augeas context is closed
+
+
+class Chrony(NTPService):
+
+ def __init__(self):
+ super(Chrony, self).__init__()
+ self.service_name = 'chronyd'
+ self.conf_file = paths.CHRONY_CONF
+ self.lens = 'chrony.lns'
+ self.source_keywords = ['server', 'pool', 'peer']
+
+ def synconce(self, servers):
+ """
+ Syncs time with specified server using ntpd.
+ Primarily designed to be used before Kerberos setup
+ to get time following the KDC time
+
+ Returns True if sync was successful
+ """
+ chronyd = paths.CHRONYD
+ for server_fqdn in servers:
+ try:
+ ipautil.run([chronyd, '-q', 'server %s iburst' % server_fqdn],
+ timeout=60)
+ except (ipautil.CalledProcessError, OSError) as e:
+ root_logger.warning('Time synchronization with server %s '
+ 'failed: %s ' % (server_fqdn, e))
+
+ # chronyd uses system capabilities a thus drops root privileges
+ # after creating pid file (among other initial actions). Therefore
+ # it is unable to remove the file at cleanup.
+ try:
+ os.remove(paths.CHRONYD_PID_FILE)
+ except OSError as e:
+ # not much we can do, log it and hope for the best
+ root_logger.warning("Failed to remove chronyd pid file.")
+
+ break # sync successful
+ else:
+ raise RuntimeError('Time synchronization failed')
+
+ def configure_client(self, ntp_servers=[], sstore=None, fstore=None):
+ self.conf_file = paths.CHRONY_CONF
+ self.server_options['iburst'] = None
+ super(Chrony, self).configure_client(ntp_servers, sstore, fstore)
+
+ def configure_server(self, sstore=None, fstore=None):
+ self._store_service_state(sstore, fstore)
+ with ipaaugeas.aug_obj() as aug:
+ try:
+ aug.add_lens(self.lens, [self.conf_file])
+ aug.load()
+ except RuntimeError as e:
+ raise NTPConfigurationError(e)
+
+ timesync = aug.tree[self.conf_file]
+
+ if not timesync['local']:
+ stratum = ipaaugeas.aug_node(aug._aug, 'stratum')
+ stratum.value = '8'
+ local = ipaaugeas.aug_node(aug._aug, 'local')
+ local['stratum'] = stratum
+ timesync['local'] = local
+ del stratum # delete before augeas context is closed
+ del local # delete before augeas context is closed
+ if not timesync['allow']:
+ allow = ipaaugeas.aug_node(aug._aug, 'allow')
+ allow.value = 'all'
+ timesync['allow'] = allow
+ del allow # delete before augeas context is closed
--
2.5.0
From 19037c8bfe816174b69a3f03e219657de59cd49a Mon Sep 17 00:00:00 2001
From: David Kupka <[email protected]>
Date: Fri, 11 Mar 2016 07:58:52 +0100
Subject: [PATCH 3/4] ntp: Add platform specific tasks
Tasks configures NTP service that is default for given platform. User still can
configure service of his choice and IPA won't touch it unless forced.
https://fedorahosted.org/freeipa/ticket/4669
---
ipaplatform/base/tasks.py | 30 ++++++++++++++++++++++++++++++
ipaplatform/fedora/tasks.py | 30 +++++++++++++++++++++++++++++-
2 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
index 573287c6bf732991946a75c8817899ee6c1842e3..0788210e82bc71e31c1e6f33e997dccf75a4eefd 100644
--- a/ipaplatform/base/tasks.py
+++ b/ipaplatform/base/tasks.py
@@ -27,6 +27,7 @@ import grp
from pkg_resources import parse_version
+import ipapython
from ipaplatform.paths import paths
from ipapython.ipa_log_manager import log_mgr
from ipapython import ipautil
@@ -236,3 +237,32 @@ class BaseTaskNamespace(object):
:return: object implementing proper __cmp__ method for version compare
"""
return parse_version(version)
+
+ def ntp_check_conflict(self, force=False):
+ if not force:
+ ntp = ipapython.ntp.Ntp()
+ ntp.check_services()
+
+ def ntp_sync_once(self, server):
+ ntp = ipapython.ntp.Ntp()
+ ntp.synconce(server)
+
+ def ntp_configure_client(self, sstore, fstore, ntp_servers=[],
+ force=False):
+ ntp = ipapython.ntp.Ntp()
+ if force:
+ ntp.force(sstore)
+ ntp.configure_client(ntp_servers, sstore, fstore)
+
+ def ntp_configure_server(self, sstore, fstore, force=False):
+ ntp = ipapython.ntp.Ntp()
+ if force:
+ ntp.force(sstore)
+ ntp.configure_server(sstore, fstore)
+
+ def ntp_uninstall(self, sstore, fstore):
+ ntp_configured = sstore.has_state('ntp')
+ if ntp_configured:
+ ntp = ipapython.ntp.Ntp()
+ ntp.restore(sstore, fstore)
+ ntp.restore_forced(sstore=sstore)
diff --git a/ipaplatform/fedora/tasks.py b/ipaplatform/fedora/tasks.py
index 903bb9211a2b9f0f2435ad66023a2b443b3ce5f1..c6c960793fc8f4097f64ea0873e304f3e2365b5b 100644
--- a/ipaplatform/fedora/tasks.py
+++ b/ipaplatform/fedora/tasks.py
@@ -24,10 +24,38 @@ This module contains default Fedora-specific implementations of system tasks.
'''
from ipaplatform.redhat.tasks import RedHatTaskNamespace
+import ipapython
class FedoraTaskNamespace(RedHatTaskNamespace):
- pass
+ def ntp_check_conflict(self, force=False):
+ if not force:
+ ntp = ipapython.ntp.Chrony()
+ ntp.check_services()
+
+ def ntp_sync_once(self, server):
+ ntp = ipapython.ntp.Chrony()
+ ntp.synconce(server)
+
+ def ntp_configure_client(self, sstore, fstore, ntp_servers=[],
+ force=False):
+ ntp = ipapython.ntp.Chrony()
+ if force:
+ ntp.force(sstore)
+ ntp.configure_client(ntp_servers, sstore, fstore)
+
+ def ntp_configure_server(self, sstore, fstore, force=False):
+ ntp = ipapython.ntp.Chrony()
+ if force:
+ ntp.force(sstore)
+ ntp.configure_server(sstore, fstore)
+
+ def ntp_uninstall(self, sstore, fstore):
+ ntp_configured = sstore.has_state('ntp')
+ if ntp_configured:
+ ntp = ipapython.ntp.Chrony()
+ ntp.restore(sstore, fstore)
+ ntp.restore_forced(sstore)
tasks = FedoraTaskNamespace()
--
2.5.0
From c13cd27249f7baae236bcdcc4497ff061534f811 Mon Sep 17 00:00:00 2001
From: David Kupka <[email protected]>
Date: Thu, 10 Mar 2016 22:41:29 +0100
Subject: [PATCH 4/4] ntp: install: Use tasks to configure NTP daemon
https://fedorahosted.org/freeipa/ticket/4669
---
client/ipa-client-install | 105 ++++---------
ipaclient/ntpconf.py | 233 -----------------------------
ipaplatform/base/services.py | 3 -
ipaplatform/fedora/services.py | 1 -
ipaplatform/redhat/services.py | 1 -
ipaplatform/rhel/services.py | 1 -
ipaserver/install/dns.py | 3 +-
ipaserver/install/ntpinstance.py | 164 ++------------------
ipaserver/install/server/common.py | 5 +
ipaserver/install/server/install.py | 29 ++--
ipaserver/install/server/replicainstall.py | 41 +++--
11 files changed, 76 insertions(+), 510 deletions(-)
delete mode 100644 ipaclient/ntpconf.py
diff --git a/client/ipa-client-install b/client/ipa-client-install
index f42d877559365af3d8def3cab9b204cdb5eb919e..ce23ae8ab0b320a00823ba652c25bdd8375905e0 100755
--- a/client/ipa-client-install
+++ b/client/ipa-client-install
@@ -41,7 +41,6 @@ try:
from ipapython.ipa_log_manager import standard_logging_setup, root_logger
from ipaclient import ipadiscovery
import ipaclient.ipachangeconf
- import ipaclient.ntpconf
from ipapython.ipautil import (
run, user_input, CalledProcessError, file_exists, dir_exists,
realm_to_suffix)
@@ -49,7 +48,7 @@ try:
from ipaplatform import services
from ipaplatform.paths import paths
from ipapython import ipautil, sysrestore, version, certmonger, ipaldap
- from ipapython import kernel_keyring, certdb
+ from ipapython import kernel_keyring, certdb, ntp
from ipapython.config import IPAOptionParser
from ipalib import api, errors
from ipalib import x509, certstore
@@ -136,9 +135,10 @@ def parse_options():
"multiple times")
basic_group.add_option("-N", "--no-ntp", action="store_false",
help="do not configure ntp", default=True, dest="conf_ntp")
- basic_group.add_option("", "--force-ntpd", dest="force_ntpd",
+ basic_group.add_option("", "--force-ntp", dest="force_ntp",
action="store_true", default=False,
- help="Stop and disable any time&date synchronization services besides ntpd")
+ help="Stop and disable any NTP services besides %s" %
+ services.ntp_service)
basic_group.add_option("--nisdomain", dest="nisdomain",
help="NIS domain name")
basic_group.add_option("--no-nisdomain", action="store_true", default=False,
@@ -224,8 +224,8 @@ def parse_options():
if (options.server and not options.domain):
parser.error("--server cannot be used without providing --domain")
- if options.force_ntpd and not options.conf_ntp:
- parser.error("--force-ntpd cannot be used together with --no-ntp")
+ if options.force_ntp and not options.conf_ntp:
+ parser.error("--force-ntp cannot be used together with --no-ntp")
if options.firefox_dir and not options.configure_firefox:
parser.error("--firefox-dir cannot be used without --configure-firefox option")
@@ -728,35 +728,7 @@ def uninstall(options, env):
service.service_name
)
- ntp_configured = statestore.has_state('ntp')
- if ntp_configured:
- ntp_enabled = statestore.restore_state('ntp', 'enabled')
- ntp_step_tickers = statestore.restore_state('ntp', 'step-tickers')
- restored = False
-
- try:
- # Restore might fail due to file missing in backup
- # the reason for it might be that freeipa-client was updated
- # to this version but not unenrolled/enrolled again
- # In such case it is OK to fail
- restored = fstore.restore_file(paths.NTP_CONF)
- restored |= fstore.restore_file(paths.SYSCONFIG_NTPD)
- if ntp_step_tickers:
- restored |= fstore.restore_file(paths.NTP_STEP_TICKERS)
- except Exception:
- pass
-
- if not ntp_enabled:
- services.knownservices.ntpd.stop()
- services.knownservices.ntpd.disable()
- else:
- if restored:
- services.knownservices.ntpd.restart()
-
- try:
- ipaclient.ntpconf.restore_forced_ntpd(statestore)
- except CalledProcessError as e:
- root_logger.error('Failed to start chronyd: %s', e)
+ tasks.ntp_uninstall(statestore, fstore)
if was_sshd_configured and services.knownservices.sshd.is_running():
services.knownservices.sshd.restart()
@@ -2224,21 +2196,12 @@ def install(options, env, fstore, statestore):
cli_domain_source = 'Unknown source'
cli_server_source = 'Unknown source'
- if options.conf_ntp and not options.on_master and not options.force_ntpd:
+ if options.conf_ntp and not options.on_master:
try:
- ipaclient.ntpconf.check_timedate_services()
- except ipaclient.ntpconf.NTPConflictingService as e:
- print("WARNING: ntpd time&date synchronization service will not" \
- " be configured as")
- print("conflicting service (%s) is enabled" % e.conflicting_service)
- print("Use --force-ntpd option to disable it and force configuration" \
- " of ntpd")
- print("")
-
- # configuration of ntpd is disabled in this case
+ tasks.ntp_check_conflict(options.force_ntp)
+ except ntp.NTPConflictingService as e:
+ print(e)
options.conf_ntp = False
- except ipaclient.ntpconf.NTPConfigurationError:
- pass
if options.unattended and (options.password is None and
options.principal is None and
@@ -2508,31 +2471,27 @@ def install(options, env, fstore, statestore):
# hostname if different from system hostname
tasks.backup_and_replace_hostname(fstore, statestore, options.hostname)
- ntp_srv_servers = []
+ ntp_servers = []
if not options.on_master and options.conf_ntp:
# Attempt to sync time with IPA server.
# If we're skipping NTP configuration, we also skip the time sync here.
# We assume that NTP servers are discoverable through SRV records in the DNS
# If that fails, we try to sync directly with IPA server, assuming it runs NTP
root_logger.info('Synchronizing time with KDC...')
- ntp_srv_servers = ds.ipadns_search_srv(cli_domain, '_ntp._udp',
+
+ if options.ntp_servers:
+ # use user specified NTP servers if there are any
+ ntp_servers = options.ntp_servers
+ else:
+ # resolve NTP servers
+ ntp_servers = ds.ipadns_search_srv(cli_domain, '_ntp._udp',
None, break_on_first=False)
- synced_ntp = False
- ntp_servers = ntp_srv_servers
- # use user specified NTP servers if there are any
- if options.ntp_servers:
- ntp_servers = options.ntp_servers
-
- for s in ntp_servers:
- synced_ntp = ipaclient.ntpconf.synconce_ntp(s, options.debug)
- if synced_ntp:
- break
-
- if not synced_ntp and not options.ntp_servers:
- synced_ntp = ipaclient.ntpconf.synconce_ntp(cli_server[0],
- options.debug)
- if not synced_ntp:
+ # add freeipa server as a NTP server
+ ntp_servers.append(cli_server[0])
+ try:
+ tasks.ntp_sync_once(ntp_servers)
+ except RuntimeError as e:
root_logger.warning("Unable to sync time with NTP " +
"server, assuming the time is in sync. Please check " +
"that 123 UDP port is opened.")
@@ -3018,20 +2977,8 @@ def install(options, env, fstore, statestore):
"/etc/ldap.conf failed: %s", str(e))
if options.conf_ntp and not options.on_master:
- # disable other time&date services first
- if options.force_ntpd:
- ipaclient.ntpconf.force_ntpd(statestore)
-
- if options.ntp_servers:
- ntp_servers = options.ntp_servers
- elif ntp_srv_servers:
- ntp_servers = ntp_srv_servers
- else:
- root_logger.warning("No SRV records of NTP servers found. IPA "
- "server address will be used")
- ntp_servers = cli_server
-
- ipaclient.ntpconf.config_ntp(ntp_servers, fstore, statestore)
+ tasks.ntp_configure_client(statestore, fstore, options.ntp_servers,
+ options.force_ntp)
root_logger.info("NTP enabled")
if options.conf_ssh:
diff --git a/ipaclient/ntpconf.py b/ipaclient/ntpconf.py
deleted file mode 100644
index 9a7db6544b54288569dc7699e67ddc865bb88db4..0000000000000000000000000000000000000000
--- a/ipaclient/ntpconf.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# Authors: Karl MacMillan <[email protected]>
-#
-# Copyright (C) 2007 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# 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 3 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 ipapython import ipautil
-from ipapython.ipa_log_manager import root_logger
-import shutil
-import os
-from ipaplatform.tasks import tasks
-from ipaplatform import services
-from ipaplatform.paths import paths
-
-ntp_conf = """# Permit time synchronization with our time source, but do not
-# permit the source to query or modify the service on this system.
-restrict default kod nomodify notrap nopeer noquery
-restrict -6 default kod nomodify notrap nopeer noquery
-
-# Permit all access over the loopback interface. This could
-# be tightened as well, but to do so would effect some of
-# the administrative functions.
-restrict 127.0.0.1
-restrict -6 ::1
-
-# Hosts on local network are less restricted.
-#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap
-
-# Use public servers from the pool.ntp.org project.
-# Please consider joining the pool (http://www.pool.ntp.org/join.html).
-$SERVERS_BLOCK
-
-#broadcast 192.168.1.255 key 42 # broadcast server
-#broadcastclient # broadcast client
-#broadcast 224.0.1.1 key 42 # multicast server
-#multicastclient 224.0.1.1 # multicast client
-#manycastserver 239.255.254.254 # manycast server
-#manycastclient 239.255.254.254 key 42 # manycast client
-
-# Undisciplined Local Clock. This is a fake driver intended for backup
-# and when no outside source of synchronized time is available.
-server 127.127.1.0 # local clock
-#fudge 127.127.1.0 stratum 10
-
-# Drift file. Put this in a directory which the daemon can write to.
-# No symbolic links allowed, either, since the daemon updates the file
-# by creating a temporary in the same directory and then rename()'ing
-# it to the file.
-driftfile /var/lib/ntp/drift
-
-# Key file containing the keys and key identifiers used when operating
-# with symmetric key cryptography.
-keys /etc/ntp/keys
-
-# Specify the key identifiers which are trusted.
-#trustedkey 4 8 42
-
-# Specify the key identifier to use with the ntpdc utility.
-#requestkey 8
-
-# Specify the key identifier to use with the ntpq utility.
-#controlkey 8
-"""
-
-ntp_sysconfig = """OPTIONS="-x -p /var/run/ntpd.pid"
-
-# Set to 'yes' to sync hw clock after successful ntpdate
-SYNC_HWCLOCK=yes
-
-# Additional options for ntpdate
-NTPDATE_OPTIONS=""
-"""
-ntp_step_tickers = """# Use IPA-provided NTP server for initial time
-$TICKER_SERVERS_BLOCK
-"""
-def __backup_config(path, fstore = None):
- if fstore:
- fstore.backup_file(path)
- else:
- shutil.copy(path, "%s.ipasave" % (path))
-
-def __write_config(path, content):
- fd = open(path, "w")
- fd.write(content)
- fd.close()
-
-def config_ntp(ntp_servers, fstore = None, sysstore = None):
- path_step_tickers = paths.NTP_STEP_TICKERS
- path_ntp_conf = paths.NTP_CONF
- path_ntp_sysconfig = paths.SYSCONFIG_NTPD
- sub_dict = {}
- sub_dict["SERVERS_BLOCK"] = "\n".join("server %s" % s for s in ntp_servers)
- sub_dict["TICKER_SERVERS_BLOCK"] = "\n".join(ntp_servers)
-
- nc = ipautil.template_str(ntp_conf, sub_dict)
- config_step_tickers = False
-
-
- if os.path.exists(path_step_tickers):
- config_step_tickers = True
- ns = ipautil.template_str(ntp_step_tickers, sub_dict)
- __backup_config(path_step_tickers, fstore)
- __write_config(path_step_tickers, ns)
- tasks.restore_context(path_step_tickers)
-
- if sysstore:
- module = 'ntp'
- sysstore.backup_state(module, "enabled", services.knownservices.ntpd.is_enabled())
- if config_step_tickers:
- sysstore.backup_state(module, "step-tickers", True)
-
- __backup_config(path_ntp_conf, fstore)
- __write_config(path_ntp_conf, nc)
- tasks.restore_context(path_ntp_conf)
-
- __backup_config(path_ntp_sysconfig, fstore)
- __write_config(path_ntp_sysconfig, ntp_sysconfig)
- tasks.restore_context(path_ntp_sysconfig)
-
- # Set the ntpd to start on boot
- services.knownservices.ntpd.enable()
-
- # Restart ntpd
- services.knownservices.ntpd.restart()
-
-
-def synconce_ntp(server_fqdn, debug=False):
- """
- Syncs time with specified server using ntpd.
- Primarily designed to be used before Kerberos setup
- to get time following the KDC time
-
- Returns True if sync was successful
- """
- ntpd = paths.NTPD
- if not os.path.exists(ntpd):
- return False
-
- tmp_ntp_conf = ipautil.write_tmp_file('server %s' % server_fqdn)
- args = [ntpd, '-qgc', tmp_ntp_conf.name]
- if debug:
- args.append('-d')
- try:
- # The ntpd command will never exit if it is unable to reach the
- # server, so timeout after 15 seconds.
- timeout = 15
- root_logger.info('Attempting to sync time using ntpd. '
- 'Will timeout after %d seconds' % timeout)
- ipautil.run(args, timeout=timeout)
- return True
- except ipautil.CalledProcessError:
- return False
-
-
-class NTPConfigurationError(Exception):
- pass
-
-class NTPConflictingService(NTPConfigurationError):
- def __init__(self, message='', conflicting_service=None):
- super(NTPConflictingService, self).__init__(self, message)
- self.conflicting_service = conflicting_service
-
-def check_timedate_services():
- """
- System may contain conflicting services used for time&date synchronization.
- As IPA server/client supports only ntpd, make sure that other services are
- not enabled to prevent conflicts. For example when both chronyd and ntpd
- are enabled, systemd would always start only chronyd to manage system
- time&date which would make IPA configuration of ntpd ineffective.
-
- Reference links:
- https://fedorahosted.org/freeipa/ticket/2974
- http://fedoraproject.org/wiki/Features/ChronyDefaultNTP
- """
- for service in services.timedate_services:
- if service == 'ntpd':
- continue
- # Make sure that the service is not enabled
- instance = services.service(service)
- if instance.is_enabled() or instance.is_running():
- raise NTPConflictingService(conflicting_service=instance.service_name)
-
-def force_ntpd(statestore):
- """
- Force ntpd configuration and disable and stop any other conflicting
- time&date service
- """
- for service in services.timedate_services:
- if service == 'ntpd':
- continue
- instance = services.service(service)
- enabled = instance.is_enabled()
- running = instance.is_running()
-
- if enabled or running:
- statestore.backup_state(instance.service_name, 'enabled', enabled)
- statestore.backup_state(instance.service_name, 'running', running)
-
- if running:
- instance.stop()
-
- if enabled:
- instance.disable()
-
-def restore_forced_ntpd(statestore):
- """
- Restore from --force-ntpd installation and enable/start service that were
- disabled/stopped during installation
- """
- for service in services.timedate_services:
- if service == 'ntpd':
- continue
- if statestore.has_state(service):
- instance = services.service(service)
- enabled = statestore.restore_state(instance.service_name, 'enabled')
- running = statestore.restore_state(instance.service_name, 'running')
- if enabled:
- instance.enable()
- if running:
- instance.start()
diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py
index 0df73ea4e86e7a04683f85cc494ef30bbc71680f..5dd6ee8ba9baa67a180b9c7f0397a4c96dc98b65 100644
--- a/ipaplatform/base/services.py
+++ b/ipaplatform/base/services.py
@@ -502,8 +502,5 @@ class SystemdService(PlatformService):
service = None
knownservices = None
-# System may support more time&date services. FreeIPA supports ntpd only, other
-# services will be disabled during IPA installation
-timedate_services = ['ntpd', 'chronyd']
ntp_service = 'ntpd'
conflicting_ntp_services = ['chronyd', 'systemd-timesyncd']
diff --git a/ipaplatform/fedora/services.py b/ipaplatform/fedora/services.py
index 4bddc2b9d5b39f81c1c95b536df380a17421cd02..ba67b219b8c643f5486cdce34c730714557025a3 100644
--- a/ipaplatform/fedora/services.py
+++ b/ipaplatform/fedora/services.py
@@ -56,7 +56,6 @@ class FedoraServices(redhat_services.RedHatServices):
# Objects below are expected to be exported by platform module
-timedate_services = redhat_services.timedate_services
ntp_service = 'chronyd'
conflicting_ntp_services = ['ntpd', 'systemd-timesyncd']
service = fedora_service_class_factory
diff --git a/ipaplatform/redhat/services.py b/ipaplatform/redhat/services.py
index 7315da4f3b99458cbb91df8443d1bde9b9ae0eba..274a84f0a935c4c2707bd191ff7bd0f8dc178567 100644
--- a/ipaplatform/redhat/services.py
+++ b/ipaplatform/redhat/services.py
@@ -280,7 +280,6 @@ class RedHatServices(base_services.KnownServices):
# Objects below are expected to be exported by platform module
-timedate_services = base_services.timedate_services
ntp_service = base_services.ntp_service
conflicting_ntp_services = base_services.conflicting_ntp_services
service = redhat_service_class_factory
diff --git a/ipaplatform/rhel/services.py b/ipaplatform/rhel/services.py
index c36ea5b43b4ad56111ce41d8c6a9c6ce60a1f36a..5cd07a2e4243fddd4f3a27fb3b90157ab94fb702 100644
--- a/ipaplatform/rhel/services.py
+++ b/ipaplatform/rhel/services.py
@@ -56,7 +56,6 @@ class RHELServices(redhat_services.RedHatServices):
# Objects below are expected to be exported by platform module
-timedate_services = redhat_services.timedate_services
ntp_service = redhat_services.ntp_service
conflicting_ntp_services = redhat_services.conflicting_ntp_services
service = rhel_service_class_factory
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index 9a2fde29f613a3ce07b4f85f5ae2a856b806bdc8..971db7b6c8ae7a7ef1d2f8f73b1ef4e4b1f33229 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -307,9 +307,10 @@ def install(standalone, replica, options, api=api):
api.env.basedn)
conn = api.Backend.ldap2
+ sstore = sysrestore.StateFile(paths.SYSRESTORE)
fstore = sysrestore.FileStore(paths.SYSRESTORE)
- conf_ntp = ntpinstance.NTPInstance(fstore).is_enabled()
+ conf_ntp = ntpinstance.NTPInstance(sstore, fstore).is_enabled()
if standalone:
# otherwise this is done by server/replica installer
diff --git a/ipaserver/install/ntpinstance.py b/ipaserver/install/ntpinstance.py
index 8b0f0e5395dae3c058fc31bd8914741e4d158830..bb4acf163dac60b5f7c31235aaca8b599e8828d2 100644
--- a/ipaserver/install/ntpinstance.py
+++ b/ipaserver/install/ntpinstance.py
@@ -19,168 +19,34 @@
#
from ipaserver.install import service
-from ipapython import sysrestore
-from ipapython import ipautil
-from ipaplatform.constants import constants
-from ipaplatform.paths import paths
-from ipapython.ipa_log_manager import root_logger
+from ipaplatform import services
+from ipaplatform.tasks import tasks
-NTPD_OPTS_VAR = constants.NTPD_OPTS_VAR
-NTPD_OPTS_QUOTE = constants.NTPD_OPTS_QUOTE
class NTPInstance(service.Service):
- def __init__(self, fstore=None):
- service.Service.__init__(self, "ntpd", service_desc="NTP daemon")
+ def __init__(self, sstore, fstore, force=False):
+ service.Service.__init__(self, services.ntp_service,
+ service_desc="NTP daemon")
- if fstore:
- self.fstore = fstore
- else:
- self.fstore = sysrestore.FileStore(paths.SYSRESTORE)
+ self.sstore = sstore
+ self.fstore = fstore
+ self.force = force
- def __write_config(self):
+ def __configure_client(self):
+ tasks.ntp_configure_client(self.sstore, self.fstore, force=self.force)
- self.fstore.backup_file(paths.NTP_CONF)
- self.fstore.backup_file(paths.SYSCONFIG_NTPD)
-
- # We use the OS variable to point it towards either the rhel
- # or fedora pools. Other distros should be added in the future
- # or we can get our own pool.
- os = ""
- if ipautil.file_exists(paths.ETC_FEDORA_RELEASE):
- os = "fedora"
- elif ipautil.file_exists(paths.ETC_REDHAT_RELEASE):
- os = "rhel"
-
- srv_vals = []
- srv_vals.append("0.%s.pool.ntp.org" % os)
- srv_vals.append("1.%s.pool.ntp.org" % os)
- srv_vals.append("2.%s.pool.ntp.org" % os)
- srv_vals.append("3.%s.pool.ntp.org" % os)
- srv_vals.append("127.127.1.0")
- fudge = ["fudge", "127.127.1.0", "stratum", "10"]
-
- #read in memory, change it, then overwrite file
- file_changed = False
- fudge_present = False
- ntpconf = []
- fd = open(paths.NTP_CONF, "r")
- for line in fd:
- opt = line.split()
- if len(opt) < 1:
- ntpconf.append(line)
- continue
-
- if opt[0] == "server":
- match = False
- for srv in srv_vals:
- if opt[1] == srv:
- match = True
- break
- if match:
- srv_vals.remove(srv)
- else:
- file_changed = True
- line = ""
- elif opt[0] == "fudge":
- if opt[0:4] == fudge[0:4]:
- fudge_present = True
- else:
- file_changed = True
- line = ""
-
- ntpconf.append(line)
-
- if file_changed or len(srv_vals) != 0 or not fudge_present:
- fd = open(paths.NTP_CONF, "w")
- for line in ntpconf:
- fd.write(line)
- fd.write("\n### Added by IPA Installer ###\n")
- if len(srv_vals) != 0:
- for srv in srv_vals:
- fd.write("server "+srv+" iburst\n")
- if not fudge_present:
- fd.write("fudge 127.127.1.0 stratum 10\n")
- fd.close()
-
- #read in memory, find OPTIONS, check/change it, then overwrite file
- needopts = [ {'val':'-x', 'need':True},
- {'val':'-g', 'need':True} ]
- fd = open(paths.SYSCONFIG_NTPD, "r")
- lines = fd.readlines()
- fd.close()
- for line in lines:
- sline = line.strip()
- if not sline.startswith(NTPD_OPTS_VAR):
- continue
- sline = sline.replace(NTPD_OPTS_QUOTE, '')
- for opt in needopts:
- if sline.find(opt['val']) != -1:
- opt['need'] = False
-
- newopts = []
- for opt in needopts:
- if opt['need']:
- newopts.append(opt['val'])
-
- done = False
- if newopts:
- fd = open(paths.SYSCONFIG_NTPD, "w")
- for line in lines:
- if not done:
- sline = line.strip()
- if not sline.startswith(NTPD_OPTS_VAR):
- fd.write(line)
- continue
- sline = sline.replace(NTPD_OPTS_QUOTE, '')
- (variable, opts) = sline.split('=', 1)
- fd.write(NTPD_OPTS_VAR + '="%s %s"\n' % (opts, ' '.join(newopts)))
- done = True
- else:
- fd.write(line)
- fd.close()
-
- def __stop(self):
- self.backup_state("running", self.is_running())
- self.stop()
-
- def __start(self):
- self.start()
-
- def __enable(self):
- self.backup_state("enabled", self.is_enabled())
- self.enable()
+ def __configure_server(self):
+ tasks.ntp_configure_server(self.sstore, self.fstore, self.force)
def create_instance(self):
# we might consider setting the date manually using ntpd -qg in case
# the current time is very far off.
- self.step("stopping ntpd", self.__stop)
- self.step("writing configuration", self.__write_config)
- self.step("configuring ntpd to start on boot", self.__enable)
- self.step("starting ntpd", self.__start)
+ self.step("configuring NTP client", self.__configure_client)
+ self.step("configuring NTP server", self.__configure_server)
self.start_creation()
def uninstall(self):
- if self.is_configured():
- self.print_msg("Unconfiguring %s" % self.service_name)
-
- running = self.restore_state("running")
- enabled = self.restore_state("enabled")
-
- # service is not in LDAP, stop and disable service
- # before restoring configuration
- self.stop()
- self.disable()
-
- try:
- self.fstore.restore_file(paths.NTP_CONF)
- except ValueError as error:
- root_logger.debug(error)
-
- if enabled:
- self.enable()
-
- if running:
- self.restart()
+ tasks.ntp_uninstall(self.sstore, self.fstore)
diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py
index bd103517277fe6fda540deeef7e0df8266fec2dc..6f3c112fc1586515a56bec42adf444bed4905769 100644
--- a/ipaserver/install/server/common.py
+++ b/ipaserver/install/server/common.py
@@ -341,6 +341,11 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
cli_short_name='N',
)
+ force_ntp = Knob(
+ bool, False,
+ description="Disble any other Time synchronization services."
+ )
+
no_pkinit = Knob(
bool, False,
description="disables pkinit setup steps",
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index b7a38a57c0f4fa7a38605585e663e767de8b0fee..f4235c36196bd9a8223c9c62f61f8d47b4428cdd 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -16,7 +16,7 @@ import textwrap
import six
-from ipapython import certmonger, ipaldap, ipautil, sysrestore
+from ipapython import certmonger, ipaldap, ipautil, sysrestore, ntp
from ipapython.dn import DN
from ipapython.install import core
from ipapython.install.common import step
@@ -31,10 +31,9 @@ from ipalib import api, create_api, constants, errors, x509
from ipalib.krb_utils import KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN
from ipalib.constants import CACERT
from ipalib.util import validate_domain_name
-import ipaclient.ntpconf
from ipaserver.install import (
bindinstance, ca, cainstance, certs, dns, dsinstance, httpinstance,
- installutils, kra, krbinstance, memcacheinstance, ntpinstance,
+ installutils, kra, krbinstance, memcacheinstance, ntpinstance,
otpdinstance, custodiainstance, replication, service, sysupgrade)
from ipaserver.install.installutils import (
IPA_MODULES, BadHostError, get_fqdn, get_server_ip_address,
@@ -526,14 +525,10 @@ def install_check(installer):
if not options.no_ntp:
try:
- ipaclient.ntpconf.check_timedate_services()
- except ipaclient.ntpconf.NTPConflictingService as e:
- print(("WARNING: conflicting time&date synchronization service '%s'"
- " will be disabled" % e.conflicting_service))
- print("in favor of ntpd")
- print("")
- except ipaclient.ntpconf.NTPConfigurationError:
- pass
+ tasks.ntp_check_conflict(options.force_ntp)
+ except ntp.NTPConflictingService as e:
+ print(e)
+ options.no_ntp = True
# Check to see if httpd is already configured to listen on 443
if httpinstance.httpd_443_configured():
@@ -842,10 +837,10 @@ def install(installer):
if not options.external_cert_files:
# Configure ntpd
if not options.no_ntp:
- ipaclient.ntpconf.force_ntpd(sstore)
- ntp = ntpinstance.NTPInstance(fstore)
- if not ntp.is_configured():
- ntp.create_instance()
+ ntp_instance = ntpinstance.NTPInstance(sstore,
+ fstore,
+ options.force_ntp)
+ ntp_instance.create_instance()
if options.dirsrv_cert_files:
ds = dsinstance.DsInstance(fstore=fstore,
@@ -1196,7 +1191,7 @@ def uninstall(installer):
except Exception as e:
pass
- ntpinstance.NTPInstance(fstore).uninstall()
+ tasks.ntp_uninstall(sstore, fstore)
kra.uninstall(False)
@@ -1227,8 +1222,6 @@ def uninstall(installer):
sstore._load()
- ipaclient.ntpconf.restore_forced_ntpd(sstore)
-
# Clean up group_exists (unused since IPA 2.2, not being set since 4.1)
sstore.restore_state("install", "group_exists")
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index e3052c129d9b6dcdef43b4fd554b13adfff27fdf..9bc994351bac5269b157be23f733418143979a5b 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -17,7 +17,7 @@ import tempfile
import six
-from ipapython import ipaldap, ipautil, sysrestore
+from ipapython import ipaldap, ipautil, sysrestore, ntp
from ipapython.dn import DN
from ipapython.install.common import step
from ipapython.install.core import Knob
@@ -27,7 +27,6 @@ from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
from ipalib import api, certstore, constants, create_api, errors, x509
import ipaclient.ipachangeconf
-import ipaclient.ntpconf
from ipaserver.install import (
bindinstance, ca, cainstance, certs, dns, dsinstance, httpinstance,
installutils, kra, krainstance, krbinstance, memcacheinstance,
@@ -521,14 +520,10 @@ def install_check(installer):
if not options.no_ntp:
try:
- ipaclient.ntpconf.check_timedate_services()
- except ipaclient.ntpconf.NTPConflictingService as e:
- print(("WARNING: conflicting time&date synchronization service '%s'"
- " will" % e.conflicting_service))
- print("be disabled in favor of ntpd")
- print("")
- except ipaclient.ntpconf.NTPConfigurationError:
- pass
+ tasks.ntp_check_conflict(options.force_ntp)
+ except ntp.NTPConflictingService as e:
+ print(e)
+ options.no_ntp = True
# get the directory manager password
dirman_password = options.password
@@ -784,9 +779,10 @@ def install(installer):
# Configure ntpd
if not options.no_ntp:
- ipaclient.ntpconf.force_ntpd(sstore)
- ntp = ntpinstance.NTPInstance()
- ntp.create_instance()
+ ntp_instance = ntpinstance.NTPInstance(sstore,
+ fstore,
+ options.force_ntp)
+ ntp_instance.create_instance()
# Configure dirsrv
ds = install_replica_ds(config, options, ca_enabled)
@@ -975,14 +971,10 @@ def promote_check(installer):
if not options.no_ntp:
try:
- ipaclient.ntpconf.check_timedate_services()
- except ipaclient.ntpconf.NTPConflictingService as e:
- print("WARNING: conflicting time&date synchronization service '%s'"
- " will" % e.conflicting_service)
- print("be disabled in favor of ntpd")
- print("")
- except ipaclient.ntpconf.NTPConfigurationError:
- pass
+ tasks.ntp_check_conflict(options.force_ntp)
+ except ntp.NTPConflictingService as e:
+ print(e)
+ options.no_ntp = True
api.bootstrap(context='installer')
api.finalize()
@@ -1349,9 +1341,10 @@ def promote(installer):
# Configure ntpd
if not options.no_ntp:
- ipaclient.ntpconf.force_ntpd(sstore)
- ntp = ntpinstance.NTPInstance()
- ntp.create_instance()
+ ntp_instance = ntpinstance.NTPInstance(sstore,
+ fstore,
+ options.force_ntp)
+ ntp_instance.create_instance()
try:
# Configure dirsrv
--
2.5.0
--
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code