LogoopenSUSE Build Service > Projects
Sign Up | Log In

View File 0001-Support-chrony-configuration-lp-1731619.patch of Package cloud-init (Project Cloud:Tools)

From 23f976be51ba9ad6e1e173f23c7220144beb942a Mon Sep 17 00:00:00 2001
From: Robert Schweikert <rjschwei@suse.com>
Date: Tue, 14 Nov 2017 18:24:17 -0500
Subject: [PATCH 1/3] - Support chrony configuration (lp#1731619)   + Add a
 template for chrony configuration   + Add new set_timesync_client to distros
 base class     - Set the timesync client provided in the config by the user
 with       system_info: ntp_client     - If no user config set the timesync
 client to one of the supported       clients if the executable is installed  
   - Fall back to the distribution default   + Handle the new settings in
 cc_ntp while retaining current behavior     as the fallback until all distro
 implementations have switched to the     new implementation   + Use new way
 of ntp client configuration for openSUSE and SLES   + Unit tests

---
 cloudinit/config/cc_ntp.py                       |  59 +++++++++----
 cloudinit/distros/__init__.py                    |  40 +++++++++
 cloudinit/distros/arch.py                        |   4 +
 cloudinit/distros/debian.py                      |   4 +
 cloudinit/distros/freebsd.py                     |   4 +
 cloudinit/distros/gentoo.py                      |   4 +
 cloudinit/distros/opensuse.py                    |  41 +++++++++
 cloudinit/distros/rhel.py                        |   4 +
 templates/chrony.conf.tmpl                       |  25 ++++++
 tests/unittests/test_distros/test_generic.py     | 101 +++++++++++++++++++++--
 tests/unittests/test_distros/test_opensuse.py    |  44 +++++++++-
 tests/unittests/test_distros/test_sles.py        |  30 ++++++-
 tests/unittests/test_handler/test_handler_ntp.py |  80 ++++++++++++++----
 13 files changed, 400 insertions(+), 40 deletions(-)
 create mode 100644 templates/chrony.conf.tmpl

--- cloudinit/config/cc_ntp.py.orig
+++ cloudinit/config/cc_ntp.py
@@ -20,8 +20,9 @@ from textwrap import dedent
 LOG = logging.getLogger(__name__)
 
 frequency = PER_INSTANCE
-NTP_CONF = '/etc/ntp.conf'
-TIMESYNCD_CONF = '/etc/systemd/timesyncd.conf.d/cloud-init.conf'
+CHRONY_CONF_FILE = '/etc/chrony.conf'
+NTP_CONF_FILE = '/etc/ntp.conf'
+TIMESYNCD_CONF_FILE = '/etc/systemd/timesyncd.conf.d/cloud-init.conf'
 NR_POOL_SERVERS = 4
 distros = ['centos', 'debian', 'fedora', 'opensuse', 'sles', 'ubuntu']
 
@@ -49,6 +50,7 @@ schema = {
     'examples': [
         dedent("""\
         ntp:
+          enabled: true
           pools: [0.int.pool.ntp.org, 1.int.pool.ntp.org, ntp.myorg.org]
           servers:
             - ntp.server.local
@@ -60,6 +62,9 @@ schema = {
         'ntp': {
             'type': ['object', 'null'],
             'properties': {
+                'enabled':  {
+                    "type": "boolean"
+                },
                 'pools': {
                     'type': 'array',
                     'items': {
@@ -110,26 +115,48 @@ def handle(name, cfg, cloud, log, _args)
             "'ntp' key existed in config, but not a dictionary type,"
             " is a {_type} instead".format(_type=type_utils.obj_name(ntp_cfg)))
 
+    if ntp_cfg.get('enabled'):
+        cloud.distro.set_timesync_client()
+    else:
+        # When all distro implementations are switched return here
+        pass
+
     validate_cloudconfig_schema(cfg, schema)
-    if ntp_installable():
-        service_name = 'ntp'
-        confpath = NTP_CONF
-        template_name = None
-        packages = ['ntp']
-        check_exe = 'ntpd'
+    if hasattr(cloud.distro, 'timesync_client'):
+        client_name = cloud.distro.timesync_client
+        service_name = cloud.distro.timesync_service_name
+        if client_name == 'ntp':
+            confpath = NTP_CONF_FILE
+            template_name = 'ntp.conf.%s' % cloud.distro.name
+        elif client_name == 'systemd-timesyncd':
+            confpath = TIMESYNCD_CONF_FILE
+            template_name = 'timesyncd.conf'
+        elif client_name == 'chrony':
+            confpath = CHRONY_CONF_FILE
+            template_name = 'chrony.conf.%s' % cloud.distro.name
     else:
-        service_name = 'systemd-timesyncd'
-        confpath = TIMESYNCD_CONF
-        template_name = 'timesyncd.conf'
-        packages = []
-        check_exe = '/lib/systemd/systemd-timesyncd'
+        if ntp_installable():
+            service_name = 'ntp'
+            confpath = NTP_CONF_FILE
+            template_name = None
+            packages = ['ntp']
+            check_exe = 'ntpd'
+        else:
+            service_name = 'systemd-timesyncd'
+            confpath = TIMESYNCD_CONF_FILE
+            template_name = 'timesyncd.conf'
+            packages = []
+            check_exe = '/lib/systemd/systemd-timesyncd'
 
-    rename_ntp_conf()
+    rename_ntp_conf(confpath)
     # ensure when ntp is installed it has a configuration file
     # to use instead of starting up with packaged defaults
     write_ntp_config_template(ntp_cfg, cloud, confpath, template=template_name)
-    install_ntp(cloud.distro.install_packages, packages=packages,
-                check_exe=check_exe)
+    if not hasattr(cloud.distro, 'timesync_client'):
+        # Updated implementation installs a package is missing in
+        # distro._set_default_timesync_client
+        install_ntp(cloud.distro.install_packages, packages=packages,
+                    check_exe=check_exe)
 
     try:
         reload_ntp(service_name, systemd=cloud.distro.uses_systemd())
@@ -167,7 +194,7 @@ def install_ntp(install_func, packages=N
 def rename_ntp_conf(config=None):
     """Rename any existing ntp.conf file"""
     if config is None:  # For testing
-        config = NTP_CONF
+        config = NTP_CONF_FILE
     if os.path.exists(config):
         util.rename(config, config + ".dist")
 
--- cloudinit/distros/__init__.py.orig
+++ cloudinit/distros/__init__.py
@@ -61,6 +61,9 @@ class Distro(object):
     init_cmd = ['service']  # systemctl, service etc
     renderer_configs = {}
 
+    __timesync_client_map = {}
+    __ntp_client_execs = []
+
     def __init__(self, name, cfg, paths):
         self._paths = paths
         self._cfg = cfg
@@ -90,6 +93,43 @@ class Distro(object):
         renderer.render_network_config(network_config=network_config)
         return []
 
+    def set_timesync_client(self):
+        system_info = self._cfg.get('system_info')
+        if system_info and isinstance(system_info, (dict)):
+            ntp_client = system_info.get('ntp_client')
+            if ntp_client and ntp_client in self.__timesync_client_map:
+                self.timesync_client, self.timesync_service_name = \
+                    self.__timesync_client_map.get(ntp_client)
+                LOG.debug('Using "%s" for timesync client per configuration',
+                          ntp_client)
+                return
+
+        found = False
+        for ntp_client in self.__ntp_client_execs:
+            ntp_exec = util.which(ntp_client)
+            if ntp_exec and not found:
+                found = ntp_client
+            # systemd-timesyncd is part of systemd and thus is probably
+            # always installed, do not consider it as a conflict
+            elif ntp_exec and found and 'systemd-timesyncd' not in ntp_exec:
+                msg = 'Found multiple timesync clients installed. Resolve '
+                msg += 'ambigutity by falling back to distro default'
+                LOG.debug(msg)
+                found = False
+                break
+
+        if found and found in self.__timesync_client_map:
+            self.timesync_client, self.timesync_service_name = \
+                self.__timesync_client_map.get(found)
+            LOG.debug('Using "%s" for timesync based on installed exec',
+                      ntp_client)
+            return
+
+        self._set_default_timesync_client()
+
+    def _set_default_timesync_client(self):
+        raise NotImplementedError()
+
     def _find_tz_file(self, tz):
         tz_file = os.path.join(self.tz_zone_dir, str(tz))
         if not os.path.isfile(tz_file):
--- /dev/null
+++ templates/chrony.conf.tmpl
@@ -0,0 +1,24 @@
+## template:jinja
+# cloud-init generated file
+# See chrony.conf(5)
+
+{% if pools %}# pools
+{% endif %}
+{% for pool in pools -%}
+pool {{pool}} iburst
+{% endfor %}
+{%- if servers %}# servers
+{% endif %}
+{% for server in servers -%}
+server {{server}} iburst
+{% endfor %}
+
+# Record the rate at which the the system clock gains/losses time
+driftfile /var/lib/chrony/drift
+
+# Allow the system clock to be stepped in the first three updates
+# if its offset is larger than 1 second.
+makestep 1.0 3
+
+# Enable kernel synchronization of the real-time clock (RTC).
+rtcsync
--- cloudinit/distros/opensuse.py.orig
+++ cloudinit/distros/opensuse.py
@@ -36,6 +36,23 @@ class Distro(distros.Distro):
     systemd_locale_conf_fn = '/etc/locale.conf'
     tz_local_fn = '/etc/localtime'
 
+    __timesync_client_map = {
+        # Map the system_info supported values
+        'chrony': ('chrony', 'chronyd'),
+        'isc-ntp': ('ntp', 'ntpd'),
+        'systemd-timesyncd': ('systemd-timesyncd', 'systemd-timesyncd'),
+        # Map the common names if different from system_info
+        'chronyd': ('chrony', 'chronyd'),
+        'ntpd': ('ntp', 'ntpd'),
+        '/usr/lib/systemd/systemd-timesyncd':
+        ('systemd-timesyncd', 'systemd-timesyncd')
+    }
+    __ntp_client_execs = [
+        'chronyd',
+        'ntpd',
+        '/usr/lib/systemd/systemd-timesyncd'
+    ]
+
     def __init__(self, name, cfg, paths):
         distros.Distro.__init__(self, name, cfg, paths)
         self._runner = helpers.Runners(paths)
@@ -145,6 +162,28 @@ class Distro(distros.Distro):
             host_fn = self.hostname_conf_fn
         return (host_fn, self._read_hostname(host_fn))
 
+    def _set_default_timesync_client(self):
+        """The default timesync client is dependent on the distribution."""
+        # When we get here the user has configured ntp to be enabled but
+        # no client is installed
+        distro_info = util.get_linux_distro()
+        name = distro_info[0]
+        major_ver = int(distro_info[1].split('.')[0])
+
+        # This is horribly complicated because of a case of
+        # "we do not care if versions should be increasing syndrome"
+        if (
+                (major_ver >= 15 and 'openSUSE' not in name) or
+                (major_ver >= 15 and 'openSUSE' in name and major_ver != 42)
+        ):
+            self.timesync_client = 'chrony'
+            self.timesync_service_name = 'chronyd'
+            self.install_packages(['chrony'])
+        else:
+            self.timesync_client = 'ntp'
+            self.timesync_service_name = 'ntpd'
+            self.install_packages(['ntp'])
+
     def _write_hostname(self, hostname, out_fn):
         if self.uses_systemd() and out_fn.endswith('/previous-hostname'):
             util.write_file(out_fn, hostname)