Merge ~chad.smith/cloud-init:cc-ntp-testing into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- cc-ntp-testing
- Merge into master
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Scott Moser | ||||
Approved revision: | 29d0bb1cffd00ec3df0c2effd4a649af07390911 | ||||
Merged at revision: | fd0c88cf8e8aa015eadb5ab842e872cb627197ec | ||||
Proposed branch: | ~chad.smith/cloud-init:cc-ntp-testing | ||||
Merge into: | cloud-init:master | ||||
Diff against target: |
625 lines (+188/-304) 3 files modified
cloudinit/config/cc_ntp.py (+10/-15) tests/unittests/helpers.py (+26/-0) tests/unittests/test_handler/test_handler_ntp.py (+152/-289) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+324450@code.launchpad.net |
Commit message
cc_ntp: Restructure cc_ntp unit tests add with_logs class attr to unit tests.
Any CiTestCase subclass can now set a class attribute with_logs = True and tests can now make assertions on self.logs.
- greater risk: mocks are permanent within the scope, so multiple call-sites could be affected by package mocks
- less legible tests: each mock doesn't advertise the actual call-site
- tight coupling: the unit test logic to tightly bound to the actual implementation in remote (unrelated) modules which makes it more costly to maintain code
- false success: we should be testing the expected behavior not specific remote method names as we want to know if that underlying behavior changes and breaks us.
LP: #1692794
Description of the change
cc_ntp: Restructure cc_ntp unit tests add with_logs class attr to unit tests.
Any CiTestCase subclass can now set a class attribute with_logs = True and tests can now make assertions on self.logs.
- greater risk: mocks are permanent within the scope, so multiple call-sites could be affected by package mocks
- less legible tests: each mock doesn't advertise the actual call-site
- tight coupling: the unit test logic to tightly bound to the actual implementation in remote (unrelated) modules which makes it more costly to maintain code
- false success: we should be testing the expected behavior not specific remote method names as we want to know if that underlying behavior changes and breaks us.
LP: #1692794
Server Team CI bot (server-team-bot) wrote : | # |
- e4f1154... by Chad Smith
-
lints
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e4f11545162
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) : | # |
Scott Moser (smoser) wrote : | # |
some comments in line.
I'm generally in agreement here.
Chad Smith (chad.smith) : | # |
- 49c4eb8... by Chad Smith
-
use util.read_
file_or_ url instead of open()read(). replace ntp_conf in write_.._template
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:49c4eb85daa
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 15a8bc4... by Chad Smith
-
add unit test for the real ntp templates for each distro. drop unused metadata param
Chad Smith (chad.smith) wrote : | # |
@scott Addressed your review comments thanks a lot. So unittest runtimes 1m30sec versus 2m20sec? Do we want to make the switch to have all CiTestCase subclasses logged?
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:15a8bc4dbb7
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
- 29d0bb1... by Chad Smith
-
calculate root_dir path for templates directory from util import as sys.path will be different on test environments
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:29d0bb1cffd
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
I'm happy with this if you are.
You mentioned some improvement on the root_dir path calculation. You can do that here or in a follow on.
Just say which you want.
Chad Smith (chad.smith) wrote : | # |
+1 let's land this as it's getting bigger. I've added a card/bug to work on a better root_dir path calculation in a followup.
Preview Diff
1 | diff --git a/cloudinit/config/cc_ntp.py b/cloudinit/config/cc_ntp.py | |||
2 | index 225f898..5cc5453 100644 | |||
3 | --- a/cloudinit/config/cc_ntp.py | |||
4 | +++ b/cloudinit/config/cc_ntp.py | |||
5 | @@ -53,14 +53,12 @@ distros = ['centos', 'debian', 'fedora', 'opensuse', 'ubuntu'] | |||
6 | 53 | 53 | ||
7 | 54 | 54 | ||
8 | 55 | def handle(name, cfg, cloud, log, _args): | 55 | def handle(name, cfg, cloud, log, _args): |
11 | 56 | """ | 56 | """Enable and configure ntp.""" |
10 | 57 | Enable and configure ntp | ||
12 | 58 | 57 | ||
18 | 59 | ntp: | 58 | if 'ntp' not in cfg: |
19 | 60 | pools: ['0.{{distro}}.pool.ntp.org', '1.{{distro}}.pool.ntp.org'] | 59 | LOG.debug( |
20 | 61 | servers: ['192.168.2.1'] | 60 | "Skipping module named %s, not present or disabled by cfg", name) |
21 | 62 | 61 | return | |
17 | 63 | """ | ||
22 | 64 | 62 | ||
23 | 65 | ntp_cfg = cfg.get('ntp', {}) | 63 | ntp_cfg = cfg.get('ntp', {}) |
24 | 66 | 64 | ||
25 | @@ -69,18 +67,12 @@ def handle(name, cfg, cloud, log, _args): | |||
26 | 69 | " but not a dictionary type," | 67 | " but not a dictionary type," |
27 | 70 | " is a %s %instead"), type_utils.obj_name(ntp_cfg)) | 68 | " is a %s %instead"), type_utils.obj_name(ntp_cfg)) |
28 | 71 | 69 | ||
29 | 72 | if 'ntp' not in cfg: | ||
30 | 73 | LOG.debug("Skipping module named %s," | ||
31 | 74 | "not present or disabled by cfg", name) | ||
32 | 75 | return True | ||
33 | 76 | |||
34 | 77 | rename_ntp_conf() | 70 | rename_ntp_conf() |
35 | 78 | # ensure when ntp is installed it has a configuration file | 71 | # ensure when ntp is installed it has a configuration file |
36 | 79 | # to use instead of starting up with packaged defaults | 72 | # to use instead of starting up with packaged defaults |
37 | 80 | write_ntp_config_template(ntp_cfg, cloud) | 73 | write_ntp_config_template(ntp_cfg, cloud) |
38 | 81 | install_ntp(cloud.distro.install_packages, packages=['ntp'], | 74 | install_ntp(cloud.distro.install_packages, packages=['ntp'], |
39 | 82 | check_exe="ntpd") | 75 | check_exe="ntpd") |
40 | 83 | |||
41 | 84 | # if ntp was already installed, it may not have started | 76 | # if ntp was already installed, it may not have started |
42 | 85 | try: | 77 | try: |
43 | 86 | reload_ntp(systemd=cloud.distro.uses_systemd()) | 78 | reload_ntp(systemd=cloud.distro.uses_systemd()) |
44 | @@ -98,8 +90,10 @@ def install_ntp(install_func, packages=None, check_exe="ntpd"): | |||
45 | 98 | install_func(packages) | 90 | install_func(packages) |
46 | 99 | 91 | ||
47 | 100 | 92 | ||
49 | 101 | def rename_ntp_conf(config=NTP_CONF): | 93 | def rename_ntp_conf(config=None): |
50 | 102 | """Rename any existing ntp.conf file and render from template""" | 94 | """Rename any existing ntp.conf file and render from template""" |
51 | 95 | if config is None: # For testing | ||
52 | 96 | config = NTP_CONF | ||
53 | 103 | if os.path.exists(config): | 97 | if os.path.exists(config): |
54 | 104 | util.rename(config, config + ".dist") | 98 | util.rename(config, config + ".dist") |
55 | 105 | 99 | ||
56 | @@ -117,8 +111,9 @@ def write_ntp_config_template(cfg, cloud): | |||
57 | 117 | pools = cfg.get('pools', []) | 111 | pools = cfg.get('pools', []) |
58 | 118 | 112 | ||
59 | 119 | if len(servers) == 0 and len(pools) == 0: | 113 | if len(servers) == 0 and len(pools) == 0: |
60 | 120 | LOG.debug('Adding distro default ntp pool servers') | ||
61 | 121 | pools = generate_server_names(cloud.distro.name) | 114 | pools = generate_server_names(cloud.distro.name) |
62 | 115 | LOG.debug( | ||
63 | 116 | 'Adding distro default ntp pool servers: %s', ','.join(pools)) | ||
64 | 122 | 117 | ||
65 | 123 | params = { | 118 | params = { |
66 | 124 | 'servers': servers, | 119 | 'servers': servers, |
67 | diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py | |||
68 | index d24f817..9ff1599 100644 | |||
69 | --- a/tests/unittests/helpers.py | |||
70 | +++ b/tests/unittests/helpers.py | |||
71 | @@ -4,6 +4,7 @@ from __future__ import print_function | |||
72 | 4 | 4 | ||
73 | 5 | import functools | 5 | import functools |
74 | 6 | import json | 6 | import json |
75 | 7 | import logging | ||
76 | 7 | import os | 8 | import os |
77 | 8 | import shutil | 9 | import shutil |
78 | 9 | import sys | 10 | import sys |
79 | @@ -18,6 +19,10 @@ try: | |||
80 | 18 | from contextlib import ExitStack | 19 | from contextlib import ExitStack |
81 | 19 | except ImportError: | 20 | except ImportError: |
82 | 20 | from contextlib2 import ExitStack | 21 | from contextlib2 import ExitStack |
83 | 22 | try: | ||
84 | 23 | from cStringIO import StringIO | ||
85 | 24 | except ImportError: | ||
86 | 25 | from io import StringIO | ||
87 | 21 | 26 | ||
88 | 22 | from cloudinit import helpers as ch | 27 | from cloudinit import helpers as ch |
89 | 23 | from cloudinit import util | 28 | from cloudinit import util |
90 | @@ -87,6 +92,27 @@ class TestCase(unittest2.TestCase): | |||
91 | 87 | class CiTestCase(TestCase): | 92 | class CiTestCase(TestCase): |
92 | 88 | """This is the preferred test case base class unless user | 93 | """This is the preferred test case base class unless user |
93 | 89 | needs other test case classes below.""" | 94 | needs other test case classes below.""" |
94 | 95 | |||
95 | 96 | # Subclass overrides for specific test behavior | ||
96 | 97 | # Whether or not a unit test needs logfile setup | ||
97 | 98 | with_logs = False | ||
98 | 99 | |||
99 | 100 | def setUp(self): | ||
100 | 101 | super(CiTestCase, self).setUp() | ||
101 | 102 | if self.with_logs: | ||
102 | 103 | # Create a log handler so unit tests can search expected logs. | ||
103 | 104 | logger = logging.getLogger() | ||
104 | 105 | self.logs = StringIO() | ||
105 | 106 | handler = logging.StreamHandler(self.logs) | ||
106 | 107 | self.old_handlers = logger.handlers | ||
107 | 108 | logger.handlers = [handler] | ||
108 | 109 | |||
109 | 110 | def tearDown(self): | ||
110 | 111 | if self.with_logs: | ||
111 | 112 | # Remove the handler we setup | ||
112 | 113 | logging.getLogger().handlers = self.old_handlers | ||
113 | 114 | super(CiTestCase, self).tearDown() | ||
114 | 115 | |||
115 | 90 | def tmp_dir(self, dir=None, cleanup=True): | 116 | def tmp_dir(self, dir=None, cleanup=True): |
116 | 91 | # return a full path to a temporary directory that will be cleaned up. | 117 | # return a full path to a temporary directory that will be cleaned up. |
117 | 92 | if dir is None: | 118 | if dir is None: |
118 | diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py | |||
119 | index 02bd304..21f2ab1 100644 | |||
120 | --- a/tests/unittests/test_handler/test_handler_ntp.py | |||
121 | +++ b/tests/unittests/test_handler/test_handler_ntp.py | |||
122 | @@ -2,351 +2,214 @@ | |||
123 | 2 | 2 | ||
124 | 3 | from cloudinit.config import cc_ntp | 3 | from cloudinit.config import cc_ntp |
125 | 4 | from cloudinit.sources import DataSourceNone | 4 | from cloudinit.sources import DataSourceNone |
126 | 5 | from cloudinit import templater | ||
127 | 6 | from cloudinit import (distros, helpers, cloud, util) | 5 | from cloudinit import (distros, helpers, cloud, util) |
128 | 7 | from ..helpers import FilesystemMockingTestCase, mock | 6 | from ..helpers import FilesystemMockingTestCase, mock |
129 | 8 | 7 | ||
131 | 9 | import logging | 8 | |
132 | 10 | import os | 9 | import os |
133 | 10 | from os.path import dirname | ||
134 | 11 | import shutil | 11 | import shutil |
135 | 12 | import tempfile | ||
136 | 13 | |||
137 | 14 | LOG = logging.getLogger(__name__) | ||
138 | 15 | 12 | ||
140 | 16 | NTP_TEMPLATE = """ | 13 | NTP_TEMPLATE = b"""\ |
141 | 17 | ## template: jinja | 14 | ## template: jinja |
163 | 18 | 15 | servers {{servers}} | |
164 | 19 | {% if pools %}# pools | 16 | pools {{pools}} |
144 | 20 | {% endif %} | ||
145 | 21 | {% for pool in pools -%} | ||
146 | 22 | pool {{pool}} iburst | ||
147 | 23 | {% endfor %} | ||
148 | 24 | {%- if servers %}# servers | ||
149 | 25 | {% endif %} | ||
150 | 26 | {% for server in servers -%} | ||
151 | 27 | server {{server}} iburst | ||
152 | 28 | {% endfor %} | ||
153 | 29 | |||
154 | 30 | """ | ||
155 | 31 | |||
156 | 32 | |||
157 | 33 | NTP_EXPECTED_UBUNTU = """ | ||
158 | 34 | # pools | ||
159 | 35 | pool 0.mycompany.pool.ntp.org iburst | ||
160 | 36 | # servers | ||
161 | 37 | server 192.168.23.3 iburst | ||
162 | 38 | |||
165 | 39 | """ | 17 | """ |
166 | 40 | 18 | ||
167 | 41 | 19 | ||
168 | 42 | class TestNtp(FilesystemMockingTestCase): | 20 | class TestNtp(FilesystemMockingTestCase): |
169 | 43 | 21 | ||
170 | 22 | with_logs = True | ||
171 | 23 | |||
172 | 44 | def setUp(self): | 24 | def setUp(self): |
173 | 45 | super(TestNtp, self).setUp() | 25 | super(TestNtp, self).setUp() |
174 | 46 | self.subp = util.subp | 26 | self.subp = util.subp |
177 | 47 | self.new_root = tempfile.mkdtemp() | 27 | self.new_root = self.tmp_dir() |
176 | 48 | self.addCleanup(shutil.rmtree, self.new_root) | ||
178 | 49 | 28 | ||
180 | 50 | def _get_cloud(self, distro, metadata=None): | 29 | def _get_cloud(self, distro): |
181 | 51 | self.patchUtils(self.new_root) | 30 | self.patchUtils(self.new_root) |
183 | 52 | paths = helpers.Paths({}) | 31 | paths = helpers.Paths({'templates_dir': self.new_root}) |
184 | 53 | cls = distros.fetch(distro) | 32 | cls = distros.fetch(distro) |
185 | 54 | mydist = cls(distro, {}, paths) | 33 | mydist = cls(distro, {}, paths) |
186 | 55 | myds = DataSourceNone.DataSourceNone({}, mydist, paths) | 34 | myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
187 | 56 | if metadata: | ||
188 | 57 | myds.metadata.update(metadata) | ||
189 | 58 | return cloud.Cloud(myds, paths, {}, mydist, None) | 35 | return cloud.Cloud(myds, paths, {}, mydist, None) |
190 | 59 | 36 | ||
191 | 60 | @mock.patch("cloudinit.config.cc_ntp.util") | 37 | @mock.patch("cloudinit.config.cc_ntp.util") |
192 | 61 | def test_ntp_install(self, mock_util): | 38 | def test_ntp_install(self, mock_util): |
197 | 62 | cc = self._get_cloud('ubuntu') | 39 | """ntp_install installs via install_func when check_exe is absent.""" |
198 | 63 | cc.distro = mock.MagicMock() | 40 | mock_util.which.return_value = None # check_exe not found. |
195 | 64 | cc.distro.name = 'ubuntu' | ||
196 | 65 | mock_util.which.return_value = None | ||
199 | 66 | install_func = mock.MagicMock() | 41 | install_func = mock.MagicMock() |
200 | 67 | |||
201 | 68 | cc_ntp.install_ntp(install_func, packages=['ntpx'], check_exe='ntpdx') | 42 | cc_ntp.install_ntp(install_func, packages=['ntpx'], check_exe='ntpdx') |
202 | 69 | 43 | ||
203 | 70 | self.assertTrue(install_func.called) | ||
204 | 71 | mock_util.which.assert_called_with('ntpdx') | 44 | mock_util.which.assert_called_with('ntpdx') |
207 | 72 | install_pkg = install_func.call_args_list[0][0][0] | 45 | install_func.assert_called_once_with(['ntpx']) |
206 | 73 | self.assertEqual(sorted(install_pkg), ['ntpx']) | ||
208 | 74 | 46 | ||
209 | 75 | @mock.patch("cloudinit.config.cc_ntp.util") | 47 | @mock.patch("cloudinit.config.cc_ntp.util") |
210 | 76 | def test_ntp_install_not_needed(self, mock_util): | 48 | def test_ntp_install_not_needed(self, mock_util): |
217 | 77 | cc = self._get_cloud('ubuntu') | 49 | """ntp_install doesn't attempt install when check_exe is found.""" |
218 | 78 | cc.distro = mock.MagicMock() | 50 | mock_util.which.return_value = ["/usr/sbin/ntpd"] # check_exe found. |
219 | 79 | cc.distro.name = 'ubuntu' | 51 | install_func = mock.MagicMock() |
220 | 80 | mock_util.which.return_value = ["/usr/sbin/ntpd"] | 52 | cc_ntp.install_ntp(install_func, packages=['ntp'], check_exe='ntpd') |
221 | 81 | cc_ntp.install_ntp(cc) | 53 | install_func.assert_not_called() |
216 | 82 | self.assertFalse(cc.distro.install_packages.called) | ||
222 | 83 | 54 | ||
223 | 84 | def test_ntp_rename_ntp_conf(self): | 55 | def test_ntp_rename_ntp_conf(self): |
231 | 85 | with mock.patch.object(os.path, 'exists', | 56 | """When NTP_CONF exists, rename_ntp moves it.""" |
232 | 86 | return_value=True) as mockpath: | 57 | ntpconf = self.tmp_path("ntp.conf", self.new_root) |
233 | 87 | with mock.patch.object(util, 'rename') as mockrename: | 58 | os.mknod(ntpconf) |
234 | 88 | cc_ntp.rename_ntp_conf() | 59 | with mock.patch("cloudinit.config.cc_ntp.NTP_CONF", ntpconf): |
235 | 89 | 60 | cc_ntp.rename_ntp_conf() | |
236 | 90 | mockpath.assert_called_with('/etc/ntp.conf') | 61 | self.assertFalse(os.path.exists(ntpconf)) |
237 | 91 | mockrename.assert_called_with('/etc/ntp.conf', '/etc/ntp.conf.dist') | 62 | self.assertTrue(os.path.exists("{}.dist".format(ntpconf))) |
238 | 92 | 63 | ||
239 | 93 | def test_ntp_rename_ntp_conf_skip_missing(self): | 64 | def test_ntp_rename_ntp_conf_skip_missing(self): |
251 | 94 | with mock.patch.object(os.path, 'exists', | 65 | """When NTP_CONF doesn't exist rename_ntp doesn't create a file.""" |
252 | 95 | return_value=False) as mockpath: | 66 | ntpconf = self.tmp_path("ntp.conf", self.new_root) |
253 | 96 | with mock.patch.object(util, 'rename') as mockrename: | 67 | self.assertFalse(os.path.exists(ntpconf)) |
254 | 97 | cc_ntp.rename_ntp_conf() | 68 | with mock.patch("cloudinit.config.cc_ntp.NTP_CONF", ntpconf): |
255 | 98 | 69 | cc_ntp.rename_ntp_conf() | |
256 | 99 | mockpath.assert_called_with('/etc/ntp.conf') | 70 | self.assertFalse(os.path.exists("{}.dist".format(ntpconf))) |
257 | 100 | mockrename.assert_not_called() | 71 | self.assertFalse(os.path.exists(ntpconf)) |
258 | 101 | 72 | ||
259 | 102 | def ntp_conf_render(self, distro): | 73 | def test_write_ntp_config_template_from_ntp_conf_tmpl_with_servers(self): |
260 | 103 | """ntp_conf_render | 74 | """write_ntp_config_template reads content from ntp.conf.tmpl. |
261 | 104 | Test rendering of a ntp.conf from template for a given distro | 75 | |
262 | 76 | It reads ntp.conf.tmpl if present and renders the value from servers | ||
263 | 77 | key. When no pools key is defined, template is rendered using an empty | ||
264 | 78 | list for pools. | ||
265 | 105 | """ | 79 | """ |
266 | 106 | |||
267 | 107 | cfg = {'ntp': {}} | ||
268 | 108 | mycloud = self._get_cloud(distro) | ||
269 | 109 | distro_names = cc_ntp.generate_server_names(distro) | ||
270 | 110 | |||
271 | 111 | with mock.patch.object(templater, 'render_to_file') as mocktmpl: | ||
272 | 112 | with mock.patch.object(os.path, 'isfile', return_value=True): | ||
273 | 113 | with mock.patch.object(util, 'rename'): | ||
274 | 114 | cc_ntp.write_ntp_config_template(cfg, mycloud) | ||
275 | 115 | |||
276 | 116 | mocktmpl.assert_called_once_with( | ||
277 | 117 | ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), | ||
278 | 118 | '/etc/ntp.conf', | ||
279 | 119 | {'servers': [], 'pools': distro_names}) | ||
280 | 120 | |||
281 | 121 | def test_ntp_conf_render_rhel(self): | ||
282 | 122 | """Test templater.render_to_file() for rhel""" | ||
283 | 123 | self.ntp_conf_render('rhel') | ||
284 | 124 | |||
285 | 125 | def test_ntp_conf_render_debian(self): | ||
286 | 126 | """Test templater.render_to_file() for debian""" | ||
287 | 127 | self.ntp_conf_render('debian') | ||
288 | 128 | |||
289 | 129 | def test_ntp_conf_render_fedora(self): | ||
290 | 130 | """Test templater.render_to_file() for fedora""" | ||
291 | 131 | self.ntp_conf_render('fedora') | ||
292 | 132 | |||
293 | 133 | def test_ntp_conf_render_sles(self): | ||
294 | 134 | """Test templater.render_to_file() for sles""" | ||
295 | 135 | self.ntp_conf_render('sles') | ||
296 | 136 | |||
297 | 137 | def test_ntp_conf_render_ubuntu(self): | ||
298 | 138 | """Test templater.render_to_file() for ubuntu""" | ||
299 | 139 | self.ntp_conf_render('ubuntu') | ||
300 | 140 | |||
301 | 141 | def test_ntp_conf_servers_no_pools(self): | ||
302 | 142 | distro = 'ubuntu' | 80 | distro = 'ubuntu' |
303 | 143 | pools = [] | ||
304 | 144 | servers = ['192.168.2.1'] | ||
305 | 145 | cfg = { | 81 | cfg = { |
310 | 146 | 'ntp': { | 82 | 'servers': ['192.168.2.1', '192.168.2.2'] |
307 | 147 | 'pools': pools, | ||
308 | 148 | 'servers': servers, | ||
309 | 149 | } | ||
311 | 150 | } | 83 | } |
312 | 151 | mycloud = self._get_cloud(distro) | 84 | mycloud = self._get_cloud(distro) |
325 | 152 | 85 | ntp_conf = self.tmp_path("ntp.conf", self.new_root) # Doesn't exist | |
326 | 153 | with mock.patch.object(templater, 'render_to_file') as mocktmpl: | 86 | # Create ntp.conf.tmpl |
327 | 154 | with mock.patch.object(os.path, 'isfile', return_value=True): | 87 | with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
328 | 155 | with mock.patch.object(util, 'rename'): | 88 | stream.write(NTP_TEMPLATE) |
329 | 156 | cc_ntp.write_ntp_config_template(cfg.get('ntp'), mycloud) | 89 | with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
330 | 157 | 90 | cc_ntp.write_ntp_config_template(cfg, mycloud) | |
331 | 158 | mocktmpl.assert_called_once_with( | 91 | content = util.read_file_or_url('file://' + ntp_conf).contents |
332 | 159 | ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), | 92 | self.assertEqual( |
333 | 160 | '/etc/ntp.conf', | 93 | "servers ['192.168.2.1', '192.168.2.2']\npools []\n", |
334 | 161 | {'servers': servers, 'pools': pools}) | 94 | content.decode()) |
335 | 162 | 95 | ||
336 | 163 | def test_ntp_conf_custom_pools_no_server(self): | 96 | def test_write_ntp_config_template_uses_ntp_conf_distro_no_servers(self): |
337 | 97 | """write_ntp_config_template reads content from ntp.conf.distro.tmpl. | ||
338 | 98 | |||
339 | 99 | It reads ntp.conf.<distro>.tmpl before attempting ntp.conf.tmpl. It | ||
340 | 100 | renders the value from the keys servers and pools. When no | ||
341 | 101 | servers value is present, template is rendered using an empty list. | ||
342 | 102 | """ | ||
343 | 164 | distro = 'ubuntu' | 103 | distro = 'ubuntu' |
344 | 165 | pools = ['0.mycompany.pool.ntp.org'] | ||
345 | 166 | servers = [] | ||
346 | 167 | cfg = { | 104 | cfg = { |
351 | 168 | 'ntp': { | 105 | 'pools': ['10.0.0.1', '10.0.0.2'] |
348 | 169 | 'pools': pools, | ||
349 | 170 | 'servers': servers, | ||
350 | 171 | } | ||
352 | 172 | } | 106 | } |
353 | 173 | mycloud = self._get_cloud(distro) | 107 | mycloud = self._get_cloud(distro) |
366 | 174 | 108 | ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist | |
367 | 175 | with mock.patch.object(templater, 'render_to_file') as mocktmpl: | 109 | # Create ntp.conf.tmpl which isn't read |
368 | 176 | with mock.patch.object(os.path, 'isfile', return_value=True): | 110 | with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
369 | 177 | with mock.patch.object(util, 'rename'): | 111 | stream.write(b'NOT READ: ntp.conf.<distro>.tmpl is primary') |
370 | 178 | cc_ntp.write_ntp_config_template(cfg.get('ntp'), mycloud) | 112 | # Create ntp.conf.tmpl.<distro> |
371 | 179 | 113 | with open('{}.{}.tmpl'.format(ntp_conf, distro), 'wb') as stream: | |
372 | 180 | mocktmpl.assert_called_once_with( | 114 | stream.write(NTP_TEMPLATE) |
373 | 181 | ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), | 115 | with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
374 | 182 | '/etc/ntp.conf', | 116 | cc_ntp.write_ntp_config_template(cfg, mycloud) |
375 | 183 | {'servers': servers, 'pools': pools}) | 117 | content = util.read_file_or_url('file://' + ntp_conf).contents |
376 | 184 | 118 | self.assertEqual( | |
377 | 185 | def test_ntp_conf_custom_pools_and_server(self): | 119 | "servers []\npools ['10.0.0.1', '10.0.0.2']\n", |
378 | 120 | content.decode()) | ||
379 | 121 | |||
380 | 122 | def test_write_ntp_config_template_defaults_pools_when_empty_lists(self): | ||
381 | 123 | """write_ntp_config_template defaults pools servers upon empty config. | ||
382 | 124 | |||
383 | 125 | When both pools and servers are empty, default NR_POOL_SERVERS get | ||
384 | 126 | configured. | ||
385 | 127 | """ | ||
386 | 186 | distro = 'ubuntu' | 128 | distro = 'ubuntu' |
387 | 187 | pools = ['0.mycompany.pool.ntp.org'] | ||
388 | 188 | servers = ['192.168.23.3'] | ||
389 | 189 | cfg = { | ||
390 | 190 | 'ntp': { | ||
391 | 191 | 'pools': pools, | ||
392 | 192 | 'servers': servers, | ||
393 | 193 | } | ||
394 | 194 | } | ||
395 | 195 | mycloud = self._get_cloud(distro) | 129 | mycloud = self._get_cloud(distro) |
411 | 196 | 130 | ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist | |
412 | 197 | with mock.patch.object(templater, 'render_to_file') as mocktmpl: | 131 | # Create ntp.conf.tmpl |
413 | 198 | with mock.patch.object(os.path, 'isfile', return_value=True): | 132 | with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
414 | 199 | with mock.patch.object(util, 'rename'): | 133 | stream.write(NTP_TEMPLATE) |
415 | 200 | cc_ntp.write_ntp_config_template(cfg.get('ntp'), mycloud) | 134 | with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
416 | 201 | 135 | cc_ntp.write_ntp_config_template({}, mycloud) | |
417 | 202 | mocktmpl.assert_called_once_with( | 136 | content = util.read_file_or_url('file://' + ntp_conf).contents |
418 | 203 | ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), | 137 | default_pools = [ |
419 | 204 | '/etc/ntp.conf', | 138 | "{}.{}.pool.ntp.org".format(x, distro) |
420 | 205 | {'servers': servers, 'pools': pools}) | 139 | for x in range(0, cc_ntp.NR_POOL_SERVERS)] |
421 | 206 | 140 | self.assertEqual( | |
422 | 207 | def test_ntp_conf_contents_match(self): | 141 | "servers []\npools {}\n".format(default_pools), |
423 | 208 | """Test rendered contents of /etc/ntp.conf for ubuntu""" | 142 | content.decode()) |
424 | 209 | pools = ['0.mycompany.pool.ntp.org'] | 143 | self.assertIn( |
425 | 210 | servers = ['192.168.23.3'] | 144 | "Adding distro default ntp pool servers: {}".format( |
426 | 145 | ",".join(default_pools)), | ||
427 | 146 | self.logs.getvalue()) | ||
428 | 147 | |||
429 | 148 | def test_ntp_handler_mocked_template(self): | ||
430 | 149 | """Test ntp handler renders ubuntu ntp.conf template.""" | ||
431 | 150 | pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org'] | ||
432 | 151 | servers = ['192.168.23.3', '192.168.23.4'] | ||
433 | 211 | cfg = { | 152 | cfg = { |
434 | 212 | 'ntp': { | 153 | 'ntp': { |
435 | 213 | 'pools': pools, | 154 | 'pools': pools, |
437 | 214 | 'servers': servers, | 155 | 'servers': servers |
438 | 215 | } | 156 | } |
439 | 216 | } | 157 | } |
440 | 217 | mycloud = self._get_cloud('ubuntu') | 158 | mycloud = self._get_cloud('ubuntu') |
468 | 218 | side_effect = [NTP_TEMPLATE.lstrip()] | 159 | ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist |
469 | 219 | 160 | # Create ntp.conf.tmpl | |
470 | 220 | # work backwards from util.write_file and mock out call path | 161 | with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
471 | 221 | # write_ntp_config_template() | 162 | stream.write(NTP_TEMPLATE) |
472 | 222 | # cloud.get_template_filename() | 163 | with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
473 | 223 | # os.path.isfile() | 164 | with mock.patch.object(util, 'which', return_value=None): |
474 | 224 | # templater.render_to_file() | 165 | cc_ntp.handle('notimportant', cfg, mycloud, None, None) |
475 | 225 | # templater.render_from_file() | 166 | |
476 | 226 | # util.load_file() | 167 | content = util.read_file_or_url('file://' + ntp_conf).contents |
477 | 227 | # util.write_file() | 168 | self.assertEqual( |
478 | 228 | # | 169 | 'servers {}\npools {}\n'.format(servers, pools), |
479 | 229 | with mock.patch.object(util, 'write_file') as mockwrite: | 170 | content.decode()) |
480 | 230 | with mock.patch.object(util, 'load_file', side_effect=side_effect): | 171 | |
481 | 231 | with mock.patch.object(os.path, 'isfile', return_value=True): | 172 | def test_ntp_handler_real_distro_templates(self): |
482 | 232 | with mock.patch.object(util, 'rename'): | 173 | """Test ntp handler renders the shipped distro ntp.conf templates.""" |
483 | 233 | cc_ntp.write_ntp_config_template(cfg.get('ntp'), | 174 | pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org'] |
484 | 234 | mycloud) | 175 | servers = ['192.168.23.3', '192.168.23.4'] |
458 | 235 | |||
459 | 236 | mockwrite.assert_called_once_with( | ||
460 | 237 | '/etc/ntp.conf', | ||
461 | 238 | NTP_EXPECTED_UBUNTU, | ||
462 | 239 | mode=420) | ||
463 | 240 | |||
464 | 241 | def test_ntp_handler(self): | ||
465 | 242 | """Test ntp handler renders ubuntu ntp.conf template""" | ||
466 | 243 | pools = ['0.mycompany.pool.ntp.org'] | ||
467 | 244 | servers = ['192.168.23.3'] | ||
485 | 245 | cfg = { | 176 | cfg = { |
486 | 246 | 'ntp': { | 177 | 'ntp': { |
487 | 247 | 'pools': pools, | 178 | 'pools': pools, |
489 | 248 | 'servers': servers, | 179 | 'servers': servers |
490 | 249 | } | 180 | } |
491 | 250 | } | 181 | } |
592 | 251 | mycloud = self._get_cloud('ubuntu') | 182 | ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist |
593 | 252 | mycloud.distro = mock.MagicMock() | 183 | for distro in ('debian', 'ubuntu', 'fedora', 'rhel', 'sles'): |
594 | 253 | mycloud.distro.uses_systemd.return_value = True | 184 | mycloud = self._get_cloud(distro) |
595 | 254 | side_effect = [NTP_TEMPLATE.lstrip()] | 185 | root_dir = dirname(dirname(os.path.realpath(util.__file__))) |
596 | 255 | 186 | tmpl_file = os.path.join( | |
597 | 256 | with mock.patch.object(util, 'which', return_value=None): | 187 | '{}/templates/ntp.conf.{}.tmpl'.format(root_dir, distro)) |
598 | 257 | with mock.patch.object(os.path, 'exists'): | 188 | # Create a copy in our tmp_dir |
599 | 258 | with mock.patch.object(util, 'write_file') as mockwrite: | 189 | shutil.copy( |
600 | 259 | with mock.patch.object(util, 'load_file', | 190 | tmpl_file, |
601 | 260 | side_effect=side_effect): | 191 | os.path.join(self.new_root, 'ntp.conf.%s.tmpl' % distro)) |
602 | 261 | with mock.patch.object(os.path, 'isfile', | 192 | with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
603 | 262 | return_value=True): | 193 | with mock.patch.object(util, 'which', return_value=[True]): |
604 | 263 | with mock.patch.object(util, 'rename'): | 194 | cc_ntp.handle('notimportant', cfg, mycloud, None, None) |
605 | 264 | with mock.patch.object(util, 'subp') as msubp: | 195 | |
606 | 265 | cc_ntp.handle("notimportant", cfg, | 196 | content = util.read_file_or_url('file://' + ntp_conf).contents |
607 | 266 | mycloud, LOG, None) | 197 | expected_servers = '\n'.join([ |
608 | 267 | 198 | 'server {} iburst'.format(server) for server in servers]) | |
609 | 268 | mockwrite.assert_called_once_with( | 199 | self.assertIn( |
610 | 269 | '/etc/ntp.conf', | 200 | expected_servers, content.decode(), |
611 | 270 | NTP_EXPECTED_UBUNTU, | 201 | 'failed to render ntp.conf for distro:{}'.format(distro)) |
612 | 271 | mode=420) | 202 | expected_pools = '\n'.join([ |
613 | 272 | msubp.assert_any_call(['systemctl', 'reload-or-restart', 'ntp'], | 203 | 'pool {} iburst'.format(pool) for pool in pools]) |
614 | 273 | capture=True) | 204 | self.assertIn( |
615 | 274 | 205 | expected_pools, content.decode(), | |
616 | 275 | @mock.patch("cloudinit.config.cc_ntp.install_ntp") | 206 | 'failed to render ntp.conf for distro:{}'.format(distro)) |
617 | 276 | @mock.patch("cloudinit.config.cc_ntp.write_ntp_config_template") | 207 | |
618 | 277 | @mock.patch("cloudinit.config.cc_ntp.rename_ntp_conf") | 208 | def test_no_ntpcfg_does_nothing(self): |
619 | 278 | def test_write_config_before_install(self, mock_ntp_rename, | 209 | """When no ntp section is defined handler logs a warning and noops.""" |
620 | 279 | mock_ntp_write_config, | 210 | cc_ntp.handle('cc_ntp', {}, None, None, []) |
621 | 280 | mock_install_ntp): | 211 | self.assertEqual( |
622 | 281 | pools = ['0.mycompany.pool.ntp.org'] | 212 | 'Skipping module named cc_ntp, not present or disabled by cfg\n', |
623 | 282 | servers = ['192.168.23.3'] | 213 | self.logs.getvalue()) |
524 | 283 | cfg = { | ||
525 | 284 | 'ntp': { | ||
526 | 285 | 'pools': pools, | ||
527 | 286 | 'servers': servers, | ||
528 | 287 | } | ||
529 | 288 | } | ||
530 | 289 | cc = self._get_cloud('ubuntu') | ||
531 | 290 | cc.distro = mock.MagicMock() | ||
532 | 291 | mock_parent = mock.MagicMock() | ||
533 | 292 | mock_parent.attach_mock(mock_ntp_rename, 'mock_ntp_rename') | ||
534 | 293 | mock_parent.attach_mock(mock_ntp_write_config, 'mock_ntp_write_config') | ||
535 | 294 | mock_parent.attach_mock(mock_install_ntp, 'mock_install_ntp') | ||
536 | 295 | |||
537 | 296 | cc_ntp.handle('cc_ntp', cfg, cc, LOG, None) | ||
538 | 297 | |||
539 | 298 | """Check call order""" | ||
540 | 299 | mock_parent.assert_has_calls([ | ||
541 | 300 | mock.call.mock_ntp_rename(), | ||
542 | 301 | mock.call.mock_ntp_write_config(cfg.get('ntp'), cc), | ||
543 | 302 | mock.call.mock_install_ntp(cc.distro.install_packages, | ||
544 | 303 | packages=['ntp'], check_exe="ntpd")]) | ||
545 | 304 | |||
546 | 305 | @mock.patch("cloudinit.config.cc_ntp.reload_ntp") | ||
547 | 306 | @mock.patch("cloudinit.config.cc_ntp.install_ntp") | ||
548 | 307 | @mock.patch("cloudinit.config.cc_ntp.write_ntp_config_template") | ||
549 | 308 | @mock.patch("cloudinit.config.cc_ntp.rename_ntp_conf") | ||
550 | 309 | def test_reload_ntp_fail_raises_exception(self, mock_rename, | ||
551 | 310 | mock_write_conf, | ||
552 | 311 | mock_install, | ||
553 | 312 | mock_reload): | ||
554 | 313 | pools = ['0.mycompany.pool.ntp.org'] | ||
555 | 314 | servers = ['192.168.23.3'] | ||
556 | 315 | cfg = { | ||
557 | 316 | 'ntp': { | ||
558 | 317 | 'pools': pools, | ||
559 | 318 | 'servers': servers, | ||
560 | 319 | } | ||
561 | 320 | } | ||
562 | 321 | cc = self._get_cloud('ubuntu') | ||
563 | 322 | cc.distro = mock.MagicMock() | ||
564 | 323 | |||
565 | 324 | mock_reload.side_effect = [util.ProcessExecutionError] | ||
566 | 325 | self.assertRaises(util.ProcessExecutionError, | ||
567 | 326 | cc_ntp.handle, 'cc_ntp', | ||
568 | 327 | cfg, cc, LOG, None) | ||
569 | 328 | |||
570 | 329 | @mock.patch("cloudinit.config.cc_ntp.util") | ||
571 | 330 | def test_no_ntpcfg_does_nothing(self, mock_util): | ||
572 | 331 | cc = self._get_cloud('ubuntu') | ||
573 | 332 | cc.distro = mock.MagicMock() | ||
574 | 333 | cc_ntp.handle('cc_ntp', {}, cc, LOG, []) | ||
575 | 334 | self.assertFalse(cc.distro.install_packages.called) | ||
576 | 335 | self.assertFalse(mock_util.subp.called) | ||
577 | 336 | |||
578 | 337 | @mock.patch("cloudinit.config.cc_ntp.util") | ||
579 | 338 | def test_reload_ntp_systemd(self, mock_util): | ||
580 | 339 | cc_ntp.reload_ntp(systemd=True) | ||
581 | 340 | self.assertTrue(mock_util.subp.called) | ||
582 | 341 | mock_util.subp.assert_called_with( | ||
583 | 342 | ['systemctl', 'reload-or-restart', 'ntp'], capture=True) | ||
584 | 343 | |||
585 | 344 | @mock.patch("cloudinit.config.cc_ntp.util") | ||
586 | 345 | def test_reload_ntp_service(self, mock_util): | ||
587 | 346 | cc_ntp.reload_ntp(systemd=False) | ||
588 | 347 | self.assertTrue(mock_util.subp.called) | ||
589 | 348 | mock_util.subp.assert_called_with( | ||
590 | 349 | ['service', 'ntp', 'restart'], capture=True) | ||
591 | 350 | |||
624 | 351 | 214 | ||
625 | 352 | # vi: ts=4 expandtab | 215 | # vi: ts=4 expandtab |
FAILED: Continuous integration, rev:5ededba3f22 2795dea69af9d7d 7e187c3fdf8bba /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 377/ /jenkins. ubuntu. com/server/ job/cloud- init-ci/ nodes=metal- amd64/377/ console /jenkins. ubuntu. com/server/ job/cloud- init-ci/ nodes=metal- arm64/377/ console /jenkins. ubuntu. com/server/ job/cloud- init-ci/ nodes=metal- ppc64el/ 377/console /jenkins. ubuntu. com/server/ job/cloud- init-ci/ nodes=metal- s390x/377/ console /jenkins. ubuntu. com/server/ job/cloud- init-ci/ nodes=vm- i386/377/ console
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 377/rebuild
https:/