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 <dku...@redhat.com>
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 <dku...@redhat.com>
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 <kmacmil...@redhat.com>
+#          David Kupka <dku...@redhat.com>
+#
+# 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 <dku...@redhat.com>
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 <dku...@redhat.com>
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 <kmacmil...@redhat.com>
-#
-# 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

Reply via email to