summaryrefslogtreecommitdiff
path: root/tests/unittests/test_handler/test_handler_ntp.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unittests/test_handler/test_handler_ntp.py')
-rw-r--r--tests/unittests/test_handler/test_handler_ntp.py765
1 files changed, 0 insertions, 765 deletions
diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py
deleted file mode 100644
index b34a18cb..00000000
--- a/tests/unittests/test_handler/test_handler_ntp.py
+++ /dev/null
@@ -1,765 +0,0 @@
-# This file is part of cloud-init. See LICENSE file for license information.
-import copy
-import os
-import shutil
-from functools import partial
-from os.path import dirname
-
-from cloudinit import (helpers, util)
-from cloudinit.config import cc_ntp
-from cloudinit.tests.helpers import (
- CiTestCase, FilesystemMockingTestCase, mock, skipUnlessJsonSchema)
-
-from tests.unittests.util import get_cloud
-
-
-NTP_TEMPLATE = """\
-## template: jinja
-servers {{servers}}
-pools {{pools}}
-"""
-
-TIMESYNCD_TEMPLATE = """\
-## template:jinja
-[Time]
-{% if servers or pools -%}
-NTP={% for host in servers|list + pools|list %}{{ host }} {% endfor -%}
-{% endif -%}
-"""
-
-
-class TestNtp(FilesystemMockingTestCase):
-
- with_logs = True
-
- def setUp(self):
- super(TestNtp, self).setUp()
- self.new_root = self.tmp_dir()
- self.add_patch('cloudinit.util.system_is_snappy', 'm_snappy')
- self.m_snappy.return_value = False
- self.new_root = self.reRoot()
- self._get_cloud = partial(
- get_cloud,
- paths=helpers.Paths({'templates_dir': self.new_root})
- )
-
- def _get_template_path(self, template_name, distro, basepath=None):
- # ntp.conf.{distro} -> ntp.conf.debian.tmpl
- template_fn = '{0}.tmpl'.format(
- template_name.replace('{distro}', distro))
- if not basepath:
- basepath = self.new_root
- path = os.path.join(basepath, template_fn)
- return path
-
- def _generate_template(self, template=None):
- if not template:
- template = NTP_TEMPLATE
- confpath = os.path.join(self.new_root, 'client.conf')
- template_fn = os.path.join(self.new_root, 'client.conf.tmpl')
- util.write_file(template_fn, content=template)
- return (confpath, template_fn)
-
- def _mock_ntp_client_config(self, client=None, distro=None):
- if not client:
- client = 'ntp'
- if not distro:
- distro = 'ubuntu'
- dcfg = cc_ntp.distro_ntp_client_configs(distro)
- if client == 'systemd-timesyncd':
- template = TIMESYNCD_TEMPLATE
- else:
- template = NTP_TEMPLATE
- (confpath, _template_fn) = self._generate_template(template=template)
- ntpconfig = copy.deepcopy(dcfg[client])
- ntpconfig['confpath'] = confpath
- ntpconfig['template_name'] = os.path.basename(confpath)
- return ntpconfig
-
- @mock.patch("cloudinit.config.cc_ntp.subp")
- def test_ntp_install(self, mock_subp):
- """ntp_install_client runs install_func when check_exe is absent."""
- mock_subp.which.return_value = None # check_exe not found.
- install_func = mock.MagicMock()
- cc_ntp.install_ntp_client(install_func,
- packages=['ntpx'], check_exe='ntpdx')
- mock_subp.which.assert_called_with('ntpdx')
- install_func.assert_called_once_with(['ntpx'])
-
- @mock.patch("cloudinit.config.cc_ntp.subp")
- def test_ntp_install_not_needed(self, mock_subp):
- """ntp_install_client doesn't install when check_exe is found."""
- client = 'chrony'
- mock_subp.which.return_value = [client] # check_exe found.
- install_func = mock.MagicMock()
- cc_ntp.install_ntp_client(install_func, packages=[client],
- check_exe=client)
- install_func.assert_not_called()
-
- @mock.patch("cloudinit.config.cc_ntp.subp")
- def test_ntp_install_no_op_with_empty_pkg_list(self, mock_subp):
- """ntp_install_client runs install_func with empty list"""
- mock_subp.which.return_value = None # check_exe not found
- install_func = mock.MagicMock()
- cc_ntp.install_ntp_client(install_func, packages=[],
- check_exe='timesyncd')
- install_func.assert_called_once_with([])
-
- def test_ntp_rename_ntp_conf(self):
- """When NTP_CONF exists, rename_ntp moves it."""
- ntpconf = self.tmp_path("ntp.conf", self.new_root)
- util.write_file(ntpconf, "")
- cc_ntp.rename_ntp_conf(confpath=ntpconf)
- self.assertFalse(os.path.exists(ntpconf))
- self.assertTrue(os.path.exists("{0}.dist".format(ntpconf)))
-
- def test_ntp_rename_ntp_conf_skip_missing(self):
- """When NTP_CONF doesn't exist rename_ntp doesn't create a file."""
- ntpconf = self.tmp_path("ntp.conf", self.new_root)
- self.assertFalse(os.path.exists(ntpconf))
- cc_ntp.rename_ntp_conf(confpath=ntpconf)
- self.assertFalse(os.path.exists("{0}.dist".format(ntpconf)))
- self.assertFalse(os.path.exists(ntpconf))
-
- def test_write_ntp_config_template_uses_ntp_conf_distro_no_servers(self):
- """write_ntp_config_template reads from $client.conf.distro.tmpl"""
- servers = []
- pools = ['10.0.0.1', '10.0.0.2']
- (confpath, template_fn) = self._generate_template()
- mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR'
- with mock.patch(mock_path, self.new_root):
- cc_ntp.write_ntp_config_template('ubuntu',
- servers=servers, pools=pools,
- path=confpath,
- template_fn=template_fn,
- template=None)
- self.assertEqual(
- "servers []\npools ['10.0.0.1', '10.0.0.2']\n",
- util.load_file(confpath))
-
- def test_write_ntp_config_template_defaults_pools_w_empty_lists(self):
- """write_ntp_config_template defaults pools servers upon empty config.
-
- When both pools and servers are empty, default NR_POOL_SERVERS get
- configured.
- """
- distro = 'ubuntu'
- pools = cc_ntp.generate_server_names(distro)
- servers = []
- (confpath, template_fn) = self._generate_template()
- mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR'
- with mock.patch(mock_path, self.new_root):
- cc_ntp.write_ntp_config_template(distro,
- servers=servers, pools=pools,
- path=confpath,
- template_fn=template_fn,
- template=None)
- self.assertEqual(
- "servers []\npools {0}\n".format(pools),
- util.load_file(confpath))
-
- def test_defaults_pools_empty_lists_sles(self):
- """write_ntp_config_template defaults opensuse pools upon empty config.
-
- When both pools and servers are empty, default NR_POOL_SERVERS get
- configured.
- """
- distro = 'sles'
- default_pools = cc_ntp.generate_server_names(distro)
- (confpath, template_fn) = self._generate_template()
-
- cc_ntp.write_ntp_config_template(distro,
- servers=[], pools=[],
- path=confpath,
- template_fn=template_fn,
- template=None)
- for pool in default_pools:
- self.assertIn('opensuse', pool)
- self.assertEqual(
- "servers []\npools {0}\n".format(default_pools),
- util.load_file(confpath))
- self.assertIn(
- "Adding distro default ntp pool servers: {0}".format(
- ",".join(default_pools)),
- self.logs.getvalue())
-
- def test_timesyncd_template(self):
- """Test timesycnd template is correct"""
- pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org']
- servers = ['192.168.23.3', '192.168.23.4']
- (confpath, template_fn) = self._generate_template(
- template=TIMESYNCD_TEMPLATE)
- cc_ntp.write_ntp_config_template('ubuntu',
- servers=servers, pools=pools,
- path=confpath,
- template_fn=template_fn,
- template=None)
- self.assertEqual(
- "[Time]\nNTP=%s %s \n" % (" ".join(servers), " ".join(pools)),
- util.load_file(confpath))
-
- def test_distro_ntp_client_configs(self):
- """Test we have updated ntp client configs on different distros"""
- delta = copy.deepcopy(cc_ntp.DISTRO_CLIENT_CONFIG)
- base = copy.deepcopy(cc_ntp.NTP_CLIENT_CONFIG)
- # confirm no-delta distros match the base config
- for distro in cc_ntp.distros:
- if distro not in delta:
- result = cc_ntp.distro_ntp_client_configs(distro)
- self.assertEqual(base, result)
- # for distros with delta, ensure the merged config values match
- # what is set in the delta
- for distro in delta.keys():
- result = cc_ntp.distro_ntp_client_configs(distro)
- for client in delta[distro].keys():
- for key in delta[distro][client].keys():
- self.assertEqual(delta[distro][client][key],
- result[client][key])
-
- def _get_expected_pools(self, pools, distro, client):
- if client in ['ntp', 'chrony']:
- if client == 'ntp' and distro == 'alpine':
- # NTP for Alpine Linux is Busybox's ntp which does not
- # support 'pool' lines in its configuration file.
- expected_pools = []
- else:
- expected_pools = [
- 'pool {0} iburst'.format(pool) for pool in pools]
- elif client == 'systemd-timesyncd':
- expected_pools = " ".join(pools)
-
- return expected_pools
-
- def _get_expected_servers(self, servers, distro, client):
- if client in ['ntp', 'chrony']:
- if client == 'ntp' and distro == 'alpine':
- # NTP for Alpine Linux is Busybox's ntp which only supports
- # 'server' lines without iburst option.
- expected_servers = [
- 'server {0}'.format(srv) for srv in servers]
- else:
- expected_servers = [
- 'server {0} iburst'.format(srv) for srv in servers]
- elif client == 'systemd-timesyncd':
- expected_servers = " ".join(servers)
-
- return expected_servers
-
- def test_ntp_handler_real_distro_ntp_templates(self):
- """Test ntp handler renders the shipped distro ntp client templates."""
- pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org']
- servers = ['192.168.23.3', '192.168.23.4']
- for client in ['ntp', 'systemd-timesyncd', 'chrony']:
- for distro in cc_ntp.distros:
- distro_cfg = cc_ntp.distro_ntp_client_configs(distro)
- ntpclient = distro_cfg[client]
- confpath = (
- os.path.join(self.new_root, ntpclient.get('confpath')[1:]))
- template = ntpclient.get('template_name')
- # find sourcetree template file
- root_dir = (
- dirname(dirname(os.path.realpath(util.__file__))) +
- '/templates')
- source_fn = self._get_template_path(template, distro,
- basepath=root_dir)
- template_fn = self._get_template_path(template, distro)
- # don't fail if cloud-init doesn't have a template for
- # a distro,client pair
- if not os.path.exists(source_fn):
- continue
- # Create a copy in our tmp_dir
- shutil.copy(source_fn, template_fn)
- cc_ntp.write_ntp_config_template(distro, servers=servers,
- pools=pools, path=confpath,
- template_fn=template_fn)
- content = util.load_file(confpath)
- if client in ['ntp', 'chrony']:
- content_lines = content.splitlines()
- expected_servers = self._get_expected_servers(servers,
- distro,
- client)
- print('distro=%s client=%s' % (distro, client))
- for sline in expected_servers:
- self.assertIn(sline, content_lines,
- ('failed to render {0} conf'
- ' for distro:{1}'.format(client,
- distro)))
- expected_pools = self._get_expected_pools(pools, distro,
- client)
- if expected_pools != []:
- for pline in expected_pools:
- self.assertIn(pline, content_lines,
- ('failed to render {0} conf'
- ' for distro:{1}'.format(client,
- distro)))
- elif client == 'systemd-timesyncd':
- expected_servers = self._get_expected_servers(servers,
- distro,
- client)
- expected_pools = self._get_expected_pools(pools,
- distro,
- client)
- expected_content = (
- "# cloud-init generated file\n" +
- "# See timesyncd.conf(5) for details.\n\n" +
- "[Time]\nNTP=%s %s \n" % (expected_servers,
- expected_pools))
- self.assertEqual(expected_content, content)
-
- def test_no_ntpcfg_does_nothing(self):
- """When no ntp section is defined handler logs a warning and noops."""
- cc_ntp.handle('cc_ntp', {}, None, None, [])
- self.assertEqual(
- 'DEBUG: Skipping module named cc_ntp, '
- 'not present or disabled by cfg\n',
- self.logs.getvalue())
-
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_schema_validation_allows_empty_ntp_config(self,
- m_select):
- """Ntp schema validation allows for an empty ntp: configuration."""
- valid_empty_configs = [{'ntp': {}}, {'ntp': None}]
- for valid_empty_config in valid_empty_configs:
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro)
- confpath = ntpconfig['confpath']
- m_select.return_value = ntpconfig
- cc_ntp.handle('cc_ntp', valid_empty_config, mycloud, None, [])
- if distro == 'alpine':
- # _mock_ntp_client_config call above did not specify a
- # client value and so it defaults to "ntp" which on
- # Alpine Linux only supports servers and not pools.
-
- servers = cc_ntp.generate_server_names(mycloud.distro.name)
- self.assertEqual(
- "servers {0}\npools []\n".format(servers),
- util.load_file(confpath))
- else:
- pools = cc_ntp.generate_server_names(mycloud.distro.name)
- self.assertEqual(
- "servers []\npools {0}\n".format(pools),
- util.load_file(confpath))
- self.assertNotIn('Invalid config:', self.logs.getvalue())
-
- @skipUnlessJsonSchema()
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_schema_validation_warns_non_string_item_type(self,
- m_sel):
- """Ntp schema validation warns of non-strings in pools or servers.
-
- Schema validation is not strict, so ntp config is still be rendered.
- """
- invalid_config = {'ntp': {'pools': [123], 'servers': ['valid', None]}}
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro)
- confpath = ntpconfig['confpath']
- m_sel.return_value = ntpconfig
- cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, [])
- self.assertIn(
- "Invalid config:\nntp.pools.0: 123 is not of type 'string'\n"
- "ntp.servers.1: None is not of type 'string'",
- self.logs.getvalue())
- self.assertEqual("servers ['valid', None]\npools [123]\n",
- util.load_file(confpath))
-
- @skipUnlessJsonSchema()
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_schema_validation_warns_of_non_array_type(self,
- m_select):
- """Ntp schema validation warns of non-array pools or servers types.
-
- Schema validation is not strict, so ntp config is still be rendered.
- """
- invalid_config = {'ntp': {'pools': 123, 'servers': 'non-array'}}
-
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro)
- confpath = ntpconfig['confpath']
- m_select.return_value = ntpconfig
- cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, [])
- self.assertIn(
- "Invalid config:\nntp.pools: 123 is not of type 'array'\n"
- "ntp.servers: 'non-array' is not of type 'array'",
- self.logs.getvalue())
- self.assertEqual("servers non-array\npools 123\n",
- util.load_file(confpath))
-
- @skipUnlessJsonSchema()
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_schema_validation_warns_invalid_key_present(self,
- m_select):
- """Ntp schema validation warns of invalid keys present in ntp config.
-
- Schema validation is not strict, so ntp config is still be rendered.
- """
- invalid_config = {
- 'ntp': {'invalidkey': 1, 'pools': ['0.mycompany.pool.ntp.org']}}
- for distro in cc_ntp.distros:
- if distro != 'alpine':
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro)
- confpath = ntpconfig['confpath']
- m_select.return_value = ntpconfig
- cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, [])
- self.assertIn(
- "Invalid config:\nntp: Additional properties are not "
- "allowed ('invalidkey' was unexpected)",
- self.logs.getvalue())
- self.assertEqual(
- "servers []\npools ['0.mycompany.pool.ntp.org']\n",
- util.load_file(confpath))
-
- @skipUnlessJsonSchema()
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_schema_validation_warns_of_duplicates(self, m_select):
- """Ntp schema validation warns of duplicates in servers or pools.
-
- Schema validation is not strict, so ntp config is still be rendered.
- """
- invalid_config = {
- 'ntp': {'pools': ['0.mypool.org', '0.mypool.org'],
- 'servers': ['10.0.0.1', '10.0.0.1']}}
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro)
- confpath = ntpconfig['confpath']
- m_select.return_value = ntpconfig
- cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, [])
- self.assertIn(
- "Invalid config:\nntp.pools: ['0.mypool.org', '0.mypool.org']"
- " has non-unique elements\nntp.servers: "
- "['10.0.0.1', '10.0.0.1'] has non-unique elements",
- self.logs.getvalue())
- self.assertEqual(
- "servers ['10.0.0.1', '10.0.0.1']\n"
- "pools ['0.mypool.org', '0.mypool.org']\n",
- util.load_file(confpath))
-
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_timesyncd(self, m_select):
- """Test ntp handler configures timesyncd"""
- servers = ['192.168.2.1', '192.168.2.2']
- pools = ['0.mypool.org']
- cfg = {'ntp': {'servers': servers, 'pools': pools}}
- client = 'systemd-timesyncd'
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro,
- client=client)
- confpath = ntpconfig['confpath']
- m_select.return_value = ntpconfig
- cc_ntp.handle('cc_ntp', cfg, mycloud, None, [])
- self.assertEqual(
- "[Time]\nNTP=192.168.2.1 192.168.2.2 0.mypool.org \n",
- util.load_file(confpath))
-
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_handler_enabled_false(self, m_select):
- """Test ntp handler does not run if enabled: false """
- cfg = {'ntp': {'enabled': False}}
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- cc_ntp.handle('notimportant', cfg, mycloud, None, None)
- self.assertEqual(0, m_select.call_count)
-
- @mock.patch("cloudinit.distros.subp")
- @mock.patch("cloudinit.config.cc_ntp.subp")
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- @mock.patch("cloudinit.distros.Distro.uses_systemd")
- def test_ntp_the_whole_package(self, m_sysd, m_select, m_subp, m_dsubp):
- """Test enabled config renders template, and restarts service """
- cfg = {'ntp': {'enabled': True}}
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(distro=distro)
- confpath = ntpconfig['confpath']
- service_name = ntpconfig['service_name']
- m_select.return_value = ntpconfig
-
- hosts = cc_ntp.generate_server_names(mycloud.distro.name)
- uses_systemd = True
- expected_service_call = ['systemctl', 'reload-or-restart',
- service_name]
- expected_content = "servers []\npools {0}\n".format(hosts)
-
- if distro == 'alpine':
- uses_systemd = False
- expected_service_call = ['rc-service', service_name, 'restart']
- # _mock_ntp_client_config call above did not specify a client
- # value and so it defaults to "ntp" which on Alpine Linux only
- # supports servers and not pools.
- expected_content = "servers {0}\npools []\n".format(hosts)
-
- m_sysd.return_value = uses_systemd
- with mock.patch('cloudinit.config.cc_ntp.util') as m_util:
- # allow use of util.mergemanydict
- m_util.mergemanydict.side_effect = util.mergemanydict
- # default client is present
- m_subp.which.return_value = True
- # use the config 'enabled' value
- m_util.is_false.return_value = util.is_false(
- cfg['ntp']['enabled'])
- cc_ntp.handle('notimportant', cfg, mycloud, None, None)
- m_dsubp.subp.assert_called_with(
- expected_service_call, capture=True)
-
- self.assertEqual(expected_content, util.load_file(confpath))
-
- @mock.patch('cloudinit.util.system_info')
- def test_opensuse_picks_chrony(self, m_sysinfo):
- """Test opensuse picks chrony or ntp on certain distro versions"""
- # < 15.0 => ntp
- m_sysinfo.return_value = {
- 'dist': ('openSUSE', '13.2', 'Harlequin')
- }
- mycloud = self._get_cloud('opensuse')
- expected_client = mycloud.distro.preferred_ntp_clients[0]
- self.assertEqual('ntp', expected_client)
-
- # >= 15.0 and not openSUSE => chrony
- m_sysinfo.return_value = {
- 'dist': ('SLES', '15.0', 'SUSE Linux Enterprise Server 15')
- }
- mycloud = self._get_cloud('sles')
- expected_client = mycloud.distro.preferred_ntp_clients[0]
- self.assertEqual('chrony', expected_client)
-
- # >= 15.0 and openSUSE and ver != 42 => chrony
- m_sysinfo.return_value = {
- 'dist': ('openSUSE Tumbleweed', '20180326', 'timbleweed')
- }
- mycloud = self._get_cloud('opensuse')
- expected_client = mycloud.distro.preferred_ntp_clients[0]
- self.assertEqual('chrony', expected_client)
-
- @mock.patch('cloudinit.util.system_info')
- def test_ubuntu_xenial_picks_ntp(self, m_sysinfo):
- """Test Ubuntu picks ntp on xenial release"""
-
- m_sysinfo.return_value = {'dist': ('Ubuntu', '16.04', 'xenial')}
- mycloud = self._get_cloud('ubuntu')
- expected_client = mycloud.distro.preferred_ntp_clients[0]
- self.assertEqual('ntp', expected_client)
-
- @mock.patch('cloudinit.config.cc_ntp.subp.which')
- def test_snappy_system_picks_timesyncd(self, m_which):
- """Test snappy systems prefer installed clients"""
-
- # we are on ubuntu-core here
- self.m_snappy.return_value = True
-
- # ubuntu core systems will have timesyncd installed
- m_which.side_effect = iter([None, '/lib/systemd/systemd-timesyncd',
- None, None, None])
- distro = 'ubuntu'
- mycloud = self._get_cloud(distro)
- distro_configs = cc_ntp.distro_ntp_client_configs(distro)
- expected_client = 'systemd-timesyncd'
- expected_cfg = distro_configs[expected_client]
- expected_calls = []
- # we only get to timesyncd
- for client in mycloud.distro.preferred_ntp_clients[0:2]:
- cfg = distro_configs[client]
- expected_calls.append(mock.call(cfg['check_exe']))
- result = cc_ntp.select_ntp_client(None, mycloud.distro)
- m_which.assert_has_calls(expected_calls)
- self.assertEqual(sorted(expected_cfg), sorted(cfg))
- self.assertEqual(sorted(expected_cfg), sorted(result))
-
- @mock.patch('cloudinit.config.cc_ntp.subp.which')
- def test_ntp_distro_searches_all_preferred_clients(self, m_which):
- """Test select_ntp_client search all distro perferred clients """
- # nothing is installed
- m_which.return_value = None
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- distro_configs = cc_ntp.distro_ntp_client_configs(distro)
- expected_client = mycloud.distro.preferred_ntp_clients[0]
- expected_cfg = distro_configs[expected_client]
- expected_calls = []
- for client in mycloud.distro.preferred_ntp_clients:
- cfg = distro_configs[client]
- expected_calls.append(mock.call(cfg['check_exe']))
- cc_ntp.select_ntp_client({}, mycloud.distro)
- m_which.assert_has_calls(expected_calls)
- self.assertEqual(sorted(expected_cfg), sorted(cfg))
-
- @mock.patch('cloudinit.config.cc_ntp.subp.which')
- def test_user_cfg_ntp_client_auto_uses_distro_clients(self, m_which):
- """Test user_cfg.ntp_client='auto' defaults to distro search"""
- # nothing is installed
- m_which.return_value = None
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- distro_configs = cc_ntp.distro_ntp_client_configs(distro)
- expected_client = mycloud.distro.preferred_ntp_clients[0]
- expected_cfg = distro_configs[expected_client]
- expected_calls = []
- for client in mycloud.distro.preferred_ntp_clients:
- cfg = distro_configs[client]
- expected_calls.append(mock.call(cfg['check_exe']))
- cc_ntp.select_ntp_client('auto', mycloud.distro)
- m_which.assert_has_calls(expected_calls)
- self.assertEqual(sorted(expected_cfg), sorted(cfg))
-
- @mock.patch('cloudinit.config.cc_ntp.write_ntp_config_template')
- @mock.patch('cloudinit.cloud.Cloud.get_template_filename')
- @mock.patch('cloudinit.config.cc_ntp.subp.which')
- def test_ntp_custom_client_overrides_installed_clients(self, m_which,
- m_tmpfn, m_write):
- """Test user client is installed despite other clients present """
- client = 'ntpdate'
- cfg = {'ntp': {'ntp_client': client}}
- for distro in cc_ntp.distros:
- # client is not installed
- m_which.side_effect = iter([None])
- mycloud = self._get_cloud(distro)
- with mock.patch.object(mycloud.distro,
- 'install_packages') as m_install:
- cc_ntp.handle('notimportant', cfg, mycloud, None, None)
- m_install.assert_called_with([client])
- m_which.assert_called_with(client)
-
- @mock.patch('cloudinit.config.cc_ntp.subp.which')
- def test_ntp_system_config_overrides_distro_builtin_clients(self, m_which):
- """Test distro system_config overrides builtin preferred ntp clients"""
- system_client = 'chrony'
- sys_cfg = {'ntp_client': system_client}
- # no clients installed
- m_which.return_value = None
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro, sys_cfg=sys_cfg)
- distro_configs = cc_ntp.distro_ntp_client_configs(distro)
- expected_cfg = distro_configs[system_client]
- result = cc_ntp.select_ntp_client(None, mycloud.distro)
- self.assertEqual(sorted(expected_cfg), sorted(result))
- m_which.assert_has_calls([])
-
- @mock.patch('cloudinit.config.cc_ntp.subp.which')
- def test_ntp_user_config_overrides_system_cfg(self, m_which):
- """Test user-data overrides system_config ntp_client"""
- system_client = 'chrony'
- sys_cfg = {'ntp_client': system_client}
- user_client = 'systemd-timesyncd'
- # no clients installed
- m_which.return_value = None
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro, sys_cfg=sys_cfg)
- distro_configs = cc_ntp.distro_ntp_client_configs(distro)
- expected_cfg = distro_configs[user_client]
- result = cc_ntp.select_ntp_client(user_client, mycloud.distro)
- self.assertEqual(sorted(expected_cfg), sorted(result))
- m_which.assert_has_calls([])
-
- @mock.patch('cloudinit.config.cc_ntp.install_ntp_client')
- def test_ntp_user_provided_config_with_template(self, m_install):
- custom = r'\n#MyCustomTemplate'
- user_template = NTP_TEMPLATE + custom
- confpath = os.path.join(self.new_root, 'etc/myntp/myntp.conf')
- cfg = {
- 'ntp': {
- 'pools': ['mypool.org'],
- 'ntp_client': 'myntpd',
- 'config': {
- 'check_exe': 'myntpd',
- 'confpath': confpath,
- 'packages': ['myntp'],
- 'service_name': 'myntp',
- 'template': user_template,
- }
- }
- }
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR'
- with mock.patch(mock_path, self.new_root):
- cc_ntp.handle('notimportant', cfg, mycloud, None, None)
- self.assertEqual(
- "servers []\npools ['mypool.org']\n%s" % custom,
- util.load_file(confpath))
-
- @mock.patch('cloudinit.config.cc_ntp.supplemental_schema_validation')
- @mock.patch('cloudinit.config.cc_ntp.install_ntp_client')
- @mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
- def test_ntp_user_provided_config_template_only(self, m_select, m_install,
- m_schema):
- """Test custom template for default client"""
- custom = r'\n#MyCustomTemplate'
- user_template = NTP_TEMPLATE + custom
- client = 'chrony'
- cfg = {
- 'pools': ['mypool.org'],
- 'ntp_client': client,
- 'config': {
- 'template': user_template,
- }
- }
- expected_merged_cfg = {
- 'check_exe': 'chronyd',
- 'confpath': '{tmpdir}/client.conf'.format(tmpdir=self.new_root),
- 'template_name': 'client.conf', 'template': user_template,
- 'service_name': 'chrony', 'packages': ['chrony']}
- for distro in cc_ntp.distros:
- mycloud = self._get_cloud(distro)
- ntpconfig = self._mock_ntp_client_config(client=client,
- distro=distro)
- confpath = ntpconfig['confpath']
- m_select.return_value = ntpconfig
- mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR'
- with mock.patch(mock_path, self.new_root):
- cc_ntp.handle('notimportant',
- {'ntp': cfg}, mycloud, None, None)
- self.assertEqual(
- "servers []\npools ['mypool.org']\n%s" % custom,
- util.load_file(confpath))
- m_schema.assert_called_with(expected_merged_cfg)
-
-
-class TestSupplementalSchemaValidation(CiTestCase):
-
- def test_error_on_missing_keys(self):
- """ValueError raised reporting any missing required ntp:config keys"""
- cfg = {}
- match = (r'Invalid ntp configuration:\\nMissing required ntp:config'
- ' keys: check_exe, confpath, packages, service_name')
- with self.assertRaisesRegex(ValueError, match):
- cc_ntp.supplemental_schema_validation(cfg)
-
- def test_error_requiring_either_template_or_template_name(self):
- """ValueError raised if both template not template_name are None."""
- cfg = {'confpath': 'someconf', 'check_exe': '', 'service_name': '',
- 'template': None, 'template_name': None, 'packages': []}
- match = (r'Invalid ntp configuration:\\nEither ntp:config:template'
- ' or ntp:config:template_name values are required')
- with self.assertRaisesRegex(ValueError, match):
- cc_ntp.supplemental_schema_validation(cfg)
-
- def test_error_on_non_list_values(self):
- """ValueError raised when packages is not of type list."""
- cfg = {'confpath': 'someconf', 'check_exe': '', 'service_name': '',
- 'template': 'asdf', 'template_name': None, 'packages': 'NOPE'}
- match = (r'Invalid ntp configuration:\\nExpected a list of required'
- ' package names for ntp:config:packages. Found \\(NOPE\\)')
- with self.assertRaisesRegex(ValueError, match):
- cc_ntp.supplemental_schema_validation(cfg)
-
- def test_error_on_non_string_values(self):
- """ValueError raised for any values expected as string type."""
- cfg = {'confpath': 1, 'check_exe': 2, 'service_name': 3,
- 'template': 4, 'template_name': 5, 'packages': []}
- errors = [
- 'Expected a config file path ntp:config:confpath. Found (1)',
- 'Expected a string type for ntp:config:check_exe. Found (2)',
- 'Expected a string type for ntp:config:service_name. Found (3)',
- 'Expected a string type for ntp:config:template. Found (4)',
- 'Expected a string type for ntp:config:template_name. Found (5)']
- with self.assertRaises(ValueError) as context_mgr:
- cc_ntp.supplemental_schema_validation(cfg)
- error_msg = str(context_mgr.exception)
- for error in errors:
- self.assertIn(error, error_msg)
-
-# vi: ts=4 expandtab