summaryrefslogtreecommitdiff
path: root/tests/unittests/test_handler
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unittests/test_handler')
-rw-r--r--tests/unittests/test_handler/test_handler_apk_configure.py299
-rw-r--r--tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py11
-rw-r--r--tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py5
-rw-r--r--tests/unittests/test_handler/test_handler_apt_source_v1.py20
-rw-r--r--tests/unittests/test_handler/test_handler_apt_source_v3.py58
-rw-r--r--tests/unittests/test_handler/test_handler_bootcmd.py6
-rw-r--r--tests/unittests/test_handler/test_handler_ca_certs.py44
-rw-r--r--tests/unittests/test_handler/test_handler_chef.py16
-rw-r--r--tests/unittests/test_handler/test_handler_disk_setup.py8
-rw-r--r--tests/unittests/test_handler/test_handler_etc_hosts.py8
-rw-r--r--tests/unittests/test_handler/test_handler_growpart.py16
-rw-r--r--tests/unittests/test_handler/test_handler_landscape.py6
-rw-r--r--tests/unittests/test_handler/test_handler_locale.py4
-rw-r--r--tests/unittests/test_handler/test_handler_lxd.py28
-rw-r--r--tests/unittests/test_handler/test_handler_mcollective.py7
-rw-r--r--tests/unittests/test_handler/test_handler_mounts.py138
-rw-r--r--tests/unittests/test_handler/test_handler_ntp.py180
-rw-r--r--tests/unittests/test_handler/test_handler_power_state.py29
-rw-r--r--tests/unittests/test_handler/test_handler_puppet.py51
-rw-r--r--tests/unittests/test_handler/test_handler_runcmd.py4
-rw-r--r--tests/unittests/test_handler/test_handler_seed_random.py5
-rw-r--r--tests/unittests/test_handler/test_handler_spacewalk.py20
-rw-r--r--tests/unittests/test_handler/test_handler_write_files.py85
-rw-r--r--tests/unittests/test_handler/test_handler_yum_add_repo.py17
-rw-r--r--tests/unittests/test_handler/test_handler_zypper_add_repo.py11
-rw-r--r--tests/unittests/test_handler/test_schema.py112
26 files changed, 944 insertions, 244 deletions
diff --git a/tests/unittests/test_handler/test_handler_apk_configure.py b/tests/unittests/test_handler/test_handler_apk_configure.py
new file mode 100644
index 00000000..8acc0b33
--- /dev/null
+++ b/tests/unittests/test_handler/test_handler_apk_configure.py
@@ -0,0 +1,299 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+""" test_apk_configure
+Test creation of repositories file
+"""
+
+import logging
+import os
+import textwrap
+
+from cloudinit import (cloud, helpers, util)
+
+from cloudinit.config import cc_apk_configure
+from cloudinit.tests.helpers import (FilesystemMockingTestCase, mock)
+
+REPO_FILE = "/etc/apk/repositories"
+DEFAULT_MIRROR_URL = "https://alpine.global.ssl.fastly.net/alpine"
+CC_APK = 'cloudinit.config.cc_apk_configure'
+
+
+class TestNoConfig(FilesystemMockingTestCase):
+ def setUp(self):
+ super(TestNoConfig, self).setUp()
+ self.add_patch(CC_APK + '._write_repositories_file', 'm_write_repos')
+ self.name = "apk-configure"
+ self.cloud_init = None
+ self.log = logging.getLogger("TestNoConfig")
+ self.args = []
+
+ def test_no_config(self):
+ """
+ Test that nothing is done if no apk-configure
+ configuration is provided.
+ """
+ config = util.get_builtin_cfg()
+
+ cc_apk_configure.handle(self.name, config, self.cloud_init,
+ self.log, self.args)
+
+ self.assertEqual(0, self.m_write_repos.call_count)
+
+
+class TestConfig(FilesystemMockingTestCase):
+ def setUp(self):
+ super(TestConfig, self).setUp()
+ self.new_root = self.tmp_dir()
+ self.new_root = self.reRoot(root=self.new_root)
+ for dirname in ['tmp', 'etc/apk']:
+ util.ensure_dir(os.path.join(self.new_root, dirname))
+ self.paths = helpers.Paths({'templates_dir': self.new_root})
+ self.name = "apk-configure"
+ self.cloud = cloud.Cloud(None, self.paths, None, None, None)
+ self.log = logging.getLogger("TestNoConfig")
+ self.args = []
+
+ @mock.patch(CC_APK + '._write_repositories_file')
+ def test_no_repo_settings(self, m_write_repos):
+ """
+ Test that nothing is written if the 'alpine-repo' key
+ is not present.
+ """
+ config = {"apk_repos": {}}
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ self.assertEqual(0, m_write_repos.call_count)
+
+ @mock.patch(CC_APK + '._write_repositories_file')
+ def test_empty_repo_settings(self, m_write_repos):
+ """
+ Test that nothing is written if 'alpine_repo' list is empty.
+ """
+ config = {"apk_repos": {"alpine_repo": []}}
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ self.assertEqual(0, m_write_repos.call_count)
+
+ def test_only_main_repo(self):
+ """
+ Test when only details of main repo is written to file.
+ """
+ alpine_version = 'v3.12'
+ config = {
+ "apk_repos": {
+ "alpine_repo": {
+ "version": alpine_version
+ }
+ }
+ }
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ expected_content = textwrap.dedent("""\
+ #
+ # Created by cloud-init
+ #
+ # This file is written on first boot of an instance
+ #
+
+ {0}/{1}/main
+
+ """.format(DEFAULT_MIRROR_URL, alpine_version))
+
+ self.assertEqual(expected_content, util.load_file(REPO_FILE))
+
+ def test_main_and_community_repos(self):
+ """
+ Test when only details of main and community repos are
+ written to file.
+ """
+ alpine_version = 'edge'
+ config = {
+ "apk_repos": {
+ "alpine_repo": {
+ "version": alpine_version,
+ "community_enabled": True
+ }
+ }
+ }
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ expected_content = textwrap.dedent("""\
+ #
+ # Created by cloud-init
+ #
+ # This file is written on first boot of an instance
+ #
+
+ {0}/{1}/main
+ {0}/{1}/community
+
+ """.format(DEFAULT_MIRROR_URL, alpine_version))
+
+ self.assertEqual(expected_content, util.load_file(REPO_FILE))
+
+ def test_main_community_testing_repos(self):
+ """
+ Test when details of main, community and testing repos
+ are written to file.
+ """
+ alpine_version = 'v3.12'
+ config = {
+ "apk_repos": {
+ "alpine_repo": {
+ "version": alpine_version,
+ "community_enabled": True,
+ "testing_enabled": True
+ }
+ }
+ }
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ expected_content = textwrap.dedent("""\
+ #
+ # Created by cloud-init
+ #
+ # This file is written on first boot of an instance
+ #
+
+ {0}/{1}/main
+ {0}/{1}/community
+ #
+ # Testing - using with non-Edge installation may cause problems!
+ #
+ {0}/edge/testing
+
+ """.format(DEFAULT_MIRROR_URL, alpine_version))
+
+ self.assertEqual(expected_content, util.load_file(REPO_FILE))
+
+ def test_edge_main_community_testing_repos(self):
+ """
+ Test when details of main, community and testing repos
+ for Edge version of Alpine are written to file.
+ """
+ alpine_version = 'edge'
+ config = {
+ "apk_repos": {
+ "alpine_repo": {
+ "version": alpine_version,
+ "community_enabled": True,
+ "testing_enabled": True
+ }
+ }
+ }
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ expected_content = textwrap.dedent("""\
+ #
+ # Created by cloud-init
+ #
+ # This file is written on first boot of an instance
+ #
+
+ {0}/{1}/main
+ {0}/{1}/community
+ {0}/{1}/testing
+
+ """.format(DEFAULT_MIRROR_URL, alpine_version))
+
+ self.assertEqual(expected_content, util.load_file(REPO_FILE))
+
+ def test_main_community_testing_local_repos(self):
+ """
+ Test when details of main, community, testing and
+ local repos are written to file.
+ """
+ alpine_version = 'v3.12'
+ local_repo_url = 'http://some.mirror/whereever'
+ config = {
+ "apk_repos": {
+ "alpine_repo": {
+ "version": alpine_version,
+ "community_enabled": True,
+ "testing_enabled": True
+ },
+ "local_repo_base_url": local_repo_url
+ }
+ }
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ expected_content = textwrap.dedent("""\
+ #
+ # Created by cloud-init
+ #
+ # This file is written on first boot of an instance
+ #
+
+ {0}/{1}/main
+ {0}/{1}/community
+ #
+ # Testing - using with non-Edge installation may cause problems!
+ #
+ {0}/edge/testing
+
+ #
+ # Local repo
+ #
+ {2}/{1}
+
+ """.format(DEFAULT_MIRROR_URL, alpine_version, local_repo_url))
+
+ self.assertEqual(expected_content, util.load_file(REPO_FILE))
+
+ def test_edge_main_community_testing_local_repos(self):
+ """
+ Test when details of main, community, testing and local repos
+ for Edge version of Alpine are written to file.
+ """
+ alpine_version = 'edge'
+ local_repo_url = 'http://some.mirror/whereever'
+ config = {
+ "apk_repos": {
+ "alpine_repo": {
+ "version": alpine_version,
+ "community_enabled": True,
+ "testing_enabled": True
+ },
+ "local_repo_base_url": local_repo_url
+ }
+ }
+
+ cc_apk_configure.handle(self.name, config, self.cloud, self.log,
+ self.args)
+
+ expected_content = textwrap.dedent("""\
+ #
+ # Created by cloud-init
+ #
+ # This file is written on first boot of an instance
+ #
+
+ {0}/{1}/main
+ {0}/{1}/community
+ {0}/edge/testing
+
+ #
+ # Local repo
+ #
+ {2}/{1}
+
+ """.format(DEFAULT_MIRROR_URL, alpine_version, local_repo_url))
+
+ self.assertEqual(expected_content, util.load_file(REPO_FILE))
+
+
+# vi: ts=4 expandtab
diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py
index 69009a44..369480be 100644
--- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py
+++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py
@@ -13,6 +13,7 @@ from cloudinit import cloud
from cloudinit import distros
from cloudinit import helpers
from cloudinit import templater
+from cloudinit import subp
from cloudinit import util
from cloudinit.config import cc_apt_configure
@@ -66,7 +67,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
"""
def setUp(self):
super(TestAptSourceConfigSourceList, self).setUp()
- self.subp = util.subp
+ self.subp = subp.subp
self.new_root = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.new_root)
@@ -100,6 +101,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
cfg = {'apt_mirror_search': mirror}
else:
cfg = {'apt_mirror': mirror}
+
mycloud = self._get_cloud(distro)
with mock.patch.object(util, 'write_file') as mockwf:
@@ -107,8 +109,9 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
return_value="faketmpl") as mocklf:
with mock.patch.object(os.path, 'isfile',
return_value=True) as mockisfile:
- with mock.patch.object(templater, 'render_string',
- return_value="fake") as mockrnd:
+ with mock.patch.object(
+ templater, 'render_string',
+ return_value='fake') as mockrnd:
with mock.patch.object(util, 'rename'):
cc_apt_configure.handle("test", cfg, mycloud,
LOG, None)
@@ -176,7 +179,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
# the second mock restores the original subp
with mock.patch.object(util, 'write_file') as mockwrite:
- with mock.patch.object(util, 'subp', self.subp):
+ with mock.patch.object(subp, 'subp', self.subp):
with mock.patch.object(Distro, 'get_primary_arch',
return_value='amd64'):
cc_apt_configure.handle("notimportant", cfg, mycloud,
diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py
index 0aa3d51a..b96fd4d4 100644
--- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py
+++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py
@@ -13,6 +13,7 @@ from unittest.mock import call
from cloudinit import cloud
from cloudinit import distros
from cloudinit import helpers
+from cloudinit import subp
from cloudinit import util
from cloudinit.config import cc_apt_configure
@@ -94,7 +95,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
"""TestAptSourceConfigSourceList - Class to test sources list rendering"""
def setUp(self):
super(TestAptSourceConfigSourceList, self).setUp()
- self.subp = util.subp
+ self.subp = subp.subp
self.new_root = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.new_root)
@@ -222,7 +223,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
# the second mock restores the original subp
with mock.patch.object(util, 'write_file') as mockwrite:
- with mock.patch.object(util, 'subp', self.subp):
+ with mock.patch.object(subp, 'subp', self.subp):
with mock.patch.object(Distro, 'get_primary_arch',
return_value='amd64'):
cc_apt_configure.handle("notimportant", cfg, mycloud,
diff --git a/tests/unittests/test_handler/test_handler_apt_source_v1.py b/tests/unittests/test_handler/test_handler_apt_source_v1.py
index 866752ef..367971cb 100644
--- a/tests/unittests/test_handler/test_handler_apt_source_v1.py
+++ b/tests/unittests/test_handler/test_handler_apt_source_v1.py
@@ -14,6 +14,7 @@ from unittest.mock import call
from cloudinit.config import cc_apt_configure
from cloudinit import gpg
+from cloudinit import subp
from cloudinit import util
from cloudinit.tests.helpers import TestCase
@@ -42,10 +43,17 @@ class FakeDistro(object):
return
+class FakeDatasource:
+ """Fake Datasource helper object"""
+ def __init__(self):
+ self.region = 'region'
+
+
class FakeCloud(object):
"""Fake Cloud helper object"""
def __init__(self):
self.distro = FakeDistro()
+ self.datasource = FakeDatasource()
class TestAptSourceConfig(TestCase):
@@ -271,7 +279,7 @@ class TestAptSourceConfig(TestCase):
"""
cfg = self.wrapv1conf(cfg)
- with mock.patch.object(util, 'subp',
+ with mock.patch.object(subp, 'subp',
return_value=('fakekey 1234', '')) as mockobj:
cc_apt_configure.handle("test", cfg, self.fakecloud, None, None)
@@ -356,7 +364,7 @@ class TestAptSourceConfig(TestCase):
"""
cfg = self.wrapv1conf([cfg])
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
cc_apt_configure.handle("test", cfg, self.fakecloud, None, None)
mockobj.assert_called_with(['apt-key', 'add', '-'],
@@ -398,7 +406,7 @@ class TestAptSourceConfig(TestCase):
'filename': self.aptlistfile}
cfg = self.wrapv1conf([cfg])
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
cc_apt_configure.handle("test", cfg, self.fakecloud, None, None)
mockobj.assert_called_once_with(['apt-key', 'add', '-'],
@@ -413,7 +421,7 @@ class TestAptSourceConfig(TestCase):
'filename': self.aptlistfile}
cfg = self.wrapv1conf([cfg])
- with mock.patch.object(util, 'subp',
+ with mock.patch.object(subp, 'subp',
return_value=('fakekey 1212', '')) as mockobj:
cc_apt_configure.handle("test", cfg, self.fakecloud, None, None)
@@ -476,7 +484,7 @@ class TestAptSourceConfig(TestCase):
'filename': self.aptlistfile}
cfg = self.wrapv1conf([cfg])
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
cc_apt_configure.handle("test", cfg, self.fakecloud, None, None)
mockobj.assert_called_once_with(['add-apt-repository',
'ppa:smoser/cloud-init-test'],
@@ -495,7 +503,7 @@ class TestAptSourceConfig(TestCase):
'filename': self.aptlistfile3}
cfg = self.wrapv1conf([cfg1, cfg2, cfg3])
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
cc_apt_configure.handle("test", cfg, self.fakecloud,
None, None)
calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'],
diff --git a/tests/unittests/test_handler/test_handler_apt_source_v3.py b/tests/unittests/test_handler/test_handler_apt_source_v3.py
index 90949b6d..ac847238 100644
--- a/tests/unittests/test_handler/test_handler_apt_source_v3.py
+++ b/tests/unittests/test_handler/test_handler_apt_source_v3.py
@@ -18,6 +18,7 @@ from cloudinit import cloud
from cloudinit import distros
from cloudinit import gpg
from cloudinit import helpers
+from cloudinit import subp
from cloudinit import util
from cloudinit.config import cc_apt_configure
@@ -48,6 +49,18 @@ MOCK_LSB_RELEASE_DATA = {
'release': '18.04', 'codename': 'bionic'}
+class FakeDatasource:
+ """Fake Datasource helper object"""
+ def __init__(self):
+ self.region = 'region'
+
+
+class FakeCloud:
+ """Fake Cloud helper object"""
+ def __init__(self):
+ self.datasource = FakeDatasource()
+
+
class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
"""TestAptSourceConfig
Main Class to test apt configs
@@ -221,7 +234,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
"""
params = self._get_default_params()
- with mock.patch("cloudinit.util.subp",
+ with mock.patch("cloudinit.subp.subp",
return_value=('fakekey 1234', '')) as mockobj:
self._add_apt_sources(cfg, TARGET, template_params=params,
aa_repo_match=self.matcher)
@@ -296,7 +309,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
' xenial main'),
'key': "fakekey 4321"}}
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
self._add_apt_sources(cfg, TARGET, template_params=params,
aa_repo_match=self.matcher)
@@ -318,7 +331,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
params = self._get_default_params()
cfg = {self.aptlistfile: {'key': "fakekey 4242"}}
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
self._add_apt_sources(cfg, TARGET, template_params=params,
aa_repo_match=self.matcher)
@@ -333,7 +346,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
params = self._get_default_params()
cfg = {self.aptlistfile: {'keyid': "03683F77"}}
- with mock.patch.object(util, 'subp',
+ with mock.patch.object(subp, 'subp',
return_value=('fakekey 1212', '')) as mockobj:
self._add_apt_sources(cfg, TARGET, template_params=params,
aa_repo_match=self.matcher)
@@ -416,7 +429,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
params = self._get_default_params()
cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'}}
- with mock.patch("cloudinit.util.subp") as mockobj:
+ with mock.patch("cloudinit.subp.subp") as mockobj:
self._add_apt_sources(cfg, TARGET, template_params=params,
aa_repo_match=self.matcher)
mockobj.assert_any_call(['add-apt-repository',
@@ -432,7 +445,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
self.aptlistfile2: {'source': 'ppa:smoser/cloud-init-test2'},
self.aptlistfile3: {'source': 'ppa:smoser/cloud-init-test3'}}
- with mock.patch("cloudinit.util.subp") as mockobj:
+ with mock.patch("cloudinit.subp.subp") as mockobj:
self._add_apt_sources(cfg, TARGET, template_params=params,
aa_repo_match=self.matcher)
calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'],
@@ -470,7 +483,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
fromfn = ("%s/%s_%s" % (pre, archive, post))
tofn = ("%s/test.ubuntu.com_%s" % (pre, post))
- mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, arch)
+ mirrors = cc_apt_configure.find_apt_mirror_info(cfg, FakeCloud(), arch)
self.assertEqual(mirrors['MIRROR'],
"http://test.ubuntu.com/%s/" % component)
@@ -558,7 +571,8 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
"security": [{'arches': ["default"],
"uri": smir}]}
- mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, 'amd64')
+ mirrors = cc_apt_configure.find_apt_mirror_info(
+ cfg, FakeCloud(), 'amd64')
self.assertEqual(mirrors['MIRROR'],
pmir)
@@ -593,7 +607,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
"security": [{'arches': ["default"], "uri": "nothis-security"},
{'arches': [arch], "uri": smir}]}
- mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, arch)
+ mirrors = cc_apt_configure.find_apt_mirror_info(cfg, FakeCloud(), arch)
self.assertEqual(mirrors['PRIMARY'], pmir)
self.assertEqual(mirrors['MIRROR'], pmir)
@@ -612,7 +626,8 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
{'arches': ["default"],
"uri": smir}]}
- mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, 'amd64')
+ mirrors = cc_apt_configure.find_apt_mirror_info(
+ cfg, FakeCloud(), 'amd64')
self.assertEqual(mirrors['MIRROR'],
pmir)
@@ -670,9 +685,9 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
"security": [{'arches': ["default"],
"search": ["sfailme", smir]}]}
- with mock.patch.object(cc_apt_configure, 'search_for_mirror',
+ with mock.patch.object(cc_apt_configure.util, 'search_for_mirror',
side_effect=[pmir, smir]) as mocksearch:
- mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None,
+ mirrors = cc_apt_configure.find_apt_mirror_info(cfg, FakeCloud(),
'amd64')
calls = [call(["pfailme", pmir]),
@@ -709,9 +724,10 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase):
mockgm.assert_has_calls(calls)
# should not be called, since primary is specified
- with mock.patch.object(cc_apt_configure,
+ with mock.patch.object(cc_apt_configure.util,
'search_for_mirror') as mockse:
- mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, arch)
+ mirrors = cc_apt_configure.find_apt_mirror_info(
+ cfg, FakeCloud(), arch)
mockse.assert_not_called()
self.assertEqual(mirrors['MIRROR'],
@@ -974,7 +990,7 @@ deb http://ubuntu.com/ubuntu/ xenial-proposed main""")
mocksdns.assert_has_calls(calls)
# first return is for the non-dns call before
- with mock.patch.object(cc_apt_configure, 'search_for_mirror',
+ with mock.patch.object(cc_apt_configure.util, 'search_for_mirror',
side_effect=[None, pmir, None, smir]) as mockse:
mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch)
@@ -996,7 +1012,7 @@ deb http://ubuntu.com/ubuntu/ xenial-proposed main""")
class TestDebconfSelections(TestCase):
- @mock.patch("cloudinit.config.cc_apt_configure.util.subp")
+ @mock.patch("cloudinit.config.cc_apt_configure.subp.subp")
def test_set_sel_appends_newline_if_absent(self, m_subp):
"""Automatically append a newline to debconf-set-selections config."""
selections = b'some/setting boolean true'
@@ -1033,7 +1049,9 @@ class TestDebconfSelections(TestCase):
# assumes called with *args value.
selections = m_set_sel.call_args_list[0][0][0].decode()
- missing = [l for l in lines if l not in selections.splitlines()]
+ missing = [
+ line for line in lines if line not in selections.splitlines()
+ ]
self.assertEqual([], missing)
@mock.patch("cloudinit.config.cc_apt_configure.dpkg_reconfigure")
@@ -1079,7 +1097,7 @@ class TestDebconfSelections(TestCase):
self.assertTrue(m_get_inst.called)
self.assertEqual(m_dpkg_r.call_count, 0)
- @mock.patch("cloudinit.config.cc_apt_configure.util.subp")
+ @mock.patch("cloudinit.config.cc_apt_configure.subp.subp")
def test_dpkg_reconfigure_does_reconfigure(self, m_subp):
target = "/foo-target"
@@ -1102,12 +1120,12 @@ class TestDebconfSelections(TestCase):
'cloud-init']
self.assertEqual(expected, found)
- @mock.patch("cloudinit.config.cc_apt_configure.util.subp")
+ @mock.patch("cloudinit.config.cc_apt_configure.subp.subp")
def test_dpkg_reconfigure_not_done_on_no_data(self, m_subp):
cc_apt_configure.dpkg_reconfigure([])
m_subp.assert_not_called()
- @mock.patch("cloudinit.config.cc_apt_configure.util.subp")
+ @mock.patch("cloudinit.config.cc_apt_configure.subp.subp")
def test_dpkg_reconfigure_not_done_if_no_cleaners(self, m_subp):
cc_apt_configure.dpkg_reconfigure(['pkgfoo', 'pkgbar'])
m_subp.assert_not_called()
diff --git a/tests/unittests/test_handler/test_handler_bootcmd.py b/tests/unittests/test_handler/test_handler_bootcmd.py
index a76760fa..b53d60d4 100644
--- a/tests/unittests/test_handler/test_handler_bootcmd.py
+++ b/tests/unittests/test_handler/test_handler_bootcmd.py
@@ -2,7 +2,7 @@
from cloudinit.config.cc_bootcmd import handle, schema
from cloudinit.sources import DataSourceNone
-from cloudinit import (distros, helpers, cloud, util)
+from cloudinit import (distros, helpers, cloud, subp, util)
from cloudinit.tests.helpers import (
CiTestCase, mock, SchemaTestCaseMixin, skipUnlessJsonSchema)
@@ -36,7 +36,7 @@ class TestBootcmd(CiTestCase):
def setUp(self):
super(TestBootcmd, self).setUp()
- self.subp = util.subp
+ self.subp = subp.subp
self.new_root = self.tmp_dir()
def _get_cloud(self, distro):
@@ -130,7 +130,7 @@ class TestBootcmd(CiTestCase):
with mock.patch(self._etmpfile_path, FakeExtendedTempFile):
with self.allow_subp(['/bin/sh']):
- with self.assertRaises(util.ProcessExecutionError) as ctxt:
+ with self.assertRaises(subp.ProcessExecutionError) as ctxt:
handle('does-not-matter', valid_config, cc, LOG, [])
self.assertIn(
'Unexpected error while running command.\n'
diff --git a/tests/unittests/test_handler/test_handler_ca_certs.py b/tests/unittests/test_handler/test_handler_ca_certs.py
index 5b4105dd..e74a0a08 100644
--- a/tests/unittests/test_handler/test_handler_ca_certs.py
+++ b/tests/unittests/test_handler/test_handler_ca_certs.py
@@ -1,8 +1,10 @@
# This file is part of cloud-init. See LICENSE file for license information.
from cloudinit import cloud
+from cloudinit import distros
from cloudinit.config import cc_ca_certs
from cloudinit import helpers
+from cloudinit import subp
from cloudinit import util
from cloudinit.tests.helpers import TestCase
@@ -11,13 +13,9 @@ import logging
import shutil
import tempfile
import unittest
+from contextlib import ExitStack
from unittest import mock
-try:
- from contextlib import ExitStack
-except ImportError:
- from contextlib2 import ExitStack
-
class TestNoConfig(unittest.TestCase):
def setUp(self):
@@ -49,8 +47,9 @@ class TestConfig(TestCase):
def setUp(self):
super(TestConfig, self).setUp()
self.name = "ca-certs"
+ distro = self._fetch_distro('ubuntu')
self.paths = None
- self.cloud = cloud.Cloud(None, self.paths, None, None, None)
+ self.cloud = cloud.Cloud(None, self.paths, None, distro, None)
self.log = logging.getLogger("TestNoConfig")
self.args = []
@@ -65,6 +64,11 @@ class TestConfig(TestCase):
self.mock_remove = self.mocks.enter_context(
mock.patch.object(cc_ca_certs, 'remove_default_ca_certs'))
+ def _fetch_distro(self, kind):
+ cls = distros.fetch(kind)
+ paths = helpers.Paths({})
+ return cls(kind, {}, paths)
+
def test_no_trusted_list(self):
"""
Test that no certificates are written if the 'trusted' key is not
@@ -204,6 +208,28 @@ class TestAddCaCerts(TestCase):
mock_load.assert_called_once_with("/etc/ca-certificates.conf")
+ def test_single_cert_to_empty_existing_ca_file(self):
+ """Test adding a single certificate to the trusted CAs
+ when existing ca-certificates.conf is empty"""
+ cert = "CERT1\nLINE2\nLINE3"
+
+ expected = "cloud-init-ca-certs.crt\n"
+
+ with ExitStack() as mocks:
+ mock_write = mocks.enter_context(
+ mock.patch.object(util, 'write_file', autospec=True))
+ mock_stat = mocks.enter_context(
+ mock.patch("cloudinit.config.cc_ca_certs.os.stat")
+ )
+ mock_stat.return_value.st_size = 0
+
+ cc_ca_certs.add_ca_certs([cert])
+
+ mock_write.assert_has_calls([
+ mock.call("/usr/share/ca-certificates/cloud-init-ca-certs.crt",
+ cert, mode=0o644),
+ mock.call("/etc/ca-certificates.conf", expected, omode="wb")])
+
def test_multiple_certs(self):
"""Test adding multiple certificates to the trusted CAs."""
certs = ["CERT1\nLINE2\nLINE3", "CERT2\nLINE2\nLINE3"]
@@ -232,7 +258,7 @@ class TestAddCaCerts(TestCase):
class TestUpdateCaCerts(unittest.TestCase):
def test_commands(self):
- with mock.patch.object(util, 'subp') as mockobj:
+ with mock.patch.object(subp, 'subp') as mockobj:
cc_ca_certs.update_ca_certs()
mockobj.assert_called_once_with(
["update-ca-certificates"], capture=False)
@@ -254,9 +280,9 @@ class TestRemoveDefaultCaCerts(TestCase):
mock.patch.object(util, 'delete_dir_contents'))
mock_write = mocks.enter_context(
mock.patch.object(util, 'write_file'))
- mock_subp = mocks.enter_context(mock.patch.object(util, 'subp'))
+ mock_subp = mocks.enter_context(mock.patch.object(subp, 'subp'))
- cc_ca_certs.remove_default_ca_certs()
+ cc_ca_certs.remove_default_ca_certs('ubuntu')
mock_delete.assert_has_calls([
mock.call("/usr/share/ca-certificates/"),
diff --git a/tests/unittests/test_handler/test_handler_chef.py b/tests/unittests/test_handler/test_handler_chef.py
index 2dab3a54..7918c609 100644
--- a/tests/unittests/test_handler/test_handler_chef.py
+++ b/tests/unittests/test_handler/test_handler_chef.py
@@ -41,7 +41,7 @@ class TestInstallChefOmnibus(HttprettyTestCase):
httpretty.GET, cc_chef.OMNIBUS_URL, body=response, status=200)
ret = (None, None) # stdout, stderr but capture=False
- with mock.patch("cloudinit.config.cc_chef.util.subp_blob_in_tempfile",
+ with mock.patch("cloudinit.config.cc_chef.subp_blob_in_tempfile",
return_value=ret) as m_subp_blob:
cc_chef.install_chef_from_omnibus()
# admittedly whitebox, but assuming subp_blob_in_tempfile works
@@ -52,7 +52,7 @@ class TestInstallChefOmnibus(HttprettyTestCase):
m_subp_blob.call_args_list)
@mock.patch('cloudinit.config.cc_chef.url_helper.readurl')
- @mock.patch('cloudinit.config.cc_chef.util.subp_blob_in_tempfile')
+ @mock.patch('cloudinit.config.cc_chef.subp_blob_in_tempfile')
def test_install_chef_from_omnibus_retries_url(self, m_subp_blob, m_rdurl):
"""install_chef_from_omnibus retries OMNIBUS_URL upon failure."""
@@ -65,23 +65,23 @@ class TestInstallChefOmnibus(HttprettyTestCase):
cc_chef.install_chef_from_omnibus()
expected_kwargs = {'retries': cc_chef.OMNIBUS_URL_RETRIES,
'url': cc_chef.OMNIBUS_URL}
- self.assertItemsEqual(expected_kwargs, m_rdurl.call_args_list[0][1])
+ self.assertCountEqual(expected_kwargs, m_rdurl.call_args_list[0][1])
cc_chef.install_chef_from_omnibus(retries=10)
expected_kwargs = {'retries': 10,
'url': cc_chef.OMNIBUS_URL}
- self.assertItemsEqual(expected_kwargs, m_rdurl.call_args_list[1][1])
+ self.assertCountEqual(expected_kwargs, m_rdurl.call_args_list[1][1])
expected_subp_kwargs = {
'args': ['-v', '2.0'],
'basename': 'chef-omnibus-install',
'blob': m_rdurl.return_value.contents,
'capture': False
}
- self.assertItemsEqual(
+ self.assertCountEqual(
expected_subp_kwargs,
m_subp_blob.call_args_list[0][1])
@mock.patch("cloudinit.config.cc_chef.OMNIBUS_URL", OMNIBUS_URL_HTTP)
- @mock.patch('cloudinit.config.cc_chef.util.subp_blob_in_tempfile')
+ @mock.patch('cloudinit.config.cc_chef.subp_blob_in_tempfile')
def test_install_chef_from_omnibus_has_omnibus_version(self, m_subp_blob):
"""install_chef_from_omnibus provides version arg to OMNIBUS_URL."""
chef_outfile = self.tmp_path('chef.out', self.new_root)
@@ -97,7 +97,7 @@ class TestInstallChefOmnibus(HttprettyTestCase):
'blob': response,
'capture': False
}
- self.assertItemsEqual(expected_kwargs, called_kwargs)
+ self.assertCountEqual(expected_kwargs, called_kwargs)
class TestChef(FilesystemMockingTestCase):
@@ -130,6 +130,7 @@ class TestChef(FilesystemMockingTestCase):
# This should create a file of the format...
# Created by cloud-init v. 0.7.6 on Sat, 11 Oct 2014 23:57:21 +0000
+ chef_license "accept"
log_level :info
ssl_verify_mode :verify_none
log_location "/var/log/chef/client.log"
@@ -153,6 +154,7 @@ class TestChef(FilesystemMockingTestCase):
util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file)
cfg = {
'chef': {
+ 'chef_license': "accept",
'server_url': 'localhost',
'validation_name': 'bob',
'validation_key': "/etc/chef/vkey.pem",
diff --git a/tests/unittests/test_handler/test_handler_disk_setup.py b/tests/unittests/test_handler/test_handler_disk_setup.py
index 0e51f17a..4f4a57fa 100644
--- a/tests/unittests/test_handler/test_handler_disk_setup.py
+++ b/tests/unittests/test_handler/test_handler_disk_setup.py
@@ -44,7 +44,7 @@ class TestGetMbrHddSize(TestCase):
super(TestGetMbrHddSize, self).setUp()
self.patches = ExitStack()
self.subp = self.patches.enter_context(
- mock.patch.object(cc_disk_setup.util, 'subp'))
+ mock.patch.object(cc_disk_setup.subp, 'subp'))
def tearDown(self):
super(TestGetMbrHddSize, self).tearDown()
@@ -173,7 +173,7 @@ class TestUpdateFsSetupDevices(TestCase):
@mock.patch('cloudinit.config.cc_disk_setup.find_device_node',
return_value=('/dev/xdb1', False))
@mock.patch('cloudinit.config.cc_disk_setup.device_type', return_value=None)
-@mock.patch('cloudinit.config.cc_disk_setup.util.subp', return_value=('', ''))
+@mock.patch('cloudinit.config.cc_disk_setup.subp.subp', return_value=('', ''))
class TestMkfsCommandHandling(CiTestCase):
with_logs = True
@@ -204,7 +204,7 @@ class TestMkfsCommandHandling(CiTestCase):
subp.assert_called_once_with(
'mkfs -t ext4 -L with_cmd /dev/xdb1', shell=True)
- @mock.patch('cloudinit.config.cc_disk_setup.util.which')
+ @mock.patch('cloudinit.config.cc_disk_setup.subp.which')
def test_overwrite_and_extra_opts_without_cmd(self, m_which, subp, *args):
"""mkfs observes extra_opts and overwrite settings when cmd is not
present."""
@@ -222,7 +222,7 @@ class TestMkfsCommandHandling(CiTestCase):
'-L', 'without_cmd', '-F', 'are', 'added'],
shell=False)
- @mock.patch('cloudinit.config.cc_disk_setup.util.which')
+ @mock.patch('cloudinit.config.cc_disk_setup.subp.which')
def test_mkswap(self, m_which, subp, *args):
"""mkfs observes extra_opts and overwrite settings when cmd is not
present."""
diff --git a/tests/unittests/test_handler/test_handler_etc_hosts.py b/tests/unittests/test_handler/test_handler_etc_hosts.py
index d854afcb..e3778b11 100644
--- a/tests/unittests/test_handler/test_handler_etc_hosts.py
+++ b/tests/unittests/test_handler/test_handler_etc_hosts.py
@@ -44,8 +44,8 @@ class TestHostsFile(t_help.FilesystemMockingTestCase):
self.patchUtils(self.tmp)
cc_update_etc_hosts.handle('test', cfg, cc, LOG, [])
contents = util.load_file('%s/etc/hosts' % self.tmp)
- if '127.0.0.1\tcloud-init.test.us\tcloud-init' not in contents:
- self.assertIsNone('No entry for 127.0.0.1 in etc/hosts')
+ if '127.0.1.1\tcloud-init.test.us\tcloud-init' not in contents:
+ self.assertIsNone('No entry for 127.0.1.1 in etc/hosts')
if '192.168.1.1\tblah.blah.us\tblah' not in contents:
self.assertIsNone('Default etc/hosts content modified')
@@ -64,7 +64,7 @@ class TestHostsFile(t_help.FilesystemMockingTestCase):
self.patchUtils(self.tmp)
cc_update_etc_hosts.handle('test', cfg, cc, LOG, [])
contents = util.load_file('%s/etc/hosts' % self.tmp)
- if '127.0.0.1 cloud-init.test.us cloud-init' not in contents:
- self.assertIsNone('No entry for 127.0.0.1 in etc/hosts')
+ if '127.0.1.1 cloud-init.test.us cloud-init' not in contents:
+ self.assertIsNone('No entry for 127.0.1.1 in etc/hosts')
if '::1 cloud-init.test.us cloud-init' not in contents:
self.assertIsNone('No entry for 127.0.0.1 in etc/hosts')
diff --git a/tests/unittests/test_handler/test_handler_growpart.py b/tests/unittests/test_handler/test_handler_growpart.py
index 43b53745..7f039b79 100644
--- a/tests/unittests/test_handler/test_handler_growpart.py
+++ b/tests/unittests/test_handler/test_handler_growpart.py
@@ -2,7 +2,7 @@
from cloudinit import cloud
from cloudinit.config import cc_growpart
-from cloudinit import util
+from cloudinit import subp
from cloudinit.tests.helpers import TestCase
@@ -11,13 +11,9 @@ import logging
import os
import re
import unittest
+from contextlib import ExitStack
from unittest import mock
-try:
- from contextlib import ExitStack
-except ImportError:
- from contextlib2 import ExitStack
-
# growpart:
# mode: auto # off, on, auto, 'growpart'
# devices: ['root']
@@ -99,7 +95,7 @@ class TestConfig(TestCase):
@mock.patch.dict("os.environ", clear=True)
def test_no_resizers_auto_is_fine(self):
with mock.patch.object(
- util, 'subp',
+ subp, 'subp',
return_value=(HELP_GROWPART_NO_RESIZE, "")) as mockobj:
config = {'growpart': {'mode': 'auto'}}
@@ -113,7 +109,7 @@ class TestConfig(TestCase):
@mock.patch.dict("os.environ", clear=True)
def test_no_resizers_mode_growpart_is_exception(self):
with mock.patch.object(
- util, 'subp',
+ subp, 'subp',
return_value=(HELP_GROWPART_NO_RESIZE, "")) as mockobj:
config = {'growpart': {'mode': "growpart"}}
self.assertRaises(
@@ -126,7 +122,7 @@ class TestConfig(TestCase):
@mock.patch.dict("os.environ", clear=True)
def test_mode_auto_prefers_growpart(self):
with mock.patch.object(
- util, 'subp',
+ subp, 'subp',
return_value=(HELP_GROWPART_RESIZE, "")) as mockobj:
ret = cc_growpart.resizer_factory(mode="auto")
self.assertIsInstance(ret, cc_growpart.ResizeGrowPart)
@@ -137,7 +133,7 @@ class TestConfig(TestCase):
@mock.patch.dict("os.environ", clear=True)
def test_mode_auto_falls_back_to_gpart(self):
with mock.patch.object(
- util, 'subp',
+ subp, 'subp',
return_value=("", HELP_GPART)) as mockobj:
ret = cc_growpart.resizer_factory(mode="auto")
self.assertIsInstance(ret, cc_growpart.ResizeGpart)
diff --git a/tests/unittests/test_handler/test_handler_landscape.py b/tests/unittests/test_handler/test_handler_landscape.py
index db92a7e2..7d165687 100644
--- a/tests/unittests/test_handler/test_handler_landscape.py
+++ b/tests/unittests/test_handler/test_handler_landscape.py
@@ -49,8 +49,8 @@ class TestLandscape(FilesystemMockingTestCase):
"'landscape' key existed in config, but not a dict",
str(context_manager.exception))
- @mock.patch('cloudinit.config.cc_landscape.util')
- def test_handler_restarts_landscape_client(self, m_util):
+ @mock.patch('cloudinit.config.cc_landscape.subp')
+ def test_handler_restarts_landscape_client(self, m_subp):
"""handler restarts lansdscape-client after install."""
mycloud = self._get_cloud('ubuntu')
cfg = {'landscape': {'client': {}}}
@@ -60,7 +60,7 @@ class TestLandscape(FilesystemMockingTestCase):
cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None)
self.assertEqual(
[mock.call(['service', 'landscape-client', 'restart'])],
- m_util.subp.call_args_list)
+ m_subp.subp.call_args_list)
def test_handler_installs_client_and_creates_config_file(self):
"""Write landscape client.conf and install landscape-client."""
diff --git a/tests/unittests/test_handler/test_handler_locale.py b/tests/unittests/test_handler/test_handler_locale.py
index 2b22559f..47e7d804 100644
--- a/tests/unittests/test_handler/test_handler_locale.py
+++ b/tests/unittests/test_handler/test_handler_locale.py
@@ -29,8 +29,6 @@ LOG = logging.getLogger(__name__)
class TestLocale(t_help.FilesystemMockingTestCase):
- with_logs = True
-
def setUp(self):
super(TestLocale, self).setUp()
self.new_root = tempfile.mkdtemp()
@@ -86,7 +84,7 @@ class TestLocale(t_help.FilesystemMockingTestCase):
util.write_file(locale_conf, 'LANG="en_US.UTF-8"\n')
cfg = {'locale': 'C.UTF-8'}
cc = self._get_cloud('ubuntu')
- with mock.patch('cloudinit.distros.debian.util.subp') as m_subp:
+ with mock.patch('cloudinit.distros.debian.subp.subp') as m_subp:
with mock.patch('cloudinit.distros.debian.LOCALE_CONF_FN',
locale_conf):
cc_locale.handle('cc_locale', cfg, cc, LOG, [])
diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py
index 40b521e5..21011204 100644
--- a/tests/unittests/test_handler/test_handler_lxd.py
+++ b/tests/unittests/test_handler/test_handler_lxd.py
@@ -31,13 +31,13 @@ class TestLxd(t_help.CiTestCase):
return cc
@mock.patch("cloudinit.config.cc_lxd.maybe_cleanup_default")
- @mock.patch("cloudinit.config.cc_lxd.util")
- def test_lxd_init(self, mock_util, m_maybe_clean):
+ @mock.patch("cloudinit.config.cc_lxd.subp")
+ def test_lxd_init(self, mock_subp, m_maybe_clean):
cc = self._get_cloud('ubuntu')
- mock_util.which.return_value = True
+ mock_subp.which.return_value = True
m_maybe_clean.return_value = None
cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, [])
- self.assertTrue(mock_util.which.called)
+ self.assertTrue(mock_subp.which.called)
# no bridge config, so maybe_cleanup should not be called.
self.assertFalse(m_maybe_clean.called)
self.assertEqual(
@@ -45,14 +45,14 @@ class TestLxd(t_help.CiTestCase):
mock.call(
['lxd', 'init', '--auto', '--network-address=0.0.0.0',
'--storage-backend=zfs', '--storage-pool=poolname'])],
- mock_util.subp.call_args_list)
+ mock_subp.subp.call_args_list)
@mock.patch("cloudinit.config.cc_lxd.maybe_cleanup_default")
- @mock.patch("cloudinit.config.cc_lxd.util")
- def test_lxd_install(self, mock_util, m_maybe_clean):
+ @mock.patch("cloudinit.config.cc_lxd.subp")
+ def test_lxd_install(self, mock_subp, m_maybe_clean):
cc = self._get_cloud('ubuntu')
cc.distro = mock.MagicMock()
- mock_util.which.return_value = None
+ mock_subp.which.return_value = None
cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, [])
self.assertNotIn('WARN', self.logs.getvalue())
self.assertTrue(cc.distro.install_packages.called)
@@ -62,23 +62,23 @@ class TestLxd(t_help.CiTestCase):
self.assertEqual(sorted(install_pkg), ['lxd', 'zfsutils-linux'])
@mock.patch("cloudinit.config.cc_lxd.maybe_cleanup_default")
- @mock.patch("cloudinit.config.cc_lxd.util")
- def test_no_init_does_nothing(self, mock_util, m_maybe_clean):
+ @mock.patch("cloudinit.config.cc_lxd.subp")
+ def test_no_init_does_nothing(self, mock_subp, m_maybe_clean):
cc = self._get_cloud('ubuntu')
cc.distro = mock.MagicMock()
cc_lxd.handle('cc_lxd', {'lxd': {}}, cc, self.logger, [])
self.assertFalse(cc.distro.install_packages.called)
- self.assertFalse(mock_util.subp.called)
+ self.assertFalse(mock_subp.subp.called)
self.assertFalse(m_maybe_clean.called)
@mock.patch("cloudinit.config.cc_lxd.maybe_cleanup_default")
- @mock.patch("cloudinit.config.cc_lxd.util")
- def test_no_lxd_does_nothing(self, mock_util, m_maybe_clean):
+ @mock.patch("cloudinit.config.cc_lxd.subp")
+ def test_no_lxd_does_nothing(self, mock_subp, m_maybe_clean):
cc = self._get_cloud('ubuntu')
cc.distro = mock.MagicMock()
cc_lxd.handle('cc_lxd', {'package_update': True}, cc, self.logger, [])
self.assertFalse(cc.distro.install_packages.called)
- self.assertFalse(mock_util.subp.called)
+ self.assertFalse(mock_subp.subp.called)
self.assertFalse(m_maybe_clean.called)
def test_lxd_debconf_new_full(self):
diff --git a/tests/unittests/test_handler/test_handler_mcollective.py b/tests/unittests/test_handler/test_handler_mcollective.py
index c013a538..6891e15f 100644
--- a/tests/unittests/test_handler/test_handler_mcollective.py
+++ b/tests/unittests/test_handler/test_handler_mcollective.py
@@ -136,8 +136,9 @@ class TestHandler(t_help.TestCase):
cc = cloud.Cloud(ds, paths, {}, d, None)
return cc
+ @t_help.mock.patch("cloudinit.config.cc_mcollective.subp")
@t_help.mock.patch("cloudinit.config.cc_mcollective.util")
- def test_mcollective_install(self, mock_util):
+ def test_mcollective_install(self, mock_util, mock_subp):
cc = self._get_cloud('ubuntu')
cc.distro = t_help.mock.MagicMock()
mock_util.load_file.return_value = b""
@@ -147,8 +148,8 @@ class TestHandler(t_help.TestCase):
install_pkg = cc.distro.install_packages.call_args_list[0][0][0]
self.assertEqual(install_pkg, ('mcollective',))
- self.assertTrue(mock_util.subp.called)
- self.assertEqual(mock_util.subp.call_args_list[0][0][0],
+ self.assertTrue(mock_subp.subp.called)
+ self.assertEqual(mock_subp.subp.call_args_list[0][0][0],
['service', 'mcollective', 'restart'])
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py
index 05ac183e..e87069f6 100644
--- a/tests/unittests/test_handler/test_handler_mounts.py
+++ b/tests/unittests/test_handler/test_handler_mounts.py
@@ -127,6 +127,119 @@ class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase):
cc_mounts.sanitize_devname(
'ephemeral0.1', lambda x: disk_path, mock.Mock()))
+ def test_network_device_returns_network_device(self):
+ disk_path = 'netdevice:/path'
+ self.assertEqual(
+ disk_path,
+ cc_mounts.sanitize_devname(disk_path, None, mock.Mock()))
+
+
+class TestSwapFileCreation(test_helpers.FilesystemMockingTestCase):
+
+ def setUp(self):
+ super(TestSwapFileCreation, self).setUp()
+ self.new_root = self.tmp_dir()
+ self.patchOS(self.new_root)
+
+ self.fstab_path = os.path.join(self.new_root, 'etc/fstab')
+ self.swap_path = os.path.join(self.new_root, 'swap.img')
+ self._makedirs('/etc')
+
+ self.add_patch('cloudinit.config.cc_mounts.FSTAB_PATH',
+ 'mock_fstab_path',
+ self.fstab_path,
+ autospec=False)
+
+ self.add_patch('cloudinit.config.cc_mounts.subp.subp',
+ 'm_subp_subp')
+
+ self.add_patch('cloudinit.config.cc_mounts.util.mounts',
+ 'mock_util_mounts',
+ return_value={
+ '/dev/sda1': {'fstype': 'ext4',
+ 'mountpoint': '/',
+ 'opts': 'rw,relatime,discard'
+ }})
+
+ self.mock_cloud = mock.Mock()
+ self.mock_log = mock.Mock()
+ self.mock_cloud.device_name_to_device = self.device_name_to_device
+
+ self.cc = {
+ 'swap': {
+ 'filename': self.swap_path,
+ 'size': '512',
+ 'maxsize': '512'}}
+
+ def _makedirs(self, directory):
+ directory = os.path.join(self.new_root, directory.lstrip('/'))
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ def device_name_to_device(self, path):
+ if path == 'swap':
+ return self.swap_path
+ else:
+ dev = None
+
+ return dev
+
+ @mock.patch('cloudinit.util.get_mount_info')
+ @mock.patch('cloudinit.util.kernel_version')
+ def test_swap_creation_method_fallocate_on_xfs(self, m_kernel_version,
+ m_get_mount_info):
+ m_kernel_version.return_value = (4, 20)
+ m_get_mount_info.return_value = ["", "xfs"]
+
+ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
+ self.m_subp_subp.assert_has_calls([
+ mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True),
+ mock.call(['mkswap', self.swap_path]),
+ mock.call(['swapon', '-a'])])
+
+ @mock.patch('cloudinit.util.get_mount_info')
+ @mock.patch('cloudinit.util.kernel_version')
+ def test_swap_creation_method_xfs(self, m_kernel_version,
+ m_get_mount_info):
+ m_kernel_version.return_value = (3, 18)
+ m_get_mount_info.return_value = ["", "xfs"]
+
+ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
+ self.m_subp_subp.assert_has_calls([
+ mock.call(['dd', 'if=/dev/zero',
+ 'of=' + self.swap_path,
+ 'bs=1M', 'count=0'], capture=True),
+ mock.call(['mkswap', self.swap_path]),
+ mock.call(['swapon', '-a'])])
+
+ @mock.patch('cloudinit.util.get_mount_info')
+ @mock.patch('cloudinit.util.kernel_version')
+ def test_swap_creation_method_btrfs(self, m_kernel_version,
+ m_get_mount_info):
+ m_kernel_version.return_value = (4, 20)
+ m_get_mount_info.return_value = ["", "btrfs"]
+
+ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
+ self.m_subp_subp.assert_has_calls([
+ mock.call(['dd', 'if=/dev/zero',
+ 'of=' + self.swap_path,
+ 'bs=1M', 'count=0'], capture=True),
+ mock.call(['mkswap', self.swap_path]),
+ mock.call(['swapon', '-a'])])
+
+ @mock.patch('cloudinit.util.get_mount_info')
+ @mock.patch('cloudinit.util.kernel_version')
+ def test_swap_creation_method_ext4(self, m_kernel_version,
+ m_get_mount_info):
+ m_kernel_version.return_value = (5, 14)
+ m_get_mount_info.return_value = ["", "ext4"]
+
+ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
+ self.m_subp_subp.assert_has_calls([
+ mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True),
+ mock.call(['mkswap', self.swap_path]),
+ mock.call(['swapon', '-a'])])
+
class TestFstabHandling(test_helpers.FilesystemMockingTestCase):
@@ -149,8 +262,8 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase):
'mock_is_block_device',
return_value=True)
- self.add_patch('cloudinit.config.cc_mounts.util.subp',
- 'm_util_subp')
+ self.add_patch('cloudinit.config.cc_mounts.subp.subp',
+ 'm_subp_subp')
self.add_patch('cloudinit.config.cc_mounts.util.mounts',
'mock_util_mounts',
@@ -177,6 +290,18 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase):
return dev
+ def test_no_fstab(self):
+ """ Handle images which do not include an fstab. """
+ self.assertFalse(os.path.exists(cc_mounts.FSTAB_PATH))
+ fstab_expected_content = (
+ '%s\tnone\tswap\tsw,comment=cloudconfig\t'
+ '0\t0\n' % (self.swap_path,)
+ )
+ cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, [])
+ with open(cc_mounts.FSTAB_PATH, 'r') as fd:
+ fstab_new_content = fd.read()
+ self.assertEqual(fstab_expected_content, fstab_new_content)
+
def test_swap_integrity(self):
'''Ensure that the swap file is correctly created and can
swapon successfully. Fixing the corner case of:
@@ -254,15 +379,18 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase):
'/dev/vdb /mnt auto defaults,noexec,comment=cloudconfig 0 2\n'
)
fstab_expected_content = fstab_original_content
- cc = {'mounts': [
- ['/dev/vdb', '/mnt', 'auto', 'defaults,noexec']]}
+ cc = {
+ 'mounts': [
+ ['/dev/vdb', '/mnt', 'auto', 'defaults,noexec']
+ ]
+ }
with open(cc_mounts.FSTAB_PATH, 'w') as fd:
fd.write(fstab_original_content)
with open(cc_mounts.FSTAB_PATH, 'r') as fd:
fstab_new_content = fd.read()
self.assertEqual(fstab_expected_content, fstab_new_content)
cc_mounts.handle(None, cc, self.mock_cloud, self.mock_log, [])
- self.m_util_subp.assert_has_calls([
+ self.m_subp_subp.assert_has_calls([
mock.call(['mount', '-a']),
mock.call(['systemctl', 'daemon-reload'])])
diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py
index 463d892a..6b9c8377 100644
--- a/tests/unittests/test_handler/test_handler_ntp.py
+++ b/tests/unittests/test_handler/test_handler_ntp.py
@@ -83,50 +83,50 @@ class TestNtp(FilesystemMockingTestCase):
ntpconfig['template_name'] = os.path.basename(confpath)
return ntpconfig
- @mock.patch("cloudinit.config.cc_ntp.util")
- def test_ntp_install(self, mock_util):
+ @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_util.which.return_value = None # check_exe not found.
+ 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_util.which.assert_called_with('ntpdx')
+ mock_subp.which.assert_called_with('ntpdx')
install_func.assert_called_once_with(['ntpx'])
- @mock.patch("cloudinit.config.cc_ntp.util")
- def test_ntp_install_not_needed(self, mock_util):
+ @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_util.which.return_value = [client] # check_exe found.
+ 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.util")
- def test_ntp_install_no_op_with_empty_pkg_list(self, mock_util):
+ @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_util.which.return_value = None # check_exe not found
+ 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([])
- @mock.patch("cloudinit.config.cc_ntp.util")
- def test_reload_ntp_defaults(self, mock_util):
+ @mock.patch("cloudinit.config.cc_ntp.subp")
+ def test_reload_ntp_defaults(self, mock_subp):
"""Test service is restarted/reloaded (defaults)"""
service = 'ntp_service_name'
cmd = ['service', service, 'restart']
cc_ntp.reload_ntp(service)
- mock_util.subp.assert_called_with(cmd, capture=True)
+ mock_subp.subp.assert_called_with(cmd, capture=True)
- @mock.patch("cloudinit.config.cc_ntp.util")
- def test_reload_ntp_systemd(self, mock_util):
+ @mock.patch("cloudinit.config.cc_ntp.subp")
+ def test_reload_ntp_systemd(self, mock_subp):
"""Test service is restarted/reloaded (systemd)"""
service = 'ntp_service_name'
cc_ntp.reload_ntp(service, systemd=True)
cmd = ['systemctl', 'reload-or-restart', service]
- mock_util.subp.assert_called_with(cmd, capture=True)
+ mock_subp.subp.assert_called_with(cmd, capture=True)
def test_ntp_rename_ntp_conf(self):
"""When NTP_CONF exists, rename_ntp moves it."""
@@ -239,6 +239,35 @@ class TestNtp(FilesystemMockingTestCase):
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']
@@ -269,27 +298,35 @@ class TestNtp(FilesystemMockingTestCase):
content = util.load_file(confpath)
if client in ['ntp', 'chrony']:
content_lines = content.splitlines()
- expected_servers = [
- 'server {0} iburst'.format(srv) for srv in servers]
+ 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 = [
- 'pool {0} iburst'.format(pool) for pool in pools]
- for pline in expected_pools:
- self.assertIn(pline, 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" % (" ".join(servers),
- " ".join(pools)))
+ "[Time]\nNTP=%s %s \n" % (expected_servers,
+ expected_pools))
self.assertEqual(expected_content, content)
def test_no_ntpcfg_does_nothing(self):
@@ -312,10 +349,20 @@ class TestNtp(FilesystemMockingTestCase):
confpath = ntpconfig['confpath']
m_select.return_value = ntpconfig
cc_ntp.handle('cc_ntp', valid_empty_config, mycloud, None, [])
- pools = cc_ntp.generate_server_names(mycloud.distro.name)
- self.assertEqual(
- "servers []\npools {0}\n".format(pools),
- util.load_file(confpath))
+ 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()
@@ -374,18 +421,19 @@ class TestNtp(FilesystemMockingTestCase):
invalid_config = {
'ntp': {'invalidkey': 1, 'pools': ['0.mycompany.pool.ntp.org']}}
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: 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))
+ 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')
@@ -440,9 +488,10 @@ class TestNtp(FilesystemMockingTestCase):
cc_ntp.handle('notimportant', cfg, mycloud, None, None)
self.assertEqual(0, m_select.call_count)
+ @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):
+ def test_ntp_the_whole_package(self, m_sysd, m_select, m_subp):
"""Test enabled config renders template, and restarts service """
cfg = {'ntp': {'enabled': True}}
for distro in cc_ntp.distros:
@@ -451,24 +500,35 @@ class TestNtp(FilesystemMockingTestCase):
confpath = ntpconfig['confpath']
service_name = ntpconfig['service_name']
m_select.return_value = ntpconfig
- pools = cc_ntp.generate_server_names(mycloud.distro.name)
- # force uses systemd path
- m_sysd.return_value = True
+
+ 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 = ['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_util.which.return_value = True
+ 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_util.subp.assert_called_with(
- ['systemctl', 'reload-or-restart',
- service_name], capture=True)
- self.assertEqual(
- "servers []\npools {0}\n".format(pools),
- util.load_file(confpath))
+ m_subp.subp.assert_called_with(
+ expected_service_call, capture=True)
+
+ self.assertEqual(expected_content, util.load_file(confpath))
def test_opensuse_picks_chrony(self):
"""Test opensuse picks chrony or ntp on certain distro versions"""
@@ -503,7 +563,7 @@ class TestNtp(FilesystemMockingTestCase):
expected_client = mycloud.distro.preferred_ntp_clients[0]
self.assertEqual('ntp', expected_client)
- @mock.patch('cloudinit.config.cc_ntp.util.which')
+ @mock.patch('cloudinit.config.cc_ntp.subp.which')
def test_snappy_system_picks_timesyncd(self, m_which):
"""Test snappy systems prefer installed clients"""
@@ -528,7 +588,7 @@ class TestNtp(FilesystemMockingTestCase):
self.assertEqual(sorted(expected_cfg), sorted(cfg))
self.assertEqual(sorted(expected_cfg), sorted(result))
- @mock.patch('cloudinit.config.cc_ntp.util.which')
+ @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
@@ -546,7 +606,7 @@ class TestNtp(FilesystemMockingTestCase):
m_which.assert_has_calls(expected_calls)
self.assertEqual(sorted(expected_cfg), sorted(cfg))
- @mock.patch('cloudinit.config.cc_ntp.util.which')
+ @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
@@ -566,7 +626,7 @@ class TestNtp(FilesystemMockingTestCase):
@mock.patch('cloudinit.config.cc_ntp.write_ntp_config_template')
@mock.patch('cloudinit.cloud.Cloud.get_template_filename')
- @mock.patch('cloudinit.config.cc_ntp.util.which')
+ @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 """
@@ -582,7 +642,7 @@ class TestNtp(FilesystemMockingTestCase):
m_install.assert_called_with([client])
m_which.assert_called_with(client)
- @mock.patch('cloudinit.config.cc_ntp.util.which')
+ @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'
@@ -597,7 +657,7 @@ class TestNtp(FilesystemMockingTestCase):
self.assertEqual(sorted(expected_cfg), sorted(result))
m_which.assert_has_calls([])
- @mock.patch('cloudinit.config.cc_ntp.util.which')
+ @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'
diff --git a/tests/unittests/test_handler/test_handler_power_state.py b/tests/unittests/test_handler/test_handler_power_state.py
index 0d8d17b9..93b24fdc 100644
--- a/tests/unittests/test_handler/test_handler_power_state.py
+++ b/tests/unittests/test_handler/test_handler_power_state.py
@@ -11,62 +11,63 @@ from cloudinit.tests.helpers import mock
class TestLoadPowerState(t_help.TestCase):
def test_no_config(self):
# completely empty config should mean do nothing
- (cmd, _timeout, _condition) = psc.load_power_state({})
+ (cmd, _timeout, _condition) = psc.load_power_state({}, 'ubuntu')
self.assertIsNone(cmd)
def test_irrelevant_config(self):
# no power_state field in config should return None for cmd
- (cmd, _timeout, _condition) = psc.load_power_state({'foo': 'bar'})
+ (cmd, _timeout, _condition) = psc.load_power_state({'foo': 'bar'},
+ 'ubuntu')
self.assertIsNone(cmd)
def test_invalid_mode(self):
cfg = {'power_state': {'mode': 'gibberish'}}
- self.assertRaises(TypeError, psc.load_power_state, cfg)
+ self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
cfg = {'power_state': {'mode': ''}}
- self.assertRaises(TypeError, psc.load_power_state, cfg)
+ self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
def test_empty_mode(self):
cfg = {'power_state': {'message': 'goodbye'}}
- self.assertRaises(TypeError, psc.load_power_state, cfg)
+ self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
def test_valid_modes(self):
cfg = {'power_state': {}}
for mode in ('halt', 'poweroff', 'reboot'):
cfg['power_state']['mode'] = mode
- check_lps_ret(psc.load_power_state(cfg), mode=mode)
+ check_lps_ret(psc.load_power_state(cfg, 'ubuntu'), mode=mode)
def test_invalid_delay(self):
cfg = {'power_state': {'mode': 'poweroff', 'delay': 'goodbye'}}
- self.assertRaises(TypeError, psc.load_power_state, cfg)
+ self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
def test_valid_delay(self):
cfg = {'power_state': {'mode': 'poweroff', 'delay': ''}}
for delay in ("now", "+1", "+30"):
cfg['power_state']['delay'] = delay
- check_lps_ret(psc.load_power_state(cfg))
+ check_lps_ret(psc.load_power_state(cfg, 'ubuntu'))
def test_message_present(self):
cfg = {'power_state': {'mode': 'poweroff', 'message': 'GOODBYE'}}
- ret = psc.load_power_state(cfg)
- check_lps_ret(psc.load_power_state(cfg))
+ ret = psc.load_power_state(cfg, 'ubuntu')
+ check_lps_ret(psc.load_power_state(cfg, 'ubuntu'))
self.assertIn(cfg['power_state']['message'], ret[0])
def test_no_message(self):
# if message is not present, then no argument should be passed for it
cfg = {'power_state': {'mode': 'poweroff'}}
- (cmd, _timeout, _condition) = psc.load_power_state(cfg)
+ (cmd, _timeout, _condition) = psc.load_power_state(cfg, 'ubuntu')
self.assertNotIn("", cmd)
- check_lps_ret(psc.load_power_state(cfg))
+ check_lps_ret(psc.load_power_state(cfg, 'ubuntu'))
self.assertTrue(len(cmd) == 3)
def test_condition_null_raises(self):
cfg = {'power_state': {'mode': 'poweroff', 'condition': None}}
- self.assertRaises(TypeError, psc.load_power_state, cfg)
+ self.assertRaises(TypeError, psc.load_power_state, cfg, 'ubuntu')
def test_condition_default_is_true(self):
cfg = {'power_state': {'mode': 'poweroff'}}
- _cmd, _timeout, cond = psc.load_power_state(cfg)
+ _cmd, _timeout, cond = psc.load_power_state(cfg, 'ubuntu')
self.assertEqual(cond, True)
diff --git a/tests/unittests/test_handler/test_handler_puppet.py b/tests/unittests/test_handler/test_handler_puppet.py
index 1494177d..62388ac6 100644
--- a/tests/unittests/test_handler/test_handler_puppet.py
+++ b/tests/unittests/test_handler/test_handler_puppet.py
@@ -12,13 +12,11 @@ import textwrap
LOG = logging.getLogger(__name__)
-@mock.patch('cloudinit.config.cc_puppet.util')
+@mock.patch('cloudinit.config.cc_puppet.subp.subp')
@mock.patch('cloudinit.config.cc_puppet.os')
class TestAutostartPuppet(CiTestCase):
- with_logs = True
-
- def test_wb_autostart_puppet_updates_puppet_default(self, m_os, m_util):
+ def test_wb_autostart_puppet_updates_puppet_default(self, m_os, m_subp):
"""Update /etc/default/puppet to autostart if it exists."""
def _fake_exists(path):
@@ -29,9 +27,9 @@ class TestAutostartPuppet(CiTestCase):
self.assertEqual(
[mock.call(['sed', '-i', '-e', 's/^START=.*/START=yes/',
'/etc/default/puppet'], capture=False)],
- m_util.subp.call_args_list)
+ m_subp.call_args_list)
- def test_wb_autostart_pupppet_enables_puppet_systemctl(self, m_os, m_util):
+ def test_wb_autostart_pupppet_enables_puppet_systemctl(self, m_os, m_subp):
"""If systemctl is present, enable puppet via systemctl."""
def _fake_exists(path):
@@ -41,9 +39,9 @@ class TestAutostartPuppet(CiTestCase):
cc_puppet._autostart_puppet(LOG)
expected_calls = [mock.call(
['/bin/systemctl', 'enable', 'puppet.service'], capture=False)]
- self.assertEqual(expected_calls, m_util.subp.call_args_list)
+ self.assertEqual(expected_calls, m_subp.call_args_list)
- def test_wb_autostart_pupppet_enables_puppet_chkconfig(self, m_os, m_util):
+ def test_wb_autostart_pupppet_enables_puppet_chkconfig(self, m_os, m_subp):
"""If chkconfig is present, enable puppet via checkcfg."""
def _fake_exists(path):
@@ -53,7 +51,7 @@ class TestAutostartPuppet(CiTestCase):
cc_puppet._autostart_puppet(LOG)
expected_calls = [mock.call(
['/sbin/chkconfig', 'puppet', 'on'], capture=False)]
- self.assertEqual(expected_calls, m_util.subp.call_args_list)
+ self.assertEqual(expected_calls, m_subp.call_args_list)
@mock.patch('cloudinit.config.cc_puppet._autostart_puppet')
@@ -83,7 +81,7 @@ class TestPuppetHandle(CiTestCase):
"no 'puppet' configuration found", self.logs.getvalue())
self.assertEqual(0, m_auto.call_count)
- @mock.patch('cloudinit.config.cc_puppet.util.subp')
+ @mock.patch('cloudinit.config.cc_puppet.subp.subp')
def test_handler_puppet_config_starts_puppet_service(self, m_subp, m_auto):
"""Cloud-config 'puppet' configuration starts puppet."""
mycloud = self._get_cloud('ubuntu')
@@ -94,7 +92,7 @@ class TestPuppetHandle(CiTestCase):
[mock.call(['service', 'puppet', 'start'], capture=False)],
m_subp.call_args_list)
- @mock.patch('cloudinit.config.cc_puppet.util.subp')
+ @mock.patch('cloudinit.config.cc_puppet.subp.subp')
def test_handler_empty_puppet_config_installs_puppet(self, m_subp, m_auto):
"""Cloud-config empty 'puppet' configuration installs latest puppet."""
mycloud = self._get_cloud('ubuntu')
@@ -105,7 +103,7 @@ class TestPuppetHandle(CiTestCase):
[mock.call(('puppet', None))],
mycloud.distro.install_packages.call_args_list)
- @mock.patch('cloudinit.config.cc_puppet.util.subp')
+ @mock.patch('cloudinit.config.cc_puppet.subp.subp')
def test_handler_puppet_config_installs_puppet_on_true(self, m_subp, _):
"""Cloud-config with 'puppet' key installs when 'install' is True."""
mycloud = self._get_cloud('ubuntu')
@@ -116,7 +114,7 @@ class TestPuppetHandle(CiTestCase):
[mock.call(('puppet', None))],
mycloud.distro.install_packages.call_args_list)
- @mock.patch('cloudinit.config.cc_puppet.util.subp')
+ @mock.patch('cloudinit.config.cc_puppet.subp.subp')
def test_handler_puppet_config_installs_puppet_version(self, m_subp, _):
"""Cloud-config 'puppet' configuration can specify a version."""
mycloud = self._get_cloud('ubuntu')
@@ -127,7 +125,7 @@ class TestPuppetHandle(CiTestCase):
[mock.call(('puppet', '3.8'))],
mycloud.distro.install_packages.call_args_list)
- @mock.patch('cloudinit.config.cc_puppet.util.subp')
+ @mock.patch('cloudinit.config.cc_puppet.subp.subp')
def test_handler_puppet_config_updates_puppet_conf(self, m_subp, m_auto):
"""When 'conf' is provided update values in PUPPET_CONF_PATH."""
mycloud = self._get_cloud('ubuntu')
@@ -143,7 +141,7 @@ class TestPuppetHandle(CiTestCase):
expected = '[agent]\nserver = puppetmaster.example.org\nother = 3\n\n'
self.assertEqual(expected, content)
- @mock.patch('cloudinit.config.cc_puppet.util.subp')
+ @mock.patch('cloudinit.config.cc_puppet.subp.subp')
def test_handler_puppet_writes_csr_attributes_file(self, m_subp, m_auto):
"""When csr_attributes is provided
creates file in PUPPET_CSR_ATTRIBUTES_PATH."""
@@ -151,15 +149,20 @@ class TestPuppetHandle(CiTestCase):
mycloud.distro = mock.MagicMock()
cfg = {
'puppet': {
- 'csr_attributes': {
- 'custom_attributes': {
- '1.2.840.113549.1.9.7': '342thbjkt82094y0ut'
- 'hhor289jnqthpc2290'},
- 'extension_requests': {
- 'pp_uuid': 'ED803750-E3C7-44F5-BB08-41A04433FE2E',
- 'pp_image_name': 'my_ami_image',
- 'pp_preshared_key': '342thbjkt82094y0uthhor289jnqthpc2290'}
- }}}
+ 'csr_attributes': {
+ 'custom_attributes': {
+ '1.2.840.113549.1.9.7':
+ '342thbjkt82094y0uthhor289jnqthpc2290'
+ },
+ 'extension_requests': {
+ 'pp_uuid': 'ED803750-E3C7-44F5-BB08-41A04433FE2E',
+ 'pp_image_name': 'my_ami_image',
+ 'pp_preshared_key':
+ '342thbjkt82094y0uthhor289jnqthpc2290'
+ }
+ }
+ }
+ }
csr_attributes = 'cloudinit.config.cc_puppet.' \
'PUPPET_CSR_ATTRIBUTES_PATH'
with mock.patch(csr_attributes, self.csr_attributes_path):
diff --git a/tests/unittests/test_handler/test_handler_runcmd.py b/tests/unittests/test_handler/test_handler_runcmd.py
index 9ce334ac..73237d68 100644
--- a/tests/unittests/test_handler/test_handler_runcmd.py
+++ b/tests/unittests/test_handler/test_handler_runcmd.py
@@ -2,7 +2,7 @@
from cloudinit.config.cc_runcmd import handle, schema
from cloudinit.sources import DataSourceNone
-from cloudinit import (distros, helpers, cloud, util)
+from cloudinit import (distros, helpers, cloud, subp, util)
from cloudinit.tests.helpers import (
CiTestCase, FilesystemMockingTestCase, SchemaTestCaseMixin,
skipUnlessJsonSchema)
@@ -20,7 +20,7 @@ class TestRuncmd(FilesystemMockingTestCase):
def setUp(self):
super(TestRuncmd, self).setUp()
- self.subp = util.subp
+ self.subp = subp.subp
self.new_root = self.tmp_dir()
def _get_cloud(self, distro):
diff --git a/tests/unittests/test_handler/test_handler_seed_random.py b/tests/unittests/test_handler/test_handler_seed_random.py
index abecc53b..85167f19 100644
--- a/tests/unittests/test_handler/test_handler_seed_random.py
+++ b/tests/unittests/test_handler/test_handler_seed_random.py
@@ -17,6 +17,7 @@ from io import BytesIO
from cloudinit import cloud
from cloudinit import distros
from cloudinit import helpers
+from cloudinit import subp
from cloudinit import util
from cloudinit.sources import DataSourceNone
@@ -35,8 +36,8 @@ class TestRandomSeed(t_help.TestCase):
self.unapply = []
# by default 'which' has nothing in its path
- self.apply_patches([(util, 'which', self._which)])
- self.apply_patches([(util, 'subp', self._subp)])
+ self.apply_patches([(subp, 'which', self._which)])
+ self.apply_patches([(subp, 'subp', self._subp)])
self.subp_called = []
self.whichdata = {}
diff --git a/tests/unittests/test_handler/test_handler_spacewalk.py b/tests/unittests/test_handler/test_handler_spacewalk.py
index 410e6f77..26f7648f 100644
--- a/tests/unittests/test_handler/test_handler_spacewalk.py
+++ b/tests/unittests/test_handler/test_handler_spacewalk.py
@@ -1,7 +1,7 @@
# This file is part of cloud-init. See LICENSE file for license information.
from cloudinit.config import cc_spacewalk
-from cloudinit import util
+from cloudinit import subp
from cloudinit.tests import helpers
@@ -19,20 +19,20 @@ class TestSpacewalk(helpers.TestCase):
}
}
- @mock.patch("cloudinit.config.cc_spacewalk.util.subp")
- def test_not_is_registered(self, mock_util_subp):
- mock_util_subp.side_effect = util.ProcessExecutionError(exit_code=1)
+ @mock.patch("cloudinit.config.cc_spacewalk.subp.subp")
+ def test_not_is_registered(self, mock_subp):
+ mock_subp.side_effect = subp.ProcessExecutionError(exit_code=1)
self.assertFalse(cc_spacewalk.is_registered())
- @mock.patch("cloudinit.config.cc_spacewalk.util.subp")
- def test_is_registered(self, mock_util_subp):
- mock_util_subp.side_effect = None
+ @mock.patch("cloudinit.config.cc_spacewalk.subp.subp")
+ def test_is_registered(self, mock_subp):
+ mock_subp.side_effect = None
self.assertTrue(cc_spacewalk.is_registered())
- @mock.patch("cloudinit.config.cc_spacewalk.util.subp")
- def test_do_register(self, mock_util_subp):
+ @mock.patch("cloudinit.config.cc_spacewalk.subp.subp")
+ def test_do_register(self, mock_subp):
cc_spacewalk.do_register(**self.space_cfg['spacewalk'])
- mock_util_subp.assert_called_with([
+ mock_subp.assert_called_with([
'rhnreg_ks',
'--serverUrl', 'https://localhost/XMLRPC',
'--profilename', 'test',
diff --git a/tests/unittests/test_handler/test_handler_write_files.py b/tests/unittests/test_handler/test_handler_write_files.py
index ed0a4da2..727681d3 100644
--- a/tests/unittests/test_handler/test_handler_write_files.py
+++ b/tests/unittests/test_handler/test_handler_write_files.py
@@ -1,15 +1,19 @@
# This file is part of cloud-init. See LICENSE file for license information.
import base64
+import copy
import gzip
import io
import shutil
import tempfile
+from cloudinit.config.cc_write_files import (
+ handle, decode_perms, write_files)
from cloudinit import log as logging
from cloudinit import util
-from cloudinit.config.cc_write_files import write_files, decode_perms
-from cloudinit.tests.helpers import CiTestCase, FilesystemMockingTestCase
+
+from cloudinit.tests.helpers import (
+ CiTestCase, FilesystemMockingTestCase, mock, skipUnlessJsonSchema)
LOG = logging.getLogger(__name__)
@@ -36,13 +40,90 @@ YAML_CONTENT_EXPECTED = {
'/tmp/message': "hi mom line 1\nhi mom line 2\n",
}
+VALID_SCHEMA = {
+ 'write_files': [
+ {'append': False, 'content': 'a', 'encoding': 'gzip', 'owner': 'jeff',
+ 'path': '/some', 'permissions': '0777'}
+ ]
+}
+
+INVALID_SCHEMA = { # Dropped required path key
+ 'write_files': [
+ {'append': False, 'content': 'a', 'encoding': 'gzip', 'owner': 'jeff',
+ 'permissions': '0777'}
+ ]
+}
+
+
+@skipUnlessJsonSchema()
+@mock.patch('cloudinit.config.cc_write_files.write_files')
+class TestWriteFilesSchema(CiTestCase):
+
+ with_logs = True
+
+ def test_schema_validation_warns_missing_path(self, m_write_files):
+ """The only required file item property is 'path'."""
+ cc = self.tmp_cloud('ubuntu')
+ valid_config = {'write_files': [{'path': '/some/path'}]}
+ handle('cc_write_file', valid_config, cc, LOG, [])
+ self.assertNotIn('Invalid config:', self.logs.getvalue())
+ handle('cc_write_file', INVALID_SCHEMA, cc, LOG, [])
+ self.assertIn('Invalid config:', self.logs.getvalue())
+ self.assertIn("'path' is a required property", self.logs.getvalue())
+
+ def test_schema_validation_warns_non_string_type_for_files(
+ self, m_write_files):
+ """Schema validation warns of non-string values for each file item."""
+ cc = self.tmp_cloud('ubuntu')
+ for key in VALID_SCHEMA['write_files'][0].keys():
+ if key == 'append':
+ key_type = 'boolean'
+ else:
+ key_type = 'string'
+ invalid_config = copy.deepcopy(VALID_SCHEMA)
+ invalid_config['write_files'][0][key] = 1
+ handle('cc_write_file', invalid_config, cc, LOG, [])
+ self.assertIn(
+ mock.call('cc_write_file', invalid_config['write_files']),
+ m_write_files.call_args_list)
+ self.assertIn(
+ 'write_files.0.%s: 1 is not of type \'%s\'' % (key, key_type),
+ self.logs.getvalue())
+ self.assertIn('Invalid config:', self.logs.getvalue())
+
+ def test_schema_validation_warns_on_additional_undefined_propertes(
+ self, m_write_files):
+ """Schema validation warns on additional undefined file properties."""
+ cc = self.tmp_cloud('ubuntu')
+ invalid_config = copy.deepcopy(VALID_SCHEMA)
+ invalid_config['write_files'][0]['bogus'] = 'value'
+ handle('cc_write_file', invalid_config, cc, LOG, [])
+ self.assertIn(
+ "Invalid config:\nwrite_files.0: Additional properties"
+ " are not allowed ('bogus' was unexpected)",
+ self.logs.getvalue())
+
class TestWriteFiles(FilesystemMockingTestCase):
+
+ with_logs = True
+
def setUp(self):
super(TestWriteFiles, self).setUp()
self.tmp = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.tmp)
+ @skipUnlessJsonSchema()
+ def test_handler_schema_validation_warns_non_array_type(self):
+ """Schema validation warns of non-array value."""
+ invalid_config = {'write_files': 1}
+ cc = self.tmp_cloud('ubuntu')
+ with self.assertRaises(TypeError):
+ handle('cc_write_file', invalid_config, cc, LOG, [])
+ self.assertIn(
+ 'Invalid config:\nwrite_files: 1 is not of type \'array\'',
+ self.logs.getvalue())
+
def test_simple(self):
self.patchUtils(self.tmp)
expected = "hello world\n"
diff --git a/tests/unittests/test_handler/test_handler_yum_add_repo.py b/tests/unittests/test_handler/test_handler_yum_add_repo.py
index 0675bd8f..7c61bbf9 100644
--- a/tests/unittests/test_handler/test_handler_yum_add_repo.py
+++ b/tests/unittests/test_handler/test_handler_yum_add_repo.py
@@ -1,14 +1,13 @@
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit.config import cc_yum_add_repo
-from cloudinit import util
-
-from cloudinit.tests import helpers
-
+import configparser
import logging
import shutil
import tempfile
-from io import StringIO
+
+from cloudinit import util
+from cloudinit.config import cc_yum_add_repo
+from cloudinit.tests import helpers
LOG = logging.getLogger(__name__)
@@ -54,7 +53,8 @@ class TestConfig(helpers.FilesystemMockingTestCase):
self.patchUtils(self.tmp)
cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
contents = util.load_file("/etc/yum.repos.d/epel_testing.repo")
- parser = self.parse_and_read(StringIO(contents))
+ parser = configparser.ConfigParser()
+ parser.read_string(contents)
expected = {
'epel_testing': {
'name': 'Extra Packages for Enterprise Linux 5 - Testing',
@@ -90,7 +90,8 @@ class TestConfig(helpers.FilesystemMockingTestCase):
self.patchUtils(self.tmp)
cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
contents = util.load_file("/etc/yum.repos.d/puppetlabs_products.repo")
- parser = self.parse_and_read(StringIO(contents))
+ parser = configparser.ConfigParser()
+ parser.read_string(contents)
expected = {
'puppetlabs_products': {
'name': 'Puppet Labs Products El 6 - $basearch',
diff --git a/tests/unittests/test_handler/test_handler_zypper_add_repo.py b/tests/unittests/test_handler/test_handler_zypper_add_repo.py
index 9685ff28..0fb1de1a 100644
--- a/tests/unittests/test_handler/test_handler_zypper_add_repo.py
+++ b/tests/unittests/test_handler/test_handler_zypper_add_repo.py
@@ -1,17 +1,15 @@
# This file is part of cloud-init. See LICENSE file for license information.
+import configparser
import glob
+import logging
import os
-from io import StringIO
-from cloudinit.config import cc_zypper_add_repo
from cloudinit import util
-
+from cloudinit.config import cc_zypper_add_repo
from cloudinit.tests import helpers
from cloudinit.tests.helpers import mock
-import logging
-
LOG = logging.getLogger(__name__)
@@ -66,7 +64,8 @@ class TestConfig(helpers.FilesystemMockingTestCase):
root_d = self.tmp_dir()
cc_zypper_add_repo._write_repos(cfg['repos'], root_d)
contents = util.load_file("%s/testing-foo.repo" % root_d)
- parser = self.parse_and_read(StringIO(contents))
+ parser = configparser.ConfigParser()
+ parser.read_string(contents)
expected = {
'testing-foo': {
'name': 'test-foo',
diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
index 987a89c9..44292571 100644
--- a/tests/unittests/test_handler/test_schema.py
+++ b/tests/unittests/test_handler/test_schema.py
@@ -1,5 +1,5 @@
# This file is part of cloud-init. See LICENSE file for license information.
-
+import cloudinit
from cloudinit.config.schema import (
CLOUD_CONFIG_HEADER, SchemaValidationError, annotated_cloudconfig_file,
get_schema_doc, get_schema, validate_cloudconfig_file,
@@ -10,7 +10,9 @@ from cloudinit.tests.helpers import CiTestCase, mock, skipUnlessJsonSchema
from copy import copy
import os
+import pytest
from io import StringIO
+from pathlib import Path
from textwrap import dedent
from yaml import safe_load
@@ -20,16 +22,21 @@ class GetSchemaTest(CiTestCase):
def test_get_schema_coalesces_known_schema(self):
"""Every cloudconfig module with schema is listed in allOf keyword."""
schema = get_schema()
- self.assertItemsEqual(
+ self.assertCountEqual(
[
+ 'cc_apk_configure',
+ 'cc_apt_configure',
'cc_bootcmd',
+ 'cc_locale',
'cc_ntp',
'cc_resizefs',
'cc_runcmd',
'cc_snap',
'cc_ubuntu_advantage',
'cc_ubuntu_drivers',
- 'cc_zypper_add_repo'
+ 'cc_write_files',
+ 'cc_zypper_add_repo',
+ 'cc_chef'
],
[subschema['id'] for subschema in schema['allOf']])
self.assertEqual('cloud-config-schema', schema['id'])
@@ -38,7 +45,7 @@ class GetSchemaTest(CiTestCase):
schema['$schema'])
# FULL_SCHEMA is updated by the get_schema call
from cloudinit.config.schema import FULL_SCHEMA
- self.assertItemsEqual(['id', '$schema', 'allOf'], FULL_SCHEMA.keys())
+ self.assertCountEqual(['id', '$schema', 'allOf'], FULL_SCHEMA.keys())
def test_get_schema_returns_global_when_set(self):
"""When FULL_SCHEMA global is already set, get_schema returns it."""
@@ -110,6 +117,23 @@ class ValidateCloudConfigSchemaTest(CiTestCase):
str(context_mgr.exception))
+class TestCloudConfigExamples:
+ schema = get_schema()
+ params = [
+ (schema["id"], example)
+ for schema in schema["allOf"] for example in schema["examples"]]
+
+ @pytest.mark.parametrize("schema_id,example", params)
+ @skipUnlessJsonSchema()
+ def test_validateconfig_schema_of_example(self, schema_id, example):
+ """ For a given example in a config module we test if it is valid
+ according to the unified schema of all config modules
+ """
+ config_load = safe_load(example)
+ validate_cloudconfig_schema(
+ config_load, self.schema, strict=True)
+
+
class ValidateCloudConfigFileTest(CiTestCase):
"""Tests for validate_cloudconfig_file."""
@@ -268,6 +292,41 @@ class GetSchemaDocTest(CiTestCase):
"""),
get_schema_doc(full_schema))
+ def test_get_schema_doc_properly_parse_description(self):
+ """get_schema_doc description properly formatted"""
+ full_schema = copy(self.required_schema)
+ full_schema.update(
+ {'properties': {
+ 'p1': {
+ 'type': 'string',
+ 'description': dedent("""\
+ This item
+ has the
+ following options:
+
+ - option1
+ - option2
+ - option3
+
+ The default value is
+ option1""")
+ }
+ }}
+ )
+
+ self.assertIn(
+ dedent("""
+ **Config schema**:
+ **p1:** (string) This item has the following options:
+
+ - option1
+ - option2
+ - option3
+
+ The default value is option1
+ """),
+ get_schema_doc(full_schema))
+
def test_get_schema_doc_raises_key_errors(self):
"""get_schema_doc raises KeyErrors on missing keys."""
for key in self.required_schema:
@@ -345,34 +404,30 @@ class MainTest(CiTestCase):
def test_main_missing_args(self):
"""Main exits non-zero and reports an error on missing parameters."""
- with mock.patch('sys.exit', side_effect=self.sys_exit):
- with mock.patch('sys.argv', ['mycmd']):
- with mock.patch('sys.stderr', new_callable=StringIO) as \
- m_stderr:
- with self.assertRaises(SystemExit) as context_manager:
- main()
+ with mock.patch('sys.argv', ['mycmd']):
+ with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
+ with self.assertRaises(SystemExit) as context_manager:
+ main()
self.assertEqual(1, context_manager.exception.code)
self.assertEqual(
- 'Expected either --config-file argument or --doc\n',
+ 'Expected either --config-file argument or --docs\n',
m_stderr.getvalue())
def test_main_absent_config_file(self):
"""Main exits non-zero when config file is absent."""
myargs = ['mycmd', '--annotate', '--config-file', 'NOT_A_FILE']
- with mock.patch('sys.exit', side_effect=self.sys_exit):
- with mock.patch('sys.argv', myargs):
- with mock.patch('sys.stderr', new_callable=StringIO) as \
- m_stderr:
- with self.assertRaises(SystemExit) as context_manager:
- main()
+ with mock.patch('sys.argv', myargs):
+ with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
+ with self.assertRaises(SystemExit) as context_manager:
+ main()
self.assertEqual(1, context_manager.exception.code)
self.assertEqual(
'Configfile NOT_A_FILE does not exist\n',
m_stderr.getvalue())
def test_main_prints_docs(self):
- """When --doc parameter is provided, main generates documentation."""
- myargs = ['mycmd', '--doc']
+ """When --docs parameter is provided, main generates documentation."""
+ myargs = ['mycmd', '--docs', 'all']
with mock.patch('sys.argv', myargs):
with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
self.assertEqual(0, main(), 'Expected 0 exit code')
@@ -430,4 +485,23 @@ class CloudTestsIntegrationTest(CiTestCase):
if errors:
raise AssertionError(', '.join(errors))
+
+def _get_schema_doc_examples():
+ examples_dir = Path(
+ cloudinit.__file__).parent.parent / 'doc' / 'examples'
+ assert examples_dir.is_dir()
+
+ all_text_files = (f for f in examples_dir.glob('cloud-config*.txt')
+ if not f.name.startswith('cloud-config-archive'))
+ return all_text_files
+
+
+class TestSchemaDocExamples:
+ schema = get_schema()
+
+ @pytest.mark.parametrize("example_path", _get_schema_doc_examples())
+ @skipUnlessJsonSchema()
+ def test_schema_doc_examples(self, example_path):
+ validate_cloudconfig_file(str(example_path), self.schema)
+
# vi: ts=4 expandtab syntax=python