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 | |
7 | |
8 | def handle(name, cfg, cloud, log, _args): |
9 | - """ |
10 | - Enable and configure ntp |
11 | + """Enable and configure ntp.""" |
12 | |
13 | - ntp: |
14 | - pools: ['0.{{distro}}.pool.ntp.org', '1.{{distro}}.pool.ntp.org'] |
15 | - servers: ['192.168.2.1'] |
16 | - |
17 | - """ |
18 | + if 'ntp' not in cfg: |
19 | + LOG.debug( |
20 | + "Skipping module named %s, not present or disabled by cfg", name) |
21 | + return |
22 | |
23 | ntp_cfg = cfg.get('ntp', {}) |
24 | |
25 | @@ -69,18 +67,12 @@ def handle(name, cfg, cloud, log, _args): |
26 | " but not a dictionary type," |
27 | " is a %s %instead"), type_utils.obj_name(ntp_cfg)) |
28 | |
29 | - if 'ntp' not in cfg: |
30 | - LOG.debug("Skipping module named %s," |
31 | - "not present or disabled by cfg", name) |
32 | - return True |
33 | - |
34 | rename_ntp_conf() |
35 | # ensure when ntp is installed it has a configuration file |
36 | # to use instead of starting up with packaged defaults |
37 | write_ntp_config_template(ntp_cfg, cloud) |
38 | install_ntp(cloud.distro.install_packages, packages=['ntp'], |
39 | check_exe="ntpd") |
40 | - |
41 | # if ntp was already installed, it may not have started |
42 | try: |
43 | reload_ntp(systemd=cloud.distro.uses_systemd()) |
44 | @@ -98,8 +90,10 @@ def install_ntp(install_func, packages=None, check_exe="ntpd"): |
45 | install_func(packages) |
46 | |
47 | |
48 | -def rename_ntp_conf(config=NTP_CONF): |
49 | +def rename_ntp_conf(config=None): |
50 | """Rename any existing ntp.conf file and render from template""" |
51 | + if config is None: # For testing |
52 | + config = NTP_CONF |
53 | if os.path.exists(config): |
54 | util.rename(config, config + ".dist") |
55 | |
56 | @@ -117,8 +111,9 @@ def write_ntp_config_template(cfg, cloud): |
57 | pools = cfg.get('pools', []) |
58 | |
59 | if len(servers) == 0 and len(pools) == 0: |
60 | - LOG.debug('Adding distro default ntp pool servers') |
61 | pools = generate_server_names(cloud.distro.name) |
62 | + LOG.debug( |
63 | + 'Adding distro default ntp pool servers: %s', ','.join(pools)) |
64 | |
65 | params = { |
66 | '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 | |
73 | import functools |
74 | import json |
75 | +import logging |
76 | import os |
77 | import shutil |
78 | import sys |
79 | @@ -18,6 +19,10 @@ try: |
80 | from contextlib import ExitStack |
81 | except ImportError: |
82 | from contextlib2 import ExitStack |
83 | +try: |
84 | + from cStringIO import StringIO |
85 | +except ImportError: |
86 | + from io import StringIO |
87 | |
88 | from cloudinit import helpers as ch |
89 | from cloudinit import util |
90 | @@ -87,6 +92,27 @@ class TestCase(unittest2.TestCase): |
91 | class CiTestCase(TestCase): |
92 | """This is the preferred test case base class unless user |
93 | needs other test case classes below.""" |
94 | + |
95 | + # Subclass overrides for specific test behavior |
96 | + # Whether or not a unit test needs logfile setup |
97 | + with_logs = False |
98 | + |
99 | + def setUp(self): |
100 | + super(CiTestCase, self).setUp() |
101 | + if self.with_logs: |
102 | + # Create a log handler so unit tests can search expected logs. |
103 | + logger = logging.getLogger() |
104 | + self.logs = StringIO() |
105 | + handler = logging.StreamHandler(self.logs) |
106 | + self.old_handlers = logger.handlers |
107 | + logger.handlers = [handler] |
108 | + |
109 | + def tearDown(self): |
110 | + if self.with_logs: |
111 | + # Remove the handler we setup |
112 | + logging.getLogger().handlers = self.old_handlers |
113 | + super(CiTestCase, self).tearDown() |
114 | + |
115 | def tmp_dir(self, dir=None, cleanup=True): |
116 | # return a full path to a temporary directory that will be cleaned up. |
117 | 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 | |
124 | from cloudinit.config import cc_ntp |
125 | from cloudinit.sources import DataSourceNone |
126 | -from cloudinit import templater |
127 | from cloudinit import (distros, helpers, cloud, util) |
128 | from ..helpers import FilesystemMockingTestCase, mock |
129 | |
130 | -import logging |
131 | + |
132 | import os |
133 | +from os.path import dirname |
134 | import shutil |
135 | -import tempfile |
136 | - |
137 | -LOG = logging.getLogger(__name__) |
138 | |
139 | -NTP_TEMPLATE = """ |
140 | +NTP_TEMPLATE = b"""\ |
141 | ## template: jinja |
142 | - |
143 | -{% if pools %}# pools |
144 | -{% endif %} |
145 | -{% for pool in pools -%} |
146 | -pool {{pool}} iburst |
147 | -{% endfor %} |
148 | -{%- if servers %}# servers |
149 | -{% endif %} |
150 | -{% for server in servers -%} |
151 | -server {{server}} iburst |
152 | -{% endfor %} |
153 | - |
154 | -""" |
155 | - |
156 | - |
157 | -NTP_EXPECTED_UBUNTU = """ |
158 | -# pools |
159 | -pool 0.mycompany.pool.ntp.org iburst |
160 | -# servers |
161 | -server 192.168.23.3 iburst |
162 | - |
163 | +servers {{servers}} |
164 | +pools {{pools}} |
165 | """ |
166 | |
167 | |
168 | class TestNtp(FilesystemMockingTestCase): |
169 | |
170 | + with_logs = True |
171 | + |
172 | def setUp(self): |
173 | super(TestNtp, self).setUp() |
174 | self.subp = util.subp |
175 | - self.new_root = tempfile.mkdtemp() |
176 | - self.addCleanup(shutil.rmtree, self.new_root) |
177 | + self.new_root = self.tmp_dir() |
178 | |
179 | - def _get_cloud(self, distro, metadata=None): |
180 | + def _get_cloud(self, distro): |
181 | self.patchUtils(self.new_root) |
182 | - paths = helpers.Paths({}) |
183 | + paths = helpers.Paths({'templates_dir': self.new_root}) |
184 | cls = distros.fetch(distro) |
185 | mydist = cls(distro, {}, paths) |
186 | myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
187 | - if metadata: |
188 | - myds.metadata.update(metadata) |
189 | return cloud.Cloud(myds, paths, {}, mydist, None) |
190 | |
191 | @mock.patch("cloudinit.config.cc_ntp.util") |
192 | def test_ntp_install(self, mock_util): |
193 | - cc = self._get_cloud('ubuntu') |
194 | - cc.distro = mock.MagicMock() |
195 | - cc.distro.name = 'ubuntu' |
196 | - mock_util.which.return_value = None |
197 | + """ntp_install installs via install_func when check_exe is absent.""" |
198 | + mock_util.which.return_value = None # check_exe not found. |
199 | install_func = mock.MagicMock() |
200 | - |
201 | cc_ntp.install_ntp(install_func, packages=['ntpx'], check_exe='ntpdx') |
202 | |
203 | - self.assertTrue(install_func.called) |
204 | mock_util.which.assert_called_with('ntpdx') |
205 | - install_pkg = install_func.call_args_list[0][0][0] |
206 | - self.assertEqual(sorted(install_pkg), ['ntpx']) |
207 | + install_func.assert_called_once_with(['ntpx']) |
208 | |
209 | @mock.patch("cloudinit.config.cc_ntp.util") |
210 | def test_ntp_install_not_needed(self, mock_util): |
211 | - cc = self._get_cloud('ubuntu') |
212 | - cc.distro = mock.MagicMock() |
213 | - cc.distro.name = 'ubuntu' |
214 | - mock_util.which.return_value = ["/usr/sbin/ntpd"] |
215 | - cc_ntp.install_ntp(cc) |
216 | - self.assertFalse(cc.distro.install_packages.called) |
217 | + """ntp_install doesn't attempt install when check_exe is found.""" |
218 | + mock_util.which.return_value = ["/usr/sbin/ntpd"] # check_exe found. |
219 | + install_func = mock.MagicMock() |
220 | + cc_ntp.install_ntp(install_func, packages=['ntp'], check_exe='ntpd') |
221 | + install_func.assert_not_called() |
222 | |
223 | def test_ntp_rename_ntp_conf(self): |
224 | - with mock.patch.object(os.path, 'exists', |
225 | - return_value=True) as mockpath: |
226 | - with mock.patch.object(util, 'rename') as mockrename: |
227 | - cc_ntp.rename_ntp_conf() |
228 | - |
229 | - mockpath.assert_called_with('/etc/ntp.conf') |
230 | - mockrename.assert_called_with('/etc/ntp.conf', '/etc/ntp.conf.dist') |
231 | + """When NTP_CONF exists, rename_ntp moves it.""" |
232 | + ntpconf = self.tmp_path("ntp.conf", self.new_root) |
233 | + os.mknod(ntpconf) |
234 | + with mock.patch("cloudinit.config.cc_ntp.NTP_CONF", ntpconf): |
235 | + cc_ntp.rename_ntp_conf() |
236 | + self.assertFalse(os.path.exists(ntpconf)) |
237 | + self.assertTrue(os.path.exists("{}.dist".format(ntpconf))) |
238 | |
239 | def test_ntp_rename_ntp_conf_skip_missing(self): |
240 | - with mock.patch.object(os.path, 'exists', |
241 | - return_value=False) as mockpath: |
242 | - with mock.patch.object(util, 'rename') as mockrename: |
243 | - cc_ntp.rename_ntp_conf() |
244 | - |
245 | - mockpath.assert_called_with('/etc/ntp.conf') |
246 | - mockrename.assert_not_called() |
247 | - |
248 | - def ntp_conf_render(self, distro): |
249 | - """ntp_conf_render |
250 | - Test rendering of a ntp.conf from template for a given distro |
251 | + """When NTP_CONF doesn't exist rename_ntp doesn't create a file.""" |
252 | + ntpconf = self.tmp_path("ntp.conf", self.new_root) |
253 | + self.assertFalse(os.path.exists(ntpconf)) |
254 | + with mock.patch("cloudinit.config.cc_ntp.NTP_CONF", ntpconf): |
255 | + cc_ntp.rename_ntp_conf() |
256 | + self.assertFalse(os.path.exists("{}.dist".format(ntpconf))) |
257 | + self.assertFalse(os.path.exists(ntpconf)) |
258 | + |
259 | + def test_write_ntp_config_template_from_ntp_conf_tmpl_with_servers(self): |
260 | + """write_ntp_config_template reads content from ntp.conf.tmpl. |
261 | + |
262 | + It reads ntp.conf.tmpl if present and renders the value from servers |
263 | + key. When no pools key is defined, template is rendered using an empty |
264 | + list for pools. |
265 | """ |
266 | - |
267 | - cfg = {'ntp': {}} |
268 | - mycloud = self._get_cloud(distro) |
269 | - distro_names = cc_ntp.generate_server_names(distro) |
270 | - |
271 | - with mock.patch.object(templater, 'render_to_file') as mocktmpl: |
272 | - with mock.patch.object(os.path, 'isfile', return_value=True): |
273 | - with mock.patch.object(util, 'rename'): |
274 | - cc_ntp.write_ntp_config_template(cfg, mycloud) |
275 | - |
276 | - mocktmpl.assert_called_once_with( |
277 | - ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), |
278 | - '/etc/ntp.conf', |
279 | - {'servers': [], 'pools': distro_names}) |
280 | - |
281 | - def test_ntp_conf_render_rhel(self): |
282 | - """Test templater.render_to_file() for rhel""" |
283 | - self.ntp_conf_render('rhel') |
284 | - |
285 | - def test_ntp_conf_render_debian(self): |
286 | - """Test templater.render_to_file() for debian""" |
287 | - self.ntp_conf_render('debian') |
288 | - |
289 | - def test_ntp_conf_render_fedora(self): |
290 | - """Test templater.render_to_file() for fedora""" |
291 | - self.ntp_conf_render('fedora') |
292 | - |
293 | - def test_ntp_conf_render_sles(self): |
294 | - """Test templater.render_to_file() for sles""" |
295 | - self.ntp_conf_render('sles') |
296 | - |
297 | - def test_ntp_conf_render_ubuntu(self): |
298 | - """Test templater.render_to_file() for ubuntu""" |
299 | - self.ntp_conf_render('ubuntu') |
300 | - |
301 | - def test_ntp_conf_servers_no_pools(self): |
302 | distro = 'ubuntu' |
303 | - pools = [] |
304 | - servers = ['192.168.2.1'] |
305 | cfg = { |
306 | - 'ntp': { |
307 | - 'pools': pools, |
308 | - 'servers': servers, |
309 | - } |
310 | + 'servers': ['192.168.2.1', '192.168.2.2'] |
311 | } |
312 | mycloud = self._get_cloud(distro) |
313 | - |
314 | - with mock.patch.object(templater, 'render_to_file') as mocktmpl: |
315 | - with mock.patch.object(os.path, 'isfile', return_value=True): |
316 | - with mock.patch.object(util, 'rename'): |
317 | - cc_ntp.write_ntp_config_template(cfg.get('ntp'), mycloud) |
318 | - |
319 | - mocktmpl.assert_called_once_with( |
320 | - ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), |
321 | - '/etc/ntp.conf', |
322 | - {'servers': servers, 'pools': pools}) |
323 | - |
324 | - def test_ntp_conf_custom_pools_no_server(self): |
325 | + ntp_conf = self.tmp_path("ntp.conf", self.new_root) # Doesn't exist |
326 | + # Create ntp.conf.tmpl |
327 | + with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
328 | + stream.write(NTP_TEMPLATE) |
329 | + with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
330 | + cc_ntp.write_ntp_config_template(cfg, mycloud) |
331 | + content = util.read_file_or_url('file://' + ntp_conf).contents |
332 | + self.assertEqual( |
333 | + "servers ['192.168.2.1', '192.168.2.2']\npools []\n", |
334 | + content.decode()) |
335 | + |
336 | + def test_write_ntp_config_template_uses_ntp_conf_distro_no_servers(self): |
337 | + """write_ntp_config_template reads content from ntp.conf.distro.tmpl. |
338 | + |
339 | + It reads ntp.conf.<distro>.tmpl before attempting ntp.conf.tmpl. It |
340 | + renders the value from the keys servers and pools. When no |
341 | + servers value is present, template is rendered using an empty list. |
342 | + """ |
343 | distro = 'ubuntu' |
344 | - pools = ['0.mycompany.pool.ntp.org'] |
345 | - servers = [] |
346 | cfg = { |
347 | - 'ntp': { |
348 | - 'pools': pools, |
349 | - 'servers': servers, |
350 | - } |
351 | + 'pools': ['10.0.0.1', '10.0.0.2'] |
352 | } |
353 | mycloud = self._get_cloud(distro) |
354 | - |
355 | - with mock.patch.object(templater, 'render_to_file') as mocktmpl: |
356 | - with mock.patch.object(os.path, 'isfile', return_value=True): |
357 | - with mock.patch.object(util, 'rename'): |
358 | - cc_ntp.write_ntp_config_template(cfg.get('ntp'), mycloud) |
359 | - |
360 | - mocktmpl.assert_called_once_with( |
361 | - ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), |
362 | - '/etc/ntp.conf', |
363 | - {'servers': servers, 'pools': pools}) |
364 | - |
365 | - def test_ntp_conf_custom_pools_and_server(self): |
366 | + ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist |
367 | + # Create ntp.conf.tmpl which isn't read |
368 | + with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
369 | + stream.write(b'NOT READ: ntp.conf.<distro>.tmpl is primary') |
370 | + # Create ntp.conf.tmpl.<distro> |
371 | + with open('{}.{}.tmpl'.format(ntp_conf, distro), 'wb') as stream: |
372 | + stream.write(NTP_TEMPLATE) |
373 | + with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
374 | + cc_ntp.write_ntp_config_template(cfg, mycloud) |
375 | + content = util.read_file_or_url('file://' + ntp_conf).contents |
376 | + self.assertEqual( |
377 | + "servers []\npools ['10.0.0.1', '10.0.0.2']\n", |
378 | + content.decode()) |
379 | + |
380 | + def test_write_ntp_config_template_defaults_pools_when_empty_lists(self): |
381 | + """write_ntp_config_template defaults pools servers upon empty config. |
382 | + |
383 | + When both pools and servers are empty, default NR_POOL_SERVERS get |
384 | + configured. |
385 | + """ |
386 | distro = 'ubuntu' |
387 | - pools = ['0.mycompany.pool.ntp.org'] |
388 | - servers = ['192.168.23.3'] |
389 | - cfg = { |
390 | - 'ntp': { |
391 | - 'pools': pools, |
392 | - 'servers': servers, |
393 | - } |
394 | - } |
395 | mycloud = self._get_cloud(distro) |
396 | - |
397 | - with mock.patch.object(templater, 'render_to_file') as mocktmpl: |
398 | - with mock.patch.object(os.path, 'isfile', return_value=True): |
399 | - with mock.patch.object(util, 'rename'): |
400 | - cc_ntp.write_ntp_config_template(cfg.get('ntp'), mycloud) |
401 | - |
402 | - mocktmpl.assert_called_once_with( |
403 | - ('/etc/cloud/templates/ntp.conf.%s.tmpl' % distro), |
404 | - '/etc/ntp.conf', |
405 | - {'servers': servers, 'pools': pools}) |
406 | - |
407 | - def test_ntp_conf_contents_match(self): |
408 | - """Test rendered contents of /etc/ntp.conf for ubuntu""" |
409 | - pools = ['0.mycompany.pool.ntp.org'] |
410 | - servers = ['192.168.23.3'] |
411 | + ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist |
412 | + # Create ntp.conf.tmpl |
413 | + with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
414 | + stream.write(NTP_TEMPLATE) |
415 | + with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
416 | + cc_ntp.write_ntp_config_template({}, mycloud) |
417 | + content = util.read_file_or_url('file://' + ntp_conf).contents |
418 | + default_pools = [ |
419 | + "{}.{}.pool.ntp.org".format(x, distro) |
420 | + for x in range(0, cc_ntp.NR_POOL_SERVERS)] |
421 | + self.assertEqual( |
422 | + "servers []\npools {}\n".format(default_pools), |
423 | + content.decode()) |
424 | + self.assertIn( |
425 | + "Adding distro default ntp pool servers: {}".format( |
426 | + ",".join(default_pools)), |
427 | + self.logs.getvalue()) |
428 | + |
429 | + def test_ntp_handler_mocked_template(self): |
430 | + """Test ntp handler renders ubuntu ntp.conf template.""" |
431 | + pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org'] |
432 | + servers = ['192.168.23.3', '192.168.23.4'] |
433 | cfg = { |
434 | 'ntp': { |
435 | 'pools': pools, |
436 | - 'servers': servers, |
437 | + 'servers': servers |
438 | } |
439 | } |
440 | mycloud = self._get_cloud('ubuntu') |
441 | - side_effect = [NTP_TEMPLATE.lstrip()] |
442 | - |
443 | - # work backwards from util.write_file and mock out call path |
444 | - # write_ntp_config_template() |
445 | - # cloud.get_template_filename() |
446 | - # os.path.isfile() |
447 | - # templater.render_to_file() |
448 | - # templater.render_from_file() |
449 | - # util.load_file() |
450 | - # util.write_file() |
451 | - # |
452 | - with mock.patch.object(util, 'write_file') as mockwrite: |
453 | - with mock.patch.object(util, 'load_file', side_effect=side_effect): |
454 | - with mock.patch.object(os.path, 'isfile', return_value=True): |
455 | - with mock.patch.object(util, 'rename'): |
456 | - cc_ntp.write_ntp_config_template(cfg.get('ntp'), |
457 | - mycloud) |
458 | - |
459 | - mockwrite.assert_called_once_with( |
460 | - '/etc/ntp.conf', |
461 | - NTP_EXPECTED_UBUNTU, |
462 | - mode=420) |
463 | - |
464 | - def test_ntp_handler(self): |
465 | - """Test ntp handler renders ubuntu ntp.conf template""" |
466 | - pools = ['0.mycompany.pool.ntp.org'] |
467 | - servers = ['192.168.23.3'] |
468 | + ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist |
469 | + # Create ntp.conf.tmpl |
470 | + with open('{}.tmpl'.format(ntp_conf), 'wb') as stream: |
471 | + stream.write(NTP_TEMPLATE) |
472 | + with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
473 | + with mock.patch.object(util, 'which', return_value=None): |
474 | + cc_ntp.handle('notimportant', cfg, mycloud, None, None) |
475 | + |
476 | + content = util.read_file_or_url('file://' + ntp_conf).contents |
477 | + self.assertEqual( |
478 | + 'servers {}\npools {}\n'.format(servers, pools), |
479 | + content.decode()) |
480 | + |
481 | + def test_ntp_handler_real_distro_templates(self): |
482 | + """Test ntp handler renders the shipped distro ntp.conf templates.""" |
483 | + pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org'] |
484 | + servers = ['192.168.23.3', '192.168.23.4'] |
485 | cfg = { |
486 | 'ntp': { |
487 | 'pools': pools, |
488 | - 'servers': servers, |
489 | + 'servers': servers |
490 | } |
491 | } |
492 | - mycloud = self._get_cloud('ubuntu') |
493 | - mycloud.distro = mock.MagicMock() |
494 | - mycloud.distro.uses_systemd.return_value = True |
495 | - side_effect = [NTP_TEMPLATE.lstrip()] |
496 | - |
497 | - with mock.patch.object(util, 'which', return_value=None): |
498 | - with mock.patch.object(os.path, 'exists'): |
499 | - with mock.patch.object(util, 'write_file') as mockwrite: |
500 | - with mock.patch.object(util, 'load_file', |
501 | - side_effect=side_effect): |
502 | - with mock.patch.object(os.path, 'isfile', |
503 | - return_value=True): |
504 | - with mock.patch.object(util, 'rename'): |
505 | - with mock.patch.object(util, 'subp') as msubp: |
506 | - cc_ntp.handle("notimportant", cfg, |
507 | - mycloud, LOG, None) |
508 | - |
509 | - mockwrite.assert_called_once_with( |
510 | - '/etc/ntp.conf', |
511 | - NTP_EXPECTED_UBUNTU, |
512 | - mode=420) |
513 | - msubp.assert_any_call(['systemctl', 'reload-or-restart', 'ntp'], |
514 | - capture=True) |
515 | - |
516 | - @mock.patch("cloudinit.config.cc_ntp.install_ntp") |
517 | - @mock.patch("cloudinit.config.cc_ntp.write_ntp_config_template") |
518 | - @mock.patch("cloudinit.config.cc_ntp.rename_ntp_conf") |
519 | - def test_write_config_before_install(self, mock_ntp_rename, |
520 | - mock_ntp_write_config, |
521 | - mock_install_ntp): |
522 | - pools = ['0.mycompany.pool.ntp.org'] |
523 | - servers = ['192.168.23.3'] |
524 | - cfg = { |
525 | - 'ntp': { |
526 | - 'pools': pools, |
527 | - 'servers': servers, |
528 | - } |
529 | - } |
530 | - cc = self._get_cloud('ubuntu') |
531 | - cc.distro = mock.MagicMock() |
532 | - mock_parent = mock.MagicMock() |
533 | - mock_parent.attach_mock(mock_ntp_rename, 'mock_ntp_rename') |
534 | - mock_parent.attach_mock(mock_ntp_write_config, 'mock_ntp_write_config') |
535 | - mock_parent.attach_mock(mock_install_ntp, 'mock_install_ntp') |
536 | - |
537 | - cc_ntp.handle('cc_ntp', cfg, cc, LOG, None) |
538 | - |
539 | - """Check call order""" |
540 | - mock_parent.assert_has_calls([ |
541 | - mock.call.mock_ntp_rename(), |
542 | - mock.call.mock_ntp_write_config(cfg.get('ntp'), cc), |
543 | - mock.call.mock_install_ntp(cc.distro.install_packages, |
544 | - packages=['ntp'], check_exe="ntpd")]) |
545 | - |
546 | - @mock.patch("cloudinit.config.cc_ntp.reload_ntp") |
547 | - @mock.patch("cloudinit.config.cc_ntp.install_ntp") |
548 | - @mock.patch("cloudinit.config.cc_ntp.write_ntp_config_template") |
549 | - @mock.patch("cloudinit.config.cc_ntp.rename_ntp_conf") |
550 | - def test_reload_ntp_fail_raises_exception(self, mock_rename, |
551 | - mock_write_conf, |
552 | - mock_install, |
553 | - mock_reload): |
554 | - pools = ['0.mycompany.pool.ntp.org'] |
555 | - servers = ['192.168.23.3'] |
556 | - cfg = { |
557 | - 'ntp': { |
558 | - 'pools': pools, |
559 | - 'servers': servers, |
560 | - } |
561 | - } |
562 | - cc = self._get_cloud('ubuntu') |
563 | - cc.distro = mock.MagicMock() |
564 | - |
565 | - mock_reload.side_effect = [util.ProcessExecutionError] |
566 | - self.assertRaises(util.ProcessExecutionError, |
567 | - cc_ntp.handle, 'cc_ntp', |
568 | - cfg, cc, LOG, None) |
569 | - |
570 | - @mock.patch("cloudinit.config.cc_ntp.util") |
571 | - def test_no_ntpcfg_does_nothing(self, mock_util): |
572 | - cc = self._get_cloud('ubuntu') |
573 | - cc.distro = mock.MagicMock() |
574 | - cc_ntp.handle('cc_ntp', {}, cc, LOG, []) |
575 | - self.assertFalse(cc.distro.install_packages.called) |
576 | - self.assertFalse(mock_util.subp.called) |
577 | - |
578 | - @mock.patch("cloudinit.config.cc_ntp.util") |
579 | - def test_reload_ntp_systemd(self, mock_util): |
580 | - cc_ntp.reload_ntp(systemd=True) |
581 | - self.assertTrue(mock_util.subp.called) |
582 | - mock_util.subp.assert_called_with( |
583 | - ['systemctl', 'reload-or-restart', 'ntp'], capture=True) |
584 | - |
585 | - @mock.patch("cloudinit.config.cc_ntp.util") |
586 | - def test_reload_ntp_service(self, mock_util): |
587 | - cc_ntp.reload_ntp(systemd=False) |
588 | - self.assertTrue(mock_util.subp.called) |
589 | - mock_util.subp.assert_called_with( |
590 | - ['service', 'ntp', 'restart'], capture=True) |
591 | - |
592 | + ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist |
593 | + for distro in ('debian', 'ubuntu', 'fedora', 'rhel', 'sles'): |
594 | + mycloud = self._get_cloud(distro) |
595 | + root_dir = dirname(dirname(os.path.realpath(util.__file__))) |
596 | + tmpl_file = os.path.join( |
597 | + '{}/templates/ntp.conf.{}.tmpl'.format(root_dir, distro)) |
598 | + # Create a copy in our tmp_dir |
599 | + shutil.copy( |
600 | + tmpl_file, |
601 | + os.path.join(self.new_root, 'ntp.conf.%s.tmpl' % distro)) |
602 | + with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
603 | + with mock.patch.object(util, 'which', return_value=[True]): |
604 | + cc_ntp.handle('notimportant', cfg, mycloud, None, None) |
605 | + |
606 | + content = util.read_file_or_url('file://' + ntp_conf).contents |
607 | + expected_servers = '\n'.join([ |
608 | + 'server {} iburst'.format(server) for server in servers]) |
609 | + self.assertIn( |
610 | + expected_servers, content.decode(), |
611 | + 'failed to render ntp.conf for distro:{}'.format(distro)) |
612 | + expected_pools = '\n'.join([ |
613 | + 'pool {} iburst'.format(pool) for pool in pools]) |
614 | + self.assertIn( |
615 | + expected_pools, content.decode(), |
616 | + 'failed to render ntp.conf for distro:{}'.format(distro)) |
617 | + |
618 | + def test_no_ntpcfg_does_nothing(self): |
619 | + """When no ntp section is defined handler logs a warning and noops.""" |
620 | + cc_ntp.handle('cc_ntp', {}, None, None, []) |
621 | + self.assertEqual( |
622 | + 'Skipping module named cc_ntp, not present or disabled by cfg\n', |
623 | + self.logs.getvalue()) |
624 | |
625 | # 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:/