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