summaryrefslogtreecommitdiffstats
path: root/ipaclient/install/timeconf.py
blob: 57ab50a3f5db976ace846c34becd96223ffaae7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# Authors: Karl MacMillan <kmacmillan@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 __future__ import absolute_import

import logging
import os
import shutil

from augeas import Augeas
from ipalib import api
from ipapython import ipautil
from ipaplatform.tasks import tasks
from ipaplatform import services
from ipaplatform.paths import paths

logger = logging.getLogger(__name__)


def __backup_config(path, fstore=None):
    if fstore:
        fstore.backup_file(path)
    else:
        shutil.copy(path, "%s.ipasave" % (path))


def sync_chrony():
    """
    This method enables chronyd service on boot and restarts it to reload
    chrony configuration file /etc/chrony.conf
    Then it tries to synchronize time with chrony's new or defaut configuration
    """
    # Set the chronyd to start on boot
    services.knownservices.chronyd.enable()

    # Restart chronyd
    services.knownservices.chronyd.restart()

    sync_attempt_count = 3
    # chrony attempt count to sync with configiured servers
    # each next attempt is tried after 10seconds of timeot
    # 3 attempts means: if first immidiate attempt fails
    # there is 10s delay between next attempts

    args = [paths.CHRONYC, 'waitsync', str(sync_attempt_count), '-d']

    try:
        logger.info('Attempting to sync time with chronyc.')
        ipautil.run(args)
        logger.info('Time synchronization was successful.')
        return True
    except ipautil.CalledProcessError:
        logger.warning('Process chronyc waitsync failed to sync time!')
        logger.warning(
            "Unable to sync time with chrony server, assuming the time "
            "is in sync. Please check that 123 UDP port is opened, "
            "and any time server is on network.")
        return False


def configure_chrony(ntp_servers, ntp_pool=None,
                     fstore=None, sysstore=None, debug=False):
    """
    This method only configures chrony client with ntp_servers or ntp_pool
    """

    module = "chrony"
    if sysstore:
        sysstore.backup_state(module, "enabled",
                              services.knownservices.chronyd.is_enabled())

    aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD,
                 loadpath=paths.USR_SHARE_IPA_DIR)

    try:
        logger.debug("Configuring chrony")
        chrony_conf = os.path.abspath(paths.CHRONY_CONF)
        aug.transform(module, chrony_conf)  # loads chrony lens file
        aug.load()  # loads augeas tree
        # augeas needs to prepend path with '/files'
        path = '/files{path}'.format(path=chrony_conf)

        # remove possible conflicting configuration of servers
        aug.remove('{}/server'.format(path))
        aug.remove('{}/pool'.format(path))
        aug.remove('{}/peer'.format(path))
        if ntp_pool:
            logger.debug("Setting server pool:")
            logger.debug("'%s'", ntp_pool)
            aug.set('{}/pool[last()+1]'.format(path), ntp_pool)
            aug.set('{}/pool[last()]/iburst'.format(path), None)

        if ntp_servers:
            logger.debug("Setting time servers:")
            for server in ntp_servers:
                aug.set('{}/server[last()+1]'.format(path), server)
                aug.set('{}/server[last()]/iburst'.format(path), None)
                logger.debug("'%s'", server)

        # backup oginal conf file
        logger.debug("Backing up '%s'", chrony_conf)
        __backup_config(chrony_conf, fstore)

        logger.debug("Writing configuration to '%s'", chrony_conf)
        aug.save()

        logger.info('Configuration of chrony was changed by installer.')
        configured = True

    except IOError:
        logger.error("Augeas failed to configure file %s", chrony_conf)
        configured = False
    except RuntimeError as e:
        logger.error("Configuration failed with: %s", e)
        configured = False
    finally:
        aug.close()

    tasks.restore_context(chrony_conf)
    return configured


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 chronyd, make sure that other services
    are not enabled to prevent conflicts.
    """
    for service in services.timedate_services:
        if service == 'chronyd':
            continue
        # Make sure that the service is not enabled
        instance = services.service(service, api)
        if instance.is_enabled() or instance.is_running():
            raise NTPConflictingService(
                    conflicting_service=instance.service_name)


def force_chrony(statestore):
    """
    Force chronyd configuration and disable and stop any other conflicting
    time&date service
    """
    for service in services.timedate_services:
        if service == 'chronyd':
            continue
        instance = services.service(service, api)
        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_timeservices(statestore, skip_service='chronyd'):
    """
    Restore from installation and enable/start service that
    were disabled/stopped during installation
    """
    for service in services.timedate_services:
        if service == skip_service:
            continue
        if statestore.has_state(service):
            instance = services.service(service, api)
            enabled = statestore.restore_state(instance.service_name,
                                               'enabled')
            running = statestore.restore_state(instance.service_name,
                                               'running')
            if enabled:
                instance.enable()
            if running:
                instance.start()