Merge ~chad.smith/cloud-init:ubuntu/artful into cloud-init:ubuntu/artful
- Git
- lp:~chad.smith/cloud-init
- ubuntu/artful
- Merge into ubuntu/artful
Proposed by
Chad Smith
Status: | Merged |
---|---|
Merged at revision: | c491d8344104610bfd3ea0cb48ca4403c789a23d |
Proposed branch: | ~chad.smith/cloud-init:ubuntu/artful |
Merge into: | cloud-init:ubuntu/artful |
Diff against target: |
667 lines (+221/-77) 15 files modified
cloudinit/net/dhcp.py (+29/-15) cloudinit/net/network_state.py (+8/-0) cloudinit/net/sysconfig.py (+15/-0) cloudinit/net/tests/test_dhcp.py (+61/-5) cloudinit/sources/DataSourceAzure.py (+3/-26) cloudinit/util.py (+22/-0) debian/changelog (+13/-0) tests/cloud_tests/images/nocloudkvm.py (+15/-7) tests/cloud_tests/instances/nocloudkvm.py (+5/-3) tests/cloud_tests/platforms/nocloudkvm.py (+11/-10) tests/cloud_tests/releases.yaml (+16/-0) tests/cloud_tests/setup_image.py (+3/-3) tests/cloud_tests/snapshots/nocloudkvm.py (+11/-6) tests/unittests/test_datasource/test_azure.py (+3/-2) tests/unittests/test_net.py (+6/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser | Pending | ||
Review via email: mp+334594@code.launchpad.net |
Commit message
Description of the change
Upstream snapshot for SRU into Artful
To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote : | # |
review:
Approve
(continuous-integration)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py |
2 | index d8624d8..875a460 100644 |
3 | --- a/cloudinit/net/dhcp.py |
4 | +++ b/cloudinit/net/dhcp.py |
5 | @@ -36,22 +36,23 @@ def maybe_perform_dhcp_discovery(nic=None): |
6 | skip dhcp_discovery and return an empty dict. |
7 | |
8 | @param nic: Name of the network interface we want to run dhclient on. |
9 | - @return: A dict of dhcp options from the dhclient discovery if run, |
10 | - otherwise an empty dict is returned. |
11 | + @return: A list of dicts representing dhcp options for each lease obtained |
12 | + from the dhclient discovery if run, otherwise an empty list is |
13 | + returned. |
14 | """ |
15 | if nic is None: |
16 | nic = find_fallback_nic() |
17 | if nic is None: |
18 | LOG.debug('Skip dhcp_discovery: Unable to find fallback nic.') |
19 | - return {} |
20 | + return [] |
21 | elif nic not in get_devicelist(): |
22 | LOG.debug( |
23 | 'Skip dhcp_discovery: nic %s not found in get_devicelist.', nic) |
24 | - return {} |
25 | + return [] |
26 | dhclient_path = util.which('dhclient') |
27 | if not dhclient_path: |
28 | LOG.debug('Skip dhclient configuration: No dhclient command found.') |
29 | - return {} |
30 | + return [] |
31 | with temp_utils.tempdir(prefix='cloud-init-dhcp-', needs_exe=True) as tdir: |
32 | # Use /var/tmp because /run/cloud-init/tmp is mounted noexec |
33 | return dhcp_discovery(dhclient_path, nic, tdir) |
34 | @@ -60,8 +61,8 @@ def maybe_perform_dhcp_discovery(nic=None): |
35 | def parse_dhcp_lease_file(lease_file): |
36 | """Parse the given dhcp lease file for the most recent lease. |
37 | |
38 | - Return a dict of dhcp options as key value pairs for the most recent lease |
39 | - block. |
40 | + Return a list of dicts of dhcp options. Each dict contains key value pairs |
41 | + a specific lease in order from oldest to newest. |
42 | |
43 | @raises: InvalidDHCPLeaseFileError on empty of unparseable leasefile |
44 | content. |
45 | @@ -96,8 +97,8 @@ def dhcp_discovery(dhclient_cmd_path, interface, cleandir): |
46 | @param cleandir: The directory from which to run dhclient as well as store |
47 | dhcp leases. |
48 | |
49 | - @return: A dict of dhcp options parsed from the dhcp.leases file or empty |
50 | - dict. |
51 | + @return: A list of dicts of representing the dhcp leases parsed from the |
52 | + dhcp.leases file or empty list. |
53 | """ |
54 | LOG.debug('Performing a dhcp discovery on %s', interface) |
55 | |
56 | @@ -119,13 +120,26 @@ def dhcp_discovery(dhclient_cmd_path, interface, cleandir): |
57 | cmd = [sandbox_dhclient_cmd, '-1', '-v', '-lf', lease_file, |
58 | '-pf', pid_file, interface, '-sf', '/bin/true'] |
59 | util.subp(cmd, capture=True) |
60 | - pid = None |
61 | + |
62 | + # dhclient doesn't write a pid file until after it forks when it gets a |
63 | + # proper lease response. Since cleandir is a temp directory that gets |
64 | + # removed, we need to wait for that pidfile creation before the |
65 | + # cleandir is removed, otherwise we get FileNotFound errors. |
66 | + missing = util.wait_for_files( |
67 | + [pid_file, lease_file], maxwait=5, naplen=0.01) |
68 | + if missing: |
69 | + LOG.warning("dhclient did not produce expected files: %s", |
70 | + ', '.join(os.path.basename(f) for f in missing)) |
71 | + return [] |
72 | + pid_content = util.load_file(pid_file).strip() |
73 | try: |
74 | - pid = int(util.load_file(pid_file).strip()) |
75 | - return parse_dhcp_lease_file(lease_file) |
76 | - finally: |
77 | - if pid: |
78 | - os.kill(pid, signal.SIGKILL) |
79 | + pid = int(pid_content) |
80 | + except ValueError: |
81 | + LOG.debug( |
82 | + "pid file contains non-integer content '%s'", pid_content) |
83 | + else: |
84 | + os.kill(pid, signal.SIGKILL) |
85 | + return parse_dhcp_lease_file(lease_file) |
86 | |
87 | |
88 | def networkd_parse_lease(content): |
89 | diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py |
90 | index 0e830ee..e9e2cf4 100644 |
91 | --- a/cloudinit/net/network_state.py |
92 | +++ b/cloudinit/net/network_state.py |
93 | @@ -746,6 +746,14 @@ def _normalize_subnet(subnet): |
94 | _normalize_net_keys(normal_subnet, address_keys=('address',))) |
95 | normal_subnet['routes'] = [_normalize_route(r) |
96 | for r in subnet.get('routes', [])] |
97 | + |
98 | + def listify(snet, name): |
99 | + if name in snet and not isinstance(snet[name], list): |
100 | + snet[name] = snet[name].split() |
101 | + |
102 | + for k in ('dns_search', 'dns_nameservers'): |
103 | + listify(normal_subnet, k) |
104 | + |
105 | return normal_subnet |
106 | |
107 | |
108 | diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py |
109 | index f572796..39d89c4 100644 |
110 | --- a/cloudinit/net/sysconfig.py |
111 | +++ b/cloudinit/net/sysconfig.py |
112 | @@ -7,12 +7,15 @@ import six |
113 | |
114 | from cloudinit.distros.parsers import networkmanager_conf |
115 | from cloudinit.distros.parsers import resolv_conf |
116 | +from cloudinit import log as logging |
117 | from cloudinit import util |
118 | |
119 | from . import renderer |
120 | from .network_state import ( |
121 | is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6) |
122 | |
123 | +LOG = logging.getLogger(__name__) |
124 | + |
125 | |
126 | def _make_header(sep='#'): |
127 | lines = [ |
128 | @@ -347,6 +350,18 @@ class Renderer(renderer.Renderer): |
129 | else: |
130 | iface_cfg['GATEWAY'] = subnet['gateway'] |
131 | |
132 | + if 'dns_search' in subnet: |
133 | + iface_cfg['DOMAIN'] = ' '.join(subnet['dns_search']) |
134 | + |
135 | + if 'dns_nameservers' in subnet: |
136 | + if len(subnet['dns_nameservers']) > 3: |
137 | + # per resolv.conf(5) MAXNS sets this to 3. |
138 | + LOG.debug("%s has %d entries in dns_nameservers. " |
139 | + "Only 3 are used.", iface_cfg.name, |
140 | + len(subnet['dns_nameservers'])) |
141 | + for i, k in enumerate(subnet['dns_nameservers'][:3], 1): |
142 | + iface_cfg['DNS' + str(i)] = k |
143 | + |
144 | @classmethod |
145 | def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): |
146 | for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
147 | diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py |
148 | index 3d8e15c..db25b6f 100644 |
149 | --- a/cloudinit/net/tests/test_dhcp.py |
150 | +++ b/cloudinit/net/tests/test_dhcp.py |
151 | @@ -1,6 +1,5 @@ |
152 | # This file is part of cloud-init. See LICENSE file for license information. |
153 | |
154 | -import mock |
155 | import os |
156 | import signal |
157 | from textwrap import dedent |
158 | @@ -9,7 +8,8 @@ from cloudinit.net.dhcp import ( |
159 | InvalidDHCPLeaseFileError, maybe_perform_dhcp_discovery, |
160 | parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases) |
161 | from cloudinit.util import ensure_file, write_file |
162 | -from cloudinit.tests.helpers import CiTestCase, wrap_and_call, populate_dir |
163 | +from cloudinit.tests.helpers import ( |
164 | + CiTestCase, mock, populate_dir, wrap_and_call) |
165 | |
166 | |
167 | class TestParseDHCPLeasesFile(CiTestCase): |
168 | @@ -69,14 +69,14 @@ class TestDHCPDiscoveryClean(CiTestCase): |
169 | def test_no_fallback_nic_found(self, m_fallback_nic): |
170 | """Log and do nothing when nic is absent and no fallback is found.""" |
171 | m_fallback_nic.return_value = None # No fallback nic found |
172 | - self.assertEqual({}, maybe_perform_dhcp_discovery()) |
173 | + self.assertEqual([], maybe_perform_dhcp_discovery()) |
174 | self.assertIn( |
175 | 'Skip dhcp_discovery: Unable to find fallback nic.', |
176 | self.logs.getvalue()) |
177 | |
178 | def test_provided_nic_does_not_exist(self): |
179 | """When the provided nic doesn't exist, log a message and no-op.""" |
180 | - self.assertEqual({}, maybe_perform_dhcp_discovery('idontexist')) |
181 | + self.assertEqual([], maybe_perform_dhcp_discovery('idontexist')) |
182 | self.assertIn( |
183 | 'Skip dhcp_discovery: nic idontexist not found in get_devicelist.', |
184 | self.logs.getvalue()) |
185 | @@ -87,7 +87,7 @@ class TestDHCPDiscoveryClean(CiTestCase): |
186 | """When dhclient doesn't exist in the OS, log the issue and no-op.""" |
187 | m_fallback.return_value = 'eth9' |
188 | m_which.return_value = None # dhclient isn't found |
189 | - self.assertEqual({}, maybe_perform_dhcp_discovery()) |
190 | + self.assertEqual([], maybe_perform_dhcp_discovery()) |
191 | self.assertIn( |
192 | 'Skip dhclient configuration: No dhclient command found.', |
193 | self.logs.getvalue()) |
194 | @@ -117,6 +117,62 @@ class TestDHCPDiscoveryClean(CiTestCase): |
195 | |
196 | @mock.patch('cloudinit.net.dhcp.os.kill') |
197 | @mock.patch('cloudinit.net.dhcp.util.subp') |
198 | + def test_dhcp_discovery_run_in_sandbox_warns_invalid_pid(self, m_subp, |
199 | + m_kill): |
200 | + """dhcp_discovery logs a warning when pidfile contains invalid content. |
201 | + |
202 | + Lease processing still occurs and no proc kill is attempted. |
203 | + """ |
204 | + tmpdir = self.tmp_dir() |
205 | + dhclient_script = os.path.join(tmpdir, 'dhclient.orig') |
206 | + script_content = '#!/bin/bash\necho fake-dhclient' |
207 | + write_file(dhclient_script, script_content, mode=0o755) |
208 | + write_file(self.tmp_path('dhclient.pid', tmpdir), '') # Empty pid '' |
209 | + lease_content = dedent(""" |
210 | + lease { |
211 | + interface "eth9"; |
212 | + fixed-address 192.168.2.74; |
213 | + option subnet-mask 255.255.255.0; |
214 | + option routers 192.168.2.1; |
215 | + } |
216 | + """) |
217 | + write_file(self.tmp_path('dhcp.leases', tmpdir), lease_content) |
218 | + |
219 | + self.assertItemsEqual( |
220 | + [{'interface': 'eth9', 'fixed-address': '192.168.2.74', |
221 | + 'subnet-mask': '255.255.255.0', 'routers': '192.168.2.1'}], |
222 | + dhcp_discovery(dhclient_script, 'eth9', tmpdir)) |
223 | + self.assertIn( |
224 | + "pid file contains non-integer content ''", self.logs.getvalue()) |
225 | + m_kill.assert_not_called() |
226 | + |
227 | + @mock.patch('cloudinit.net.dhcp.os.kill') |
228 | + @mock.patch('cloudinit.net.dhcp.util.wait_for_files') |
229 | + @mock.patch('cloudinit.net.dhcp.util.subp') |
230 | + def test_dhcp_discovery_run_in_sandbox_waits_on_lease_and_pid(self, |
231 | + m_subp, |
232 | + m_wait, |
233 | + m_kill): |
234 | + """dhcp_discovery waits for the presence of pidfile and dhcp.leases.""" |
235 | + tmpdir = self.tmp_dir() |
236 | + dhclient_script = os.path.join(tmpdir, 'dhclient.orig') |
237 | + script_content = '#!/bin/bash\necho fake-dhclient' |
238 | + write_file(dhclient_script, script_content, mode=0o755) |
239 | + # Don't create pid or leases file |
240 | + pidfile = self.tmp_path('dhclient.pid', tmpdir) |
241 | + leasefile = self.tmp_path('dhcp.leases', tmpdir) |
242 | + m_wait.return_value = [pidfile] # Return the missing pidfile wait for |
243 | + self.assertEqual([], dhcp_discovery(dhclient_script, 'eth9', tmpdir)) |
244 | + self.assertEqual( |
245 | + mock.call([pidfile, leasefile], maxwait=5, naplen=0.01), |
246 | + m_wait.call_args_list[0]) |
247 | + self.assertIn( |
248 | + 'WARNING: dhclient did not produce expected files: dhclient.pid', |
249 | + self.logs.getvalue()) |
250 | + m_kill.assert_not_called() |
251 | + |
252 | + @mock.patch('cloudinit.net.dhcp.os.kill') |
253 | + @mock.patch('cloudinit.net.dhcp.util.subp') |
254 | def test_dhcp_discovery_run_in_sandbox(self, m_subp, m_kill): |
255 | """dhcp_discovery brings up the interface and runs dhclient. |
256 | |
257 | diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py |
258 | index 8c3492d..14367e9 100644 |
259 | --- a/cloudinit/sources/DataSourceAzure.py |
260 | +++ b/cloudinit/sources/DataSourceAzure.py |
261 | @@ -11,7 +11,6 @@ from functools import partial |
262 | import os |
263 | import os.path |
264 | import re |
265 | -import time |
266 | from xml.dom import minidom |
267 | import xml.etree.ElementTree as ET |
268 | |
269 | @@ -321,7 +320,7 @@ class DataSourceAzure(sources.DataSource): |
270 | # https://bugs.launchpad.net/cloud-init/+bug/1717611 |
271 | missing = util.log_time(logfunc=LOG.debug, |
272 | msg="waiting for SSH public key files", |
273 | - func=wait_for_files, |
274 | + func=util.wait_for_files, |
275 | args=(fp_files, 900)) |
276 | |
277 | if len(missing): |
278 | @@ -556,8 +555,8 @@ def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120, |
279 | is_new_instance=False): |
280 | # wait for ephemeral disk to come up |
281 | naplen = .2 |
282 | - missing = wait_for_files([devpath], maxwait=maxwait, naplen=naplen, |
283 | - log_pre="Azure ephemeral disk: ") |
284 | + missing = util.wait_for_files([devpath], maxwait=maxwait, naplen=naplen, |
285 | + log_pre="Azure ephemeral disk: ") |
286 | |
287 | if missing: |
288 | LOG.warning("ephemeral device '%s' did not appear after %d seconds.", |
289 | @@ -639,28 +638,6 @@ def pubkeys_from_crt_files(flist): |
290 | return pubkeys |
291 | |
292 | |
293 | -def wait_for_files(flist, maxwait, naplen=.5, log_pre=""): |
294 | - need = set(flist) |
295 | - waited = 0 |
296 | - while True: |
297 | - need -= set([f for f in need if os.path.exists(f)]) |
298 | - if len(need) == 0: |
299 | - LOG.debug("%sAll files appeared after %s seconds: %s", |
300 | - log_pre, waited, flist) |
301 | - return [] |
302 | - if waited == 0: |
303 | - LOG.info("%sWaiting up to %s seconds for the following files: %s", |
304 | - log_pre, maxwait, flist) |
305 | - if waited + naplen > maxwait: |
306 | - break |
307 | - time.sleep(naplen) |
308 | - waited += naplen |
309 | - |
310 | - LOG.warning("%sStill missing files after %s seconds: %s", |
311 | - log_pre, maxwait, need) |
312 | - return need |
313 | - |
314 | - |
315 | def write_files(datadir, files, dirmode=None): |
316 | |
317 | def _redact_password(cnt, fname): |
318 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
319 | index e1290aa..6c014ba 100644 |
320 | --- a/cloudinit/util.py |
321 | +++ b/cloudinit/util.py |
322 | @@ -2541,4 +2541,26 @@ def load_shell_content(content, add_empty=False, empty_val=None): |
323 | return data |
324 | |
325 | |
326 | +def wait_for_files(flist, maxwait, naplen=.5, log_pre=""): |
327 | + need = set(flist) |
328 | + waited = 0 |
329 | + while True: |
330 | + need -= set([f for f in need if os.path.exists(f)]) |
331 | + if len(need) == 0: |
332 | + LOG.debug("%sAll files appeared after %s seconds: %s", |
333 | + log_pre, waited, flist) |
334 | + return [] |
335 | + if waited == 0: |
336 | + LOG.debug("%sWaiting up to %s seconds for the following files: %s", |
337 | + log_pre, maxwait, flist) |
338 | + if waited + naplen > maxwait: |
339 | + break |
340 | + time.sleep(naplen) |
341 | + waited += naplen |
342 | + |
343 | + LOG.debug("%sStill missing files after %s seconds: %s", |
344 | + log_pre, maxwait, need) |
345 | + return need |
346 | + |
347 | + |
348 | # vi: ts=4 expandtab |
349 | diff --git a/debian/changelog b/debian/changelog |
350 | index b209fa9..ea72cf2 100644 |
351 | --- a/debian/changelog |
352 | +++ b/debian/changelog |
353 | @@ -1,3 +1,16 @@ |
354 | +cloud-init (17.1-46-g7acc9e68-0ubuntu1~17.10.1) artful-proposed; urgency=medium |
355 | + |
356 | + * New upstream snapshot. |
357 | + - ec2: Fix sandboxed dhclient background process cleanup. |
358 | + (LP: #1735331) |
359 | + - tests: NoCloudKVMImage do not modify the original local cache image. |
360 | + - tests: Enable bionic in integration tests. [Joshua Powers] |
361 | + - tests: Use apt-get to install a deb so that depends get resolved. |
362 | + - sysconfig: Correctly render dns and dns search info. |
363 | + [Ryan McCabe] |
364 | + |
365 | + -- Chad Smith <chad.smith@canonical.com> Fri, 01 Dec 2017 09:58:48 -0700 |
366 | + |
367 | cloud-init (17.1-41-g76243487-0ubuntu1~17.10.1) artful-proposed; urgency=medium |
368 | |
369 | * debian/cloud-init.templates: Fix capitilazation in 'AliYun' |
370 | diff --git a/tests/cloud_tests/images/nocloudkvm.py b/tests/cloud_tests/images/nocloudkvm.py |
371 | index 1e7962c..8678b07 100644 |
372 | --- a/tests/cloud_tests/images/nocloudkvm.py |
373 | +++ b/tests/cloud_tests/images/nocloudkvm.py |
374 | @@ -4,6 +4,10 @@ |
375 | |
376 | from cloudinit import util as c_util |
377 | |
378 | +import os |
379 | +import shutil |
380 | +import tempfile |
381 | + |
382 | from tests.cloud_tests.images import base |
383 | from tests.cloud_tests.snapshots import nocloudkvm as nocloud_kvm_snapshot |
384 | |
385 | @@ -13,7 +17,7 @@ class NoCloudKVMImage(base.Image): |
386 | |
387 | platform_name = "nocloud-kvm" |
388 | |
389 | - def __init__(self, platform, config, img_path): |
390 | + def __init__(self, platform, config, orig_img_path): |
391 | """Set up image. |
392 | |
393 | @param platform: platform object |
394 | @@ -21,7 +25,13 @@ class NoCloudKVMImage(base.Image): |
395 | @param img_path: path to the image |
396 | """ |
397 | self.modified = False |
398 | - self._img_path = img_path |
399 | + self._workd = tempfile.mkdtemp(prefix='NoCloudKVMImage') |
400 | + self._orig_img_path = orig_img_path |
401 | + self._img_path = os.path.join(self._workd, |
402 | + os.path.basename(self._orig_img_path)) |
403 | + |
404 | + c_util.subp(['qemu-img', 'create', '-f', 'qcow2', |
405 | + '-b', orig_img_path, self._img_path]) |
406 | |
407 | super(NoCloudKVMImage, self).__init__(platform, config) |
408 | |
409 | @@ -61,13 +71,9 @@ class NoCloudKVMImage(base.Image): |
410 | if not self._img_path: |
411 | raise RuntimeError() |
412 | |
413 | - instance = self.platform.create_image( |
414 | - self.properties, self.config, self.features, |
415 | - self._img_path, image_desc=str(self), use_desc='snapshot') |
416 | - |
417 | return nocloud_kvm_snapshot.NoCloudKVMSnapshot( |
418 | self.platform, self.properties, self.config, |
419 | - self.features, instance) |
420 | + self.features, self._img_path) |
421 | |
422 | def destroy(self): |
423 | """Unset path to signal image is no longer used. |
424 | @@ -77,6 +83,8 @@ class NoCloudKVMImage(base.Image): |
425 | framework decide whether to keep or destroy everything. |
426 | """ |
427 | self._img_path = None |
428 | + shutil.rmtree(self._workd) |
429 | + |
430 | super(NoCloudKVMImage, self).destroy() |
431 | |
432 | # vi: ts=4 expandtab |
433 | diff --git a/tests/cloud_tests/instances/nocloudkvm.py b/tests/cloud_tests/instances/nocloudkvm.py |
434 | index cc82580..bc06a79 100644 |
435 | --- a/tests/cloud_tests/instances/nocloudkvm.py |
436 | +++ b/tests/cloud_tests/instances/nocloudkvm.py |
437 | @@ -25,12 +25,13 @@ class NoCloudKVMInstance(base.Instance): |
438 | platform_name = "nocloud-kvm" |
439 | _ssh_client = None |
440 | |
441 | - def __init__(self, platform, name, properties, config, features, |
442 | - user_data, meta_data): |
443 | + def __init__(self, platform, name, image_path, properties, config, |
444 | + features, user_data, meta_data): |
445 | """Set up instance. |
446 | |
447 | @param platform: platform object |
448 | @param name: image path |
449 | + @param image_path: path to disk image to boot. |
450 | @param properties: dictionary of properties |
451 | @param config: dictionary of configuration values |
452 | @param features: dictionary of supported feature flags |
453 | @@ -43,6 +44,7 @@ class NoCloudKVMInstance(base.Instance): |
454 | self.pid = None |
455 | self.pid_file = None |
456 | self.console_file = None |
457 | + self.disk = image_path |
458 | |
459 | super(NoCloudKVMInstance, self).__init__( |
460 | platform, name, properties, config, features) |
461 | @@ -145,7 +147,7 @@ class NoCloudKVMInstance(base.Instance): |
462 | self.ssh_port = self.get_free_port() |
463 | |
464 | cmd = ['./tools/xkvm', |
465 | - '--disk', '%s,cache=unsafe' % self.name, |
466 | + '--disk', '%s,cache=unsafe' % self.disk, |
467 | '--disk', '%s,cache=unsafe' % seed, |
468 | '--netdev', ','.join(['user', |
469 | 'hostfwd=tcp::%s-:22' % self.ssh_port, |
470 | diff --git a/tests/cloud_tests/platforms/nocloudkvm.py b/tests/cloud_tests/platforms/nocloudkvm.py |
471 | index f1f8187..76cd83a 100644 |
472 | --- a/tests/cloud_tests/platforms/nocloudkvm.py |
473 | +++ b/tests/cloud_tests/platforms/nocloudkvm.py |
474 | @@ -55,19 +55,20 @@ class NoCloudKVMPlatform(base.Platform): |
475 | for fname in glob.iglob(search_d, recursive=True): |
476 | images.append(fname) |
477 | |
478 | - if len(images) != 1: |
479 | - raise Exception('No unique images found') |
480 | + if len(images) < 1: |
481 | + raise RuntimeError("No images found under '%s'" % search_d) |
482 | + if len(images) > 1: |
483 | + raise RuntimeError( |
484 | + "Multiple images found in '%s': %s" % (search_d, |
485 | + ' '.join(images))) |
486 | |
487 | image = nocloud_kvm_image.NoCloudKVMImage(self, img_conf, images[0]) |
488 | - if img_conf.get('override_templates', False): |
489 | - image.update_templates(self.config.get('template_overrides', {}), |
490 | - self.config.get('template_files', {})) |
491 | return image |
492 | |
493 | - def create_image(self, properties, config, features, |
494 | - src_img_path, image_desc=None, use_desc=None, |
495 | - user_data=None, meta_data=None): |
496 | - """Create an image |
497 | + def create_instance(self, properties, config, features, |
498 | + src_img_path, image_desc=None, use_desc=None, |
499 | + user_data=None, meta_data=None): |
500 | + """Create an instance |
501 | |
502 | @param src_img_path: image path to launch from |
503 | @param properties: image properties |
504 | @@ -82,7 +83,7 @@ class NoCloudKVMPlatform(base.Platform): |
505 | c_util.subp(['qemu-img', 'create', '-f', 'qcow2', |
506 | '-b', src_img_path, img_path]) |
507 | |
508 | - return nocloud_kvm_instance.NoCloudKVMInstance(self, img_path, |
509 | + return nocloud_kvm_instance.NoCloudKVMInstance(self, name, img_path, |
510 | properties, config, |
511 | features, user_data, |
512 | meta_data) |
513 | diff --git a/tests/cloud_tests/releases.yaml b/tests/cloud_tests/releases.yaml |
514 | index ec7e2d5..e593380 100644 |
515 | --- a/tests/cloud_tests/releases.yaml |
516 | +++ b/tests/cloud_tests/releases.yaml |
517 | @@ -122,6 +122,22 @@ features: |
518 | |
519 | releases: |
520 | # UBUNTU ================================================================= |
521 | + bionic: |
522 | + # EOL: Apr 2023 |
523 | + default: |
524 | + enabled: true |
525 | + release: bionic |
526 | + version: 18.04 |
527 | + family: ubuntu |
528 | + feature_groups: |
529 | + - base |
530 | + - debian_base |
531 | + - ubuntu_specific |
532 | + lxd: |
533 | + sstreams_server: https://cloud-images.ubuntu.com/daily |
534 | + alias: bionic |
535 | + setup_overrides: null |
536 | + override_templates: false |
537 | artful: |
538 | # EOL: Jul 2018 |
539 | default: |
540 | diff --git a/tests/cloud_tests/setup_image.py b/tests/cloud_tests/setup_image.py |
541 | index 6672ffb..179f40d 100644 |
542 | --- a/tests/cloud_tests/setup_image.py |
543 | +++ b/tests/cloud_tests/setup_image.py |
544 | @@ -50,9 +50,9 @@ def install_deb(args, image): |
545 | LOG.debug(msg) |
546 | remote_path = os.path.join('/tmp', os.path.basename(args.deb)) |
547 | image.push_file(args.deb, remote_path) |
548 | - cmd = 'dpkg -i {}; apt-get install --yes -f'.format(remote_path) |
549 | - image.execute(cmd, description=msg) |
550 | - |
551 | + image.execute( |
552 | + ['apt-get', 'install', '--allow-downgrades', '--assume-yes', |
553 | + remote_path], description=msg) |
554 | # check installed deb version matches package |
555 | fmt = ['-W', "--showformat=${Version}"] |
556 | (out, err, exit) = image.execute(['dpkg-deb'] + fmt + [remote_path]) |
557 | diff --git a/tests/cloud_tests/snapshots/nocloudkvm.py b/tests/cloud_tests/snapshots/nocloudkvm.py |
558 | index 0999834..21e908d 100644 |
559 | --- a/tests/cloud_tests/snapshots/nocloudkvm.py |
560 | +++ b/tests/cloud_tests/snapshots/nocloudkvm.py |
561 | @@ -2,6 +2,8 @@ |
562 | |
563 | """Base NoCloud KVM snapshot.""" |
564 | import os |
565 | +import shutil |
566 | +import tempfile |
567 | |
568 | from tests.cloud_tests.snapshots import base |
569 | |
570 | @@ -11,16 +13,19 @@ class NoCloudKVMSnapshot(base.Snapshot): |
571 | |
572 | platform_name = "nocloud-kvm" |
573 | |
574 | - def __init__(self, platform, properties, config, features, |
575 | - instance): |
576 | + def __init__(self, platform, properties, config, features, image_path): |
577 | """Set up snapshot. |
578 | |
579 | @param platform: platform object |
580 | @param properties: image properties |
581 | @param config: image config |
582 | @param features: supported feature flags |
583 | + @param image_path: image file to snapshot. |
584 | """ |
585 | - self.instance = instance |
586 | + self._workd = tempfile.mkdtemp(prefix='NoCloudKVMSnapshot') |
587 | + snapshot = os.path.join(self._workd, 'snapshot') |
588 | + shutil.copyfile(image_path, snapshot) |
589 | + self._image_path = snapshot |
590 | |
591 | super(NoCloudKVMSnapshot, self).__init__( |
592 | platform, properties, config, features) |
593 | @@ -40,9 +45,9 @@ class NoCloudKVMSnapshot(base.Snapshot): |
594 | self.platform.config['public_key']) |
595 | user_data = self.inject_ssh_key(user_data, key_file) |
596 | |
597 | - instance = self.platform.create_image( |
598 | + instance = self.platform.create_instance( |
599 | self.properties, self.config, self.features, |
600 | - self.instance.name, image_desc=str(self), use_desc=use_desc, |
601 | + self._image_path, image_desc=str(self), use_desc=use_desc, |
602 | user_data=user_data, meta_data=meta_data) |
603 | |
604 | if start: |
605 | @@ -68,7 +73,7 @@ class NoCloudKVMSnapshot(base.Snapshot): |
606 | |
607 | def destroy(self): |
608 | """Clean up snapshot data.""" |
609 | - self.instance.destroy() |
610 | + shutil.rmtree(self._workd) |
611 | super(NoCloudKVMSnapshot, self).destroy() |
612 | |
613 | # vi: ts=4 expandtab |
614 | diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py |
615 | index 0a11777..7cb1812 100644 |
616 | --- a/tests/unittests/test_datasource/test_azure.py |
617 | +++ b/tests/unittests/test_datasource/test_azure.py |
618 | @@ -171,7 +171,6 @@ scbus-1 on xpt0 bus 0 |
619 | self.apply_patches([ |
620 | (dsaz, 'list_possible_azure_ds_devs', dsdevs), |
621 | (dsaz, 'invoke_agent', _invoke_agent), |
622 | - (dsaz, 'wait_for_files', _wait_for_files), |
623 | (dsaz, 'pubkeys_from_crt_files', _pubkeys_from_crt_files), |
624 | (dsaz, 'perform_hostname_bounce', mock.MagicMock()), |
625 | (dsaz, 'get_hostname', mock.MagicMock()), |
626 | @@ -179,6 +178,8 @@ scbus-1 on xpt0 bus 0 |
627 | (dsaz, 'get_metadata_from_fabric', self.get_metadata_from_fabric), |
628 | (dsaz.util, 'read_dmi_data', mock.MagicMock( |
629 | side_effect=_dmi_mocks)), |
630 | + (dsaz.util, 'wait_for_files', mock.MagicMock( |
631 | + side_effect=_wait_for_files)), |
632 | ]) |
633 | |
634 | dsrc = dsaz.DataSourceAzure( |
635 | @@ -647,7 +648,7 @@ class TestAzureBounce(TestCase): |
636 | self.patches.enter_context( |
637 | mock.patch.object(dsaz, 'invoke_agent')) |
638 | self.patches.enter_context( |
639 | - mock.patch.object(dsaz, 'wait_for_files')) |
640 | + mock.patch.object(dsaz.util, 'wait_for_files')) |
641 | self.patches.enter_context( |
642 | mock.patch.object(dsaz, 'list_possible_azure_ds_devs', |
643 | mock.MagicMock(return_value=[]))) |
644 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
645 | index bbb63cb..f3fa2a3 100644 |
646 | --- a/tests/unittests/test_net.py |
647 | +++ b/tests/unittests/test_net.py |
648 | @@ -436,6 +436,9 @@ NETWORK_CONFIGS = { |
649 | BOOTPROTO=dhcp |
650 | DEFROUTE=yes |
651 | DEVICE=eth99 |
652 | + DNS1=8.8.8.8 |
653 | + DNS2=8.8.4.4 |
654 | + DOMAIN="barley.maas sach.maas" |
655 | GATEWAY=65.61.151.37 |
656 | HWADDR=c0:d6:9f:2c:e8:80 |
657 | IPADDR=192.168.21.3 |
658 | @@ -836,6 +839,9 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true |
659 | BOOTPROTO=none |
660 | DEFROUTE=yes |
661 | DEVICE=eth0.101 |
662 | + DNS1=192.168.0.10 |
663 | + DNS2=10.23.23.134 |
664 | + DOMAIN="barley.maas sacchromyces.maas brettanomyces.maas" |
665 | GATEWAY=192.168.0.1 |
666 | IPADDR=192.168.0.2 |
667 | IPADDR1=192.168.2.10 |
PASSED: Continuous integration, rev:c491d834410 4610bfd3ea0cb48 ca4403c789a23d /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 574/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 574/rebuild
https:/