Blame cloudinit/config/cc_ntp.py

Packit bc9a3a
# Copyright (C) 2016 Canonical Ltd.
Packit bc9a3a
#
Packit bc9a3a
# Author: Ryan Harper <ryan.harper@canonical.com>
Packit bc9a3a
#
Packit bc9a3a
# This file is part of cloud-init. See LICENSE file for license information.
Packit bc9a3a
Packit bc9a3a
"""NTP: enable and configure ntp"""
Packit bc9a3a
Packit bc9a3a
from cloudinit.config.schema import (
Packit bc9a3a
    get_schema_doc, validate_cloudconfig_schema)
Packit bc9a3a
from cloudinit import log as logging
Packit bc9a3a
from cloudinit.settings import PER_INSTANCE
Packit bc9a3a
from cloudinit import temp_utils
Packit bc9a3a
from cloudinit import templater
Packit bc9a3a
from cloudinit import type_utils
Packit bc9a3a
from cloudinit import util
Packit bc9a3a
Packit bc9a3a
import copy
Packit bc9a3a
import os
Packit bc9a3a
import six
Packit bc9a3a
from textwrap import dedent
Packit bc9a3a
Packit bc9a3a
LOG = logging.getLogger(__name__)
Packit bc9a3a
Packit bc9a3a
frequency = PER_INSTANCE
Packit bc9a3a
NTP_CONF = '/etc/ntp.conf'
Packit bc9a3a
NR_POOL_SERVERS = 4
Packit bc9a3a
distros = ['centos', 'debian', 'fedora', 'opensuse', 'rhel', 'sles', 'ubuntu']
Packit bc9a3a
Packit bc9a3a
NTP_CLIENT_CONFIG = {
Packit bc9a3a
    'chrony': {
Packit bc9a3a
        'check_exe': 'chronyd',
Packit bc9a3a
        'confpath': '/etc/chrony.conf',
Packit bc9a3a
        'packages': ['chrony'],
Packit bc9a3a
        'service_name': 'chrony',
Packit bc9a3a
        'template_name': 'chrony.conf.{distro}',
Packit bc9a3a
        'template': None,
Packit bc9a3a
    },
Packit bc9a3a
    'ntp': {
Packit bc9a3a
        'check_exe': 'ntpd',
Packit bc9a3a
        'confpath': NTP_CONF,
Packit bc9a3a
        'packages': ['ntp'],
Packit bc9a3a
        'service_name': 'ntp',
Packit bc9a3a
        'template_name': 'ntp.conf.{distro}',
Packit bc9a3a
        'template': None,
Packit bc9a3a
    },
Packit bc9a3a
    'ntpdate': {
Packit bc9a3a
        'check_exe': 'ntpdate',
Packit bc9a3a
        'confpath': NTP_CONF,
Packit bc9a3a
        'packages': ['ntpdate'],
Packit bc9a3a
        'service_name': 'ntpdate',
Packit bc9a3a
        'template_name': 'ntp.conf.{distro}',
Packit bc9a3a
        'template': None,
Packit bc9a3a
    },
Packit bc9a3a
    'systemd-timesyncd': {
Packit bc9a3a
        'check_exe': '/lib/systemd/systemd-timesyncd',
Packit bc9a3a
        'confpath': '/etc/systemd/timesyncd.conf.d/cloud-init.conf',
Packit bc9a3a
        'packages': [],
Packit bc9a3a
        'service_name': 'systemd-timesyncd',
Packit bc9a3a
        'template_name': 'timesyncd.conf',
Packit bc9a3a
        'template': None,
Packit bc9a3a
    },
Packit bc9a3a
}
Packit bc9a3a
Packit bc9a3a
# This is Distro-specific configuration overrides of the base config
Packit bc9a3a
DISTRO_CLIENT_CONFIG = {
Packit bc9a3a
    'debian': {
Packit bc9a3a
        'chrony': {
Packit bc9a3a
            'confpath': '/etc/chrony/chrony.conf',
Packit bc9a3a
        },
Packit bc9a3a
    },
Packit bc9a3a
    'opensuse': {
Packit bc9a3a
        'chrony': {
Packit bc9a3a
            'service_name': 'chronyd',
Packit bc9a3a
        },
Packit bc9a3a
        'ntp': {
Packit bc9a3a
            'confpath': '/etc/ntp.conf',
Packit bc9a3a
            'service_name': 'ntpd',
Packit bc9a3a
        },
Packit bc9a3a
        'systemd-timesyncd': {
Packit bc9a3a
            'check_exe': '/usr/lib/systemd/systemd-timesyncd',
Packit bc9a3a
        },
Packit bc9a3a
    },
Packit bc9a3a
    'sles': {
Packit bc9a3a
        'chrony': {
Packit bc9a3a
            'service_name': 'chronyd',
Packit bc9a3a
        },
Packit bc9a3a
        'ntp': {
Packit bc9a3a
            'confpath': '/etc/ntp.conf',
Packit bc9a3a
            'service_name': 'ntpd',
Packit bc9a3a
        },
Packit bc9a3a
        'systemd-timesyncd': {
Packit bc9a3a
            'check_exe': '/usr/lib/systemd/systemd-timesyncd',
Packit bc9a3a
        },
Packit bc9a3a
    },
Packit bc9a3a
    'ubuntu': {
Packit bc9a3a
        'chrony': {
Packit bc9a3a
            'confpath': '/etc/chrony/chrony.conf',
Packit bc9a3a
        },
Packit bc9a3a
    },
Packit bc9a3a
}
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
# The schema definition for each cloud-config module is a strict contract for
Packit bc9a3a
# describing supported configuration parameters for each cloud-config section.
Packit bc9a3a
# It allows cloud-config to validate and alert users to invalid or ignored
Packit bc9a3a
# configuration options before actually attempting to deploy with said
Packit bc9a3a
# configuration.
Packit bc9a3a
Packit bc9a3a
schema = {
Packit bc9a3a
    'id': 'cc_ntp',
Packit bc9a3a
    'name': 'NTP',
Packit bc9a3a
    'title': 'enable and configure ntp',
Packit bc9a3a
    'description': dedent("""\
Packit bc9a3a
        Handle ntp configuration. If ntp is not installed on the system and
Packit bc9a3a
        ntp configuration is specified, ntp will be installed. If there is a
Packit bc9a3a
        default ntp config file in the image or one is present in the
Packit bc9a3a
        distro's ntp package, it will be copied to ``/etc/ntp.conf.dist``
Packit bc9a3a
        before any changes are made. A list of ntp pools and ntp servers can
Packit bc9a3a
        be provided under the ``ntp`` config key. If no ntp ``servers`` or
Packit bc9a3a
        ``pools`` are provided, 4 pools will be used in the format
Packit bc9a3a
        ``{0-3}.{distro}.pool.ntp.org``."""),
Packit bc9a3a
    'distros': distros,
Packit bc9a3a
    'examples': [
Packit bc9a3a
        dedent("""\
Packit bc9a3a
        # Override ntp with chrony configuration on Ubuntu
Packit bc9a3a
        ntp:
Packit bc9a3a
          enabled: true
Packit bc9a3a
          ntp_client: chrony  # Uses cloud-init default chrony configuration
Packit bc9a3a
        """),
Packit bc9a3a
        dedent("""\
Packit bc9a3a
        # Provide a custom ntp client configuration
Packit bc9a3a
        ntp:
Packit bc9a3a
          enabled: true
Packit bc9a3a
          ntp_client: myntpclient
Packit bc9a3a
          config:
Packit bc9a3a
             confpath: /etc/myntpclient/myntpclient.conf
Packit bc9a3a
             check_exe: myntpclientd
Packit bc9a3a
             packages:
Packit bc9a3a
               - myntpclient
Packit bc9a3a
             service_name: myntpclient
Packit bc9a3a
             template: |
Packit bc9a3a
                 ## template:jinja
Packit bc9a3a
                 # My NTP Client config
Packit bc9a3a
                 {% if pools -%}# pools{% endif %}
Packit bc9a3a
                 {% for pool in pools -%}
Packit bc9a3a
                 pool {{pool}} iburst
Packit bc9a3a
                 {% endfor %}
Packit bc9a3a
                 {%- if servers %}# servers
Packit bc9a3a
                 {% endif %}
Packit bc9a3a
                 {% for server in servers -%}
Packit bc9a3a
                 server {{server}} iburst
Packit bc9a3a
                 {% endfor %}
Packit bc9a3a
          pools: [0.int.pool.ntp.org, 1.int.pool.ntp.org, ntp.myorg.org]
Packit bc9a3a
          servers:
Packit bc9a3a
            - ntp.server.local
Packit bc9a3a
            - ntp.ubuntu.com
Packit bc9a3a
            - 192.168.23.2""")],
Packit bc9a3a
    'frequency': PER_INSTANCE,
Packit bc9a3a
    'type': 'object',
Packit bc9a3a
    'properties': {
Packit bc9a3a
        'ntp': {
Packit bc9a3a
            'type': ['object', 'null'],
Packit bc9a3a
            'properties': {
Packit bc9a3a
                'pools': {
Packit bc9a3a
                    'type': 'array',
Packit bc9a3a
                    'items': {
Packit bc9a3a
                        'type': 'string',
Packit bc9a3a
                        'format': 'hostname'
Packit bc9a3a
                    },
Packit bc9a3a
                    'uniqueItems': True,
Packit bc9a3a
                    'description': dedent("""\
Packit bc9a3a
                        List of ntp pools. If both pools and servers are
Packit bc9a3a
                         empty, 4 default pool servers will be provided of
Packit bc9a3a
                         the format ``{0-3}.{distro}.pool.ntp.org``.""")
Packit bc9a3a
                },
Packit bc9a3a
                'servers': {
Packit bc9a3a
                    'type': 'array',
Packit bc9a3a
                    'items': {
Packit bc9a3a
                        'type': 'string',
Packit bc9a3a
                        'format': 'hostname'
Packit bc9a3a
                    },
Packit bc9a3a
                    'uniqueItems': True,
Packit bc9a3a
                    'description': dedent("""\
Packit bc9a3a
                        List of ntp servers. If both pools and servers are
Packit bc9a3a
                         empty, 4 default pool servers will be provided with
Packit bc9a3a
                         the format ``{0-3}.{distro}.pool.ntp.org``.""")
Packit bc9a3a
                },
Packit bc9a3a
                'ntp_client': {
Packit bc9a3a
                    'type': 'string',
Packit bc9a3a
                    'default': 'auto',
Packit bc9a3a
                    'description': dedent("""\
Packit bc9a3a
                        Name of an NTP client to use to configure system NTP.
Packit bc9a3a
                         When unprovided or 'auto' the default client preferred
Packit bc9a3a
                         by the distribution will be used. The following
Packit bc9a3a
                         built-in client names can be used to override existing
Packit bc9a3a
                         configuration defaults: chrony, ntp, ntpdate,
Packit bc9a3a
                         systemd-timesyncd."""),
Packit bc9a3a
                },
Packit bc9a3a
                'enabled': {
Packit bc9a3a
                    'type': 'boolean',
Packit bc9a3a
                    'default': True,
Packit bc9a3a
                    'description': dedent("""\
Packit bc9a3a
                        Attempt to enable ntp clients if set to True.  If set
Packit bc9a3a
                         to False, ntp client will not be configured or
Packit bc9a3a
                         installed"""),
Packit bc9a3a
                },
Packit bc9a3a
                'config': {
Packit bc9a3a
                    'description': dedent("""\
Packit bc9a3a
                        Configuration settings or overrides for the
Packit bc9a3a
                         ``ntp_client`` specified."""),
Packit bc9a3a
                    'type': ['object'],
Packit bc9a3a
                    'properties': {
Packit bc9a3a
                        'confpath': {
Packit bc9a3a
                            'type': 'string',
Packit bc9a3a
                            'description': dedent("""\
Packit bc9a3a
                                The path to where the ``ntp_client``
Packit bc9a3a
                                 configuration is written."""),
Packit bc9a3a
                        },
Packit bc9a3a
                        'check_exe': {
Packit bc9a3a
                            'type': 'string',
Packit bc9a3a
                            'description': dedent("""\
Packit bc9a3a
                                The executable name for the ``ntp_client``.
Packit bc9a3a
                                 For example, ntp service ``check_exe`` is
Packit bc9a3a
                                 'ntpd' because it runs the ntpd binary."""),
Packit bc9a3a
                        },
Packit bc9a3a
                        'packages': {
Packit bc9a3a
                            'type': 'array',
Packit bc9a3a
                            'items': {
Packit bc9a3a
                                'type': 'string',
Packit bc9a3a
                            },
Packit bc9a3a
                            'uniqueItems': True,
Packit bc9a3a
                            'description': dedent("""\
Packit bc9a3a
                                List of packages needed to be installed for the
Packit bc9a3a
                                 selected ``ntp_client``."""),
Packit bc9a3a
                        },
Packit bc9a3a
                        'service_name': {
Packit bc9a3a
                            'type': 'string',
Packit bc9a3a
                            'description': dedent("""\
Packit bc9a3a
                                The systemd or sysvinit service name used to
Packit bc9a3a
                                 start and stop the ``ntp_client``
Packit bc9a3a
                                 service."""),
Packit bc9a3a
                        },
Packit bc9a3a
                        'template': {
Packit bc9a3a
                            'type': 'string',
Packit bc9a3a
                            'description': dedent("""\
Packit bc9a3a
                                Inline template allowing users to define their
Packit bc9a3a
                                 own ``ntp_client`` configuration template.
Packit bc9a3a
                                 The value must start with '## template:jinja'
Packit bc9a3a
                                 to enable use of templating support.
Packit bc9a3a
                                """),
Packit bc9a3a
                        },
Packit bc9a3a
                    },
Packit bc9a3a
                    # Don't use REQUIRED_NTP_CONFIG_KEYS to allow for override
Packit bc9a3a
                    # of builtin client values.
Packit bc9a3a
                    'required': [],
Packit bc9a3a
                    'minProperties': 1,  # If we have config, define something
Packit bc9a3a
                    'additionalProperties': False
Packit bc9a3a
                },
Packit bc9a3a
            },
Packit bc9a3a
            'required': [],
Packit bc9a3a
            'additionalProperties': False
Packit bc9a3a
        }
Packit bc9a3a
    }
Packit bc9a3a
}
Packit bc9a3a
REQUIRED_NTP_CONFIG_KEYS = frozenset([
Packit bc9a3a
    'check_exe', 'confpath', 'packages', 'service_name'])
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
__doc__ = get_schema_doc(schema)  # Supplement python help()
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def distro_ntp_client_configs(distro):
Packit bc9a3a
    """Construct a distro-specific ntp client config dictionary by merging
Packit bc9a3a
       distro specific changes into base config.
Packit bc9a3a
Packit bc9a3a
    @param distro: String providing the distro class name.
Packit bc9a3a
    @returns: Dict of distro configurations for ntp clients.
Packit bc9a3a
    """
Packit bc9a3a
    dcfg = DISTRO_CLIENT_CONFIG
Packit bc9a3a
    cfg = copy.copy(NTP_CLIENT_CONFIG)
Packit bc9a3a
    if distro in dcfg:
Packit bc9a3a
        cfg = util.mergemanydict([cfg, dcfg[distro]], reverse=True)
Packit bc9a3a
    return cfg
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def select_ntp_client(ntp_client, distro):
Packit bc9a3a
    """Determine which ntp client is to be used, consulting the distro
Packit bc9a3a
       for its preference.
Packit bc9a3a
Packit bc9a3a
    @param ntp_client: String name of the ntp client to use.
Packit bc9a3a
    @param distro: Distro class instance.
Packit bc9a3a
    @returns: Dict of the selected ntp client or {} if none selected.
Packit bc9a3a
    """
Packit bc9a3a
Packit bc9a3a
    # construct distro-specific ntp_client_config dict
Packit bc9a3a
    distro_cfg = distro_ntp_client_configs(distro.name)
Packit bc9a3a
Packit bc9a3a
    # user specified client, return its config
Packit bc9a3a
    if ntp_client and ntp_client != 'auto':
Packit bc9a3a
        LOG.debug('Selected NTP client "%s" via user-data configuration',
Packit bc9a3a
                  ntp_client)
Packit bc9a3a
        return distro_cfg.get(ntp_client, {})
Packit bc9a3a
Packit bc9a3a
    # default to auto if unset in distro
Packit bc9a3a
    distro_ntp_client = distro.get_option('ntp_client', 'auto')
Packit bc9a3a
Packit bc9a3a
    clientcfg = {}
Packit bc9a3a
    if distro_ntp_client == "auto":
Packit bc9a3a
        for client in distro.preferred_ntp_clients:
Packit bc9a3a
            cfg = distro_cfg.get(client)
Packit bc9a3a
            if util.which(cfg.get('check_exe')):
Packit bc9a3a
                LOG.debug('Selected NTP client "%s", already installed',
Packit bc9a3a
                          client)
Packit bc9a3a
                clientcfg = cfg
Packit bc9a3a
                break
Packit bc9a3a
Packit bc9a3a
        if not clientcfg:
Packit bc9a3a
            client = distro.preferred_ntp_clients[0]
Packit bc9a3a
            LOG.debug(
Packit bc9a3a
                'Selected distro preferred NTP client "%s", not yet installed',
Packit bc9a3a
                client)
Packit bc9a3a
            clientcfg = distro_cfg.get(client)
Packit bc9a3a
    else:
Packit bc9a3a
        LOG.debug('Selected NTP client "%s" via distro system config',
Packit bc9a3a
                  distro_ntp_client)
Packit bc9a3a
        clientcfg = distro_cfg.get(distro_ntp_client, {})
Packit bc9a3a
Packit bc9a3a
    return clientcfg
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def install_ntp_client(install_func, packages=None, check_exe="ntpd"):
Packit bc9a3a
    """Install ntp client package if not already installed.
Packit bc9a3a
Packit bc9a3a
    @param install_func: function.  This parameter is invoked with the contents
Packit bc9a3a
    of the packages parameter.
Packit bc9a3a
    @param packages: list.  This parameter defaults to ['ntp'].
Packit bc9a3a
    @param check_exe: string.  The name of a binary that indicates the package
Packit bc9a3a
    the specified package is already installed.
Packit bc9a3a
    """
Packit bc9a3a
    if util.which(check_exe):
Packit bc9a3a
        return
Packit bc9a3a
    if packages is None:
Packit bc9a3a
        packages = ['ntp']
Packit bc9a3a
Packit bc9a3a
    install_func(packages)
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def rename_ntp_conf(confpath=None):
Packit bc9a3a
    """Rename any existing ntp client config file
Packit bc9a3a
Packit bc9a3a
    @param confpath: string. Specify a path to an existing ntp client
Packit bc9a3a
    configuration file.
Packit bc9a3a
    """
Packit bc9a3a
    if os.path.exists(confpath):
Packit bc9a3a
        util.rename(confpath, confpath + ".dist")
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def generate_server_names(distro):
Packit bc9a3a
    """Generate a list of server names to populate an ntp client configuration
Packit bc9a3a
    file.
Packit bc9a3a
Packit bc9a3a
    @param distro: string.  Specify the distro name
Packit bc9a3a
    @returns: list: A list of strings representing ntp servers for this distro.
Packit bc9a3a
    """
Packit bc9a3a
    names = []
Packit bc9a3a
    pool_distro = distro
Packit bc9a3a
    # For legal reasons x.pool.sles.ntp.org does not exist,
Packit bc9a3a
    # use the opensuse pool
Packit bc9a3a
    if distro == 'sles':
Packit bc9a3a
        pool_distro = 'opensuse'
Packit bc9a3a
    for x in range(0, NR_POOL_SERVERS):
Packit bc9a3a
        name = "%d.%s.pool.ntp.org" % (x, pool_distro)
Packit bc9a3a
        names.append(name)
Packit bc9a3a
    return names
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def write_ntp_config_template(distro_name, servers=None, pools=None,
Packit bc9a3a
                              path=None, template_fn=None, template=None):
Packit bc9a3a
    """Render a ntp client configuration for the specified client.
Packit bc9a3a
Packit bc9a3a
    @param distro_name: string.  The distro class name.
Packit bc9a3a
    @param servers: A list of strings specifying ntp servers. Defaults to empty
Packit bc9a3a
    list.
Packit bc9a3a
    @param pools: A list of strings specifying ntp pools. Defaults to empty
Packit bc9a3a
    list.
Packit bc9a3a
    @param path: A string to specify where to write the rendered template.
Packit bc9a3a
    @param template_fn: A string to specify the template source file.
Packit bc9a3a
    @param template: A string specifying the contents of the template. This
Packit bc9a3a
    content will be written to a temporary file before being used to render
Packit bc9a3a
    the configuration file.
Packit bc9a3a
Packit bc9a3a
    @raises: ValueError when path is None.
Packit bc9a3a
    @raises: ValueError when template_fn is None and template is None.
Packit bc9a3a
    """
Packit bc9a3a
    if not servers:
Packit bc9a3a
        servers = []
Packit bc9a3a
    if not pools:
Packit bc9a3a
        pools = []
Packit bc9a3a
Packit bc9a3a
    if len(servers) == 0 and len(pools) == 0:
Packit bc9a3a
        pools = generate_server_names(distro_name)
Packit bc9a3a
        LOG.debug(
Packit bc9a3a
            'Adding distro default ntp pool servers: %s', ','.join(pools))
Packit bc9a3a
Packit bc9a3a
    if not path:
Packit bc9a3a
        raise ValueError('Invalid value for path parameter')
Packit bc9a3a
Packit bc9a3a
    if not template_fn and not template:
Packit bc9a3a
        raise ValueError('Not template_fn or template provided')
Packit bc9a3a
Packit bc9a3a
    params = {'servers': servers, 'pools': pools}
Packit bc9a3a
    if template:
Packit bc9a3a
        tfile = temp_utils.mkstemp(prefix='template_name-', suffix=".tmpl")
Packit bc9a3a
        template_fn = tfile[1]  # filepath is second item in tuple
Packit bc9a3a
        util.write_file(template_fn, content=template)
Packit bc9a3a
Packit bc9a3a
    templater.render_to_file(template_fn, path, params)
Packit bc9a3a
    # clean up temporary template
Packit bc9a3a
    if template:
Packit bc9a3a
        util.del_file(template_fn)
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def reload_ntp(service, systemd=False):
Packit bc9a3a
    """Restart or reload an ntp system service.
Packit bc9a3a
Packit bc9a3a
    @param service: A string specifying the name of the service to be affected.
Packit bc9a3a
    @param systemd: A boolean indicating if the distro uses systemd, defaults
Packit bc9a3a
    to False.
Packit bc9a3a
    @returns: A tuple of stdout, stderr results from executing the action.
Packit bc9a3a
    """
Packit bc9a3a
    if systemd:
Packit bc9a3a
        cmd = ['systemctl', 'reload-or-restart', service]
Packit bc9a3a
    else:
Packit bc9a3a
        cmd = ['service', service, 'restart']
Packit bc9a3a
    util.subp(cmd, capture=True)
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def supplemental_schema_validation(ntp_config):
Packit bc9a3a
    """Validate user-provided ntp:config option values.
Packit bc9a3a
Packit bc9a3a
    This function supplements flexible jsonschema validation with specific
Packit bc9a3a
    value checks to aid in triage of invalid user-provided configuration.
Packit bc9a3a
Packit bc9a3a
    @param ntp_config: Dictionary of configuration value under 'ntp'.
Packit bc9a3a
Packit bc9a3a
    @raises: ValueError describing invalid values provided.
Packit bc9a3a
    """
Packit bc9a3a
    errors = []
Packit bc9a3a
    missing = REQUIRED_NTP_CONFIG_KEYS.difference(set(ntp_config.keys()))
Packit bc9a3a
    if missing:
Packit bc9a3a
        keys = ', '.join(sorted(missing))
Packit bc9a3a
        errors.append(
Packit bc9a3a
            'Missing required ntp:config keys: {keys}'.format(keys=keys))
Packit bc9a3a
    elif not any([ntp_config.get('template'),
Packit bc9a3a
                  ntp_config.get('template_name')]):
Packit bc9a3a
        errors.append(
Packit bc9a3a
            'Either ntp:config:template or ntp:config:template_name values'
Packit bc9a3a
            ' are required')
Packit bc9a3a
    for key, value in sorted(ntp_config.items()):
Packit bc9a3a
        keypath = 'ntp:config:' + key
Packit bc9a3a
        if key == 'confpath':
Packit bc9a3a
            if not all([value, isinstance(value, six.string_types)]):
Packit bc9a3a
                errors.append(
Packit bc9a3a
                    'Expected a config file path {keypath}.'
Packit bc9a3a
                    ' Found ({value})'.format(keypath=keypath, value=value))
Packit bc9a3a
        elif key == 'packages':
Packit bc9a3a
            if not isinstance(value, list):
Packit bc9a3a
                errors.append(
Packit bc9a3a
                    'Expected a list of required package names for {keypath}.'
Packit bc9a3a
                    ' Found ({value})'.format(keypath=keypath, value=value))
Packit bc9a3a
        elif key in ('template', 'template_name'):
Packit bc9a3a
            if value is None:  # Either template or template_name can be none
Packit bc9a3a
                continue
Packit bc9a3a
            if not isinstance(value, six.string_types):
Packit bc9a3a
                errors.append(
Packit bc9a3a
                    'Expected a string type for {keypath}.'
Packit bc9a3a
                    ' Found ({value})'.format(keypath=keypath, value=value))
Packit bc9a3a
        elif not isinstance(value, six.string_types):
Packit bc9a3a
            errors.append(
Packit bc9a3a
                'Expected a string type for {keypath}.'
Packit bc9a3a
                ' Found ({value})'.format(keypath=keypath, value=value))
Packit bc9a3a
Packit bc9a3a
    if errors:
Packit bc9a3a
        raise ValueError(r'Invalid ntp configuration:\n{errors}'.format(
Packit bc9a3a
            errors='\n'.join(errors)))
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def handle(name, cfg, cloud, log, _args):
Packit bc9a3a
    """Enable and configure ntp."""
Packit bc9a3a
    if 'ntp' not in cfg:
Packit bc9a3a
        LOG.debug(
Packit bc9a3a
            "Skipping module named %s, not present or disabled by cfg", name)
Packit bc9a3a
        return
Packit bc9a3a
    ntp_cfg = cfg['ntp']
Packit bc9a3a
    if ntp_cfg is None:
Packit bc9a3a
        ntp_cfg = {}  # Allow empty config which will install the package
Packit bc9a3a
Packit bc9a3a
    # TODO drop this when validate_cloudconfig_schema is strict=True
Packit bc9a3a
    if not isinstance(ntp_cfg, (dict)):
Packit bc9a3a
        raise RuntimeError(
Packit bc9a3a
            "'ntp' key existed in config, but not a dictionary type,"
Packit bc9a3a
            " is a {_type} instead".format(_type=type_utils.obj_name(ntp_cfg)))
Packit bc9a3a
Packit bc9a3a
    validate_cloudconfig_schema(cfg, schema)
Packit bc9a3a
Packit bc9a3a
    # Allow users to explicitly enable/disable
Packit bc9a3a
    enabled = ntp_cfg.get('enabled', True)
Packit bc9a3a
    if util.is_false(enabled):
Packit bc9a3a
        LOG.debug("Skipping module named %s, disabled by cfg", name)
Packit bc9a3a
        return
Packit bc9a3a
Packit bc9a3a
    # Select which client is going to be used and get the configuration
Packit bc9a3a
    ntp_client_config = select_ntp_client(ntp_cfg.get('ntp_client'),
Packit bc9a3a
                                          cloud.distro)
Packit bc9a3a
Packit bc9a3a
    # Allow user ntp config to override distro configurations
Packit bc9a3a
    ntp_client_config = util.mergemanydict(
Packit bc9a3a
        [ntp_client_config, ntp_cfg.get('config', {})], reverse=True)
Packit bc9a3a
Packit bc9a3a
    supplemental_schema_validation(ntp_client_config)
Packit bc9a3a
    rename_ntp_conf(confpath=ntp_client_config.get('confpath'))
Packit bc9a3a
Packit bc9a3a
    template_fn = None
Packit bc9a3a
    if not ntp_client_config.get('template'):
Packit bc9a3a
        template_name = (
Packit bc9a3a
            ntp_client_config.get('template_name').replace('{distro}',
Packit bc9a3a
                                                           cloud.distro.name))
Packit bc9a3a
        template_fn = cloud.get_template_filename(template_name)
Packit bc9a3a
        if not template_fn:
Packit bc9a3a
            msg = ('No template found, not rendering %s' %
Packit bc9a3a
                   ntp_client_config.get('template_name'))
Packit bc9a3a
            raise RuntimeError(msg)
Packit bc9a3a
Packit bc9a3a
    write_ntp_config_template(cloud.distro.name,
Packit bc9a3a
                              servers=ntp_cfg.get('servers', []),
Packit bc9a3a
                              pools=ntp_cfg.get('pools', []),
Packit bc9a3a
                              path=ntp_client_config.get('confpath'),
Packit bc9a3a
                              template_fn=template_fn,
Packit bc9a3a
                              template=ntp_client_config.get('template'))
Packit bc9a3a
Packit bc9a3a
    install_ntp_client(cloud.distro.install_packages,
Packit bc9a3a
                       packages=ntp_client_config['packages'],
Packit bc9a3a
                       check_exe=ntp_client_config['check_exe'])
Packit bc9a3a
    try:
Packit bc9a3a
        reload_ntp(ntp_client_config['service_name'],
Packit bc9a3a
                   systemd=cloud.distro.uses_systemd())
Packit bc9a3a
    except util.ProcessExecutionError as e:
Packit bc9a3a
        LOG.exception("Failed to reload/start ntp service: %s", e)
Packit bc9a3a
        raise
Packit bc9a3a
Packit bc9a3a
# vi: ts=4 expandtab