Merge ~chad.smith/cloud-init:ubuntu/disco into cloud-init:ubuntu/disco
- Git
- lp:~chad.smith/cloud-init
- ubuntu/disco
- Merge into ubuntu/disco
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | f0141fe08a30d606035109975696bb1bd6c1c54d | ||||
Proposed branch: | ~chad.smith/cloud-init:ubuntu/disco | ||||
Merge into: | cloud-init:ubuntu/disco | ||||
Diff against target: |
557 lines (+279/-81) 7 files modified
cloudinit/net/cmdline.py (+95/-32) cloudinit/sources/DataSourceExoscale.py (+17/-9) cloudinit/sources/DataSourceOracle.py (+20/-16) cloudinit/sources/tests/test_oracle.py (+36/-3) debian/changelog (+9/-0) tests/unittests/test_datasource/test_exoscale.py (+16/-8) tests/unittests/test_net.py (+86/-13) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper | Approve | ||
Review via email:
|
Commit message
new-upstream snapshot to get exoscale fixes before we complete current -proposed SRU
Description of the change

Server Team CI bot (server-team-bot) wrote : | # |

Ryan Harper (raharper) wrote : | # |
This matches my output.

Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:f0141fe08a3
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
FAILED: Ubuntu LTS: Build
Click here to trigger a rebuild:
https:/

Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:f0141fe08a3
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
FAILED: Ubuntu LTS: Integration
Click here to trigger a rebuild:
https:/

Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f0141fe08a3
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/

Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f0141fe08a3
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py |
2 | index 556a10f..55166ea 100755 |
3 | --- a/cloudinit/net/cmdline.py |
4 | +++ b/cloudinit/net/cmdline.py |
5 | @@ -5,20 +5,95 @@ |
6 | # |
7 | # This file is part of cloud-init. See LICENSE file for license information. |
8 | |
9 | +import abc |
10 | import base64 |
11 | import glob |
12 | import gzip |
13 | import io |
14 | import os |
15 | |
16 | -from . import get_devicelist |
17 | -from . import read_sys_net_safe |
18 | +import six |
19 | |
20 | from cloudinit import util |
21 | |
22 | +from . import get_devicelist |
23 | +from . import read_sys_net_safe |
24 | + |
25 | _OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface" |
26 | |
27 | |
28 | +@six.add_metaclass(abc.ABCMeta) |
29 | +class InitramfsNetworkConfigSource(object): |
30 | + """ABC for net config sources that read config written by initramfses""" |
31 | + |
32 | + @abc.abstractmethod |
33 | + def is_applicable(self): |
34 | + # type: () -> bool |
35 | + """Is this initramfs config source applicable to the current system?""" |
36 | + pass |
37 | + |
38 | + @abc.abstractmethod |
39 | + def render_config(self): |
40 | + # type: () -> dict |
41 | + """Render a v1 network config from the initramfs configuration""" |
42 | + pass |
43 | + |
44 | + |
45 | +class KlibcNetworkConfigSource(InitramfsNetworkConfigSource): |
46 | + """InitramfsNetworkConfigSource for klibc initramfs (i.e. Debian/Ubuntu) |
47 | + |
48 | + Has three parameters, but they are intended to make testing simpler, _not_ |
49 | + for use in production code. (This is indicated by the prepended |
50 | + underscores.) |
51 | + """ |
52 | + |
53 | + def __init__(self, _files=None, _mac_addrs=None, _cmdline=None): |
54 | + self._files = _files |
55 | + self._mac_addrs = _mac_addrs |
56 | + self._cmdline = _cmdline |
57 | + |
58 | + # Set defaults here, as they require computation that we don't want to |
59 | + # do at method definition time |
60 | + if self._files is None: |
61 | + self._files = _get_klibc_net_cfg_files() |
62 | + if self._cmdline is None: |
63 | + self._cmdline = util.get_cmdline() |
64 | + if self._mac_addrs is None: |
65 | + self._mac_addrs = {} |
66 | + for k in get_devicelist(): |
67 | + mac_addr = read_sys_net_safe(k, 'address') |
68 | + if mac_addr: |
69 | + self._mac_addrs[k] = mac_addr |
70 | + |
71 | + def is_applicable(self): |
72 | + # type: () -> bool |
73 | + """ |
74 | + Return whether this system has klibc initramfs network config or not |
75 | + |
76 | + Will return True if: |
77 | + (a) klibc files exist in /run, AND |
78 | + (b) either: |
79 | + (i) ip= or ip6= are on the kernel cmdline, OR |
80 | + (ii) an open-iscsi interface file is present in the system |
81 | + """ |
82 | + if self._files: |
83 | + if 'ip=' in self._cmdline or 'ip6=' in self._cmdline: |
84 | + return True |
85 | + if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE): |
86 | + # iBft can configure networking without ip= |
87 | + return True |
88 | + return False |
89 | + |
90 | + def render_config(self): |
91 | + # type: () -> dict |
92 | + return config_from_klibc_net_cfg( |
93 | + files=self._files, mac_addrs=self._mac_addrs, |
94 | + ) |
95 | + |
96 | + |
97 | +_INITRAMFS_CONFIG_SOURCES = [KlibcNetworkConfigSource] |
98 | + |
99 | + |
100 | def _klibc_to_config_entry(content, mac_addrs=None): |
101 | """Convert a klibc written shell content file to a 'config' entry |
102 | When ip= is seen on the kernel command line in debian initramfs |
103 | @@ -137,6 +212,24 @@ def config_from_klibc_net_cfg(files=None, mac_addrs=None): |
104 | return {'config': entries, 'version': 1} |
105 | |
106 | |
107 | +def read_initramfs_config(): |
108 | + """ |
109 | + Return v1 network config for initramfs-configured networking (or None) |
110 | + |
111 | + This will consider each _INITRAMFS_CONFIG_SOURCES entry in turn, and return |
112 | + v1 network configuration for the first one that is applicable. If none are |
113 | + applicable, return None. |
114 | + """ |
115 | + for src_cls in _INITRAMFS_CONFIG_SOURCES: |
116 | + cfg_source = src_cls() |
117 | + |
118 | + if not cfg_source.is_applicable(): |
119 | + continue |
120 | + |
121 | + return cfg_source.render_config() |
122 | + return None |
123 | + |
124 | + |
125 | def _decomp_gzip(blob, strict=True): |
126 | # decompress blob. raise exception if not compressed unless strict=False. |
127 | with io.BytesIO(blob) as iobuf: |
128 | @@ -167,36 +260,6 @@ def _b64dgz(b64str, gzipped="try"): |
129 | return _decomp_gzip(blob, strict=gzipped != "try") |
130 | |
131 | |
132 | -def _is_initramfs_netconfig(files, cmdline): |
133 | - if files: |
134 | - if 'ip=' in cmdline or 'ip6=' in cmdline: |
135 | - return True |
136 | - if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE): |
137 | - # iBft can configure networking without ip= |
138 | - return True |
139 | - return False |
140 | - |
141 | - |
142 | -def read_initramfs_config(files=None, mac_addrs=None, cmdline=None): |
143 | - if cmdline is None: |
144 | - cmdline = util.get_cmdline() |
145 | - |
146 | - if files is None: |
147 | - files = _get_klibc_net_cfg_files() |
148 | - |
149 | - if not _is_initramfs_netconfig(files, cmdline): |
150 | - return None |
151 | - |
152 | - if mac_addrs is None: |
153 | - mac_addrs = {} |
154 | - for k in get_devicelist(): |
155 | - mac_addr = read_sys_net_safe(k, 'address') |
156 | - if mac_addr: |
157 | - mac_addrs[k] = mac_addr |
158 | - |
159 | - return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs) |
160 | - |
161 | - |
162 | def read_kernel_cmdline_config(cmdline=None): |
163 | if cmdline is None: |
164 | cmdline = util.get_cmdline() |
165 | diff --git a/cloudinit/sources/DataSourceExoscale.py b/cloudinit/sources/DataSourceExoscale.py |
166 | index 52e7f6f..fdfb4ed 100644 |
167 | --- a/cloudinit/sources/DataSourceExoscale.py |
168 | +++ b/cloudinit/sources/DataSourceExoscale.py |
169 | @@ -6,6 +6,7 @@ |
170 | from cloudinit import ec2_utils as ec2 |
171 | from cloudinit import log as logging |
172 | from cloudinit import sources |
173 | +from cloudinit import helpers |
174 | from cloudinit import url_helper |
175 | from cloudinit import util |
176 | |
177 | @@ -20,13 +21,6 @@ URL_RETRIES = 6 |
178 | |
179 | EXOSCALE_DMI_NAME = "Exoscale" |
180 | |
181 | -BUILTIN_DS_CONFIG = { |
182 | - # We run the set password config module on every boot in order to enable |
183 | - # resetting the instance's password via the exoscale console (and a |
184 | - # subsequent instance reboot). |
185 | - 'cloud_config_modules': [["set-passwords", "always"]] |
186 | -} |
187 | - |
188 | |
189 | class DataSourceExoscale(sources.DataSource): |
190 | |
191 | @@ -42,8 +36,22 @@ class DataSourceExoscale(sources.DataSource): |
192 | self.ds_cfg.get('password_server_port', PASSWORD_SERVER_PORT)) |
193 | self.url_timeout = self.ds_cfg.get('timeout', URL_TIMEOUT) |
194 | self.url_retries = self.ds_cfg.get('retries', URL_RETRIES) |
195 | - |
196 | - self.extra_config = BUILTIN_DS_CONFIG |
197 | + self.extra_config = {} |
198 | + |
199 | + def activate(self, cfg, is_new_instance): |
200 | + """Adjust set-passwords module to run 'always' during each boot""" |
201 | + # We run the set password config module on every boot in order to |
202 | + # enable resetting the instance's password via the exoscale console |
203 | + # (and a subsequent instance reboot). |
204 | + # Exoscale password server only provides set-passwords user-data if |
205 | + # a user has triggered a password reset. So calling that password |
206 | + # service generally results in no additional cloud-config. |
207 | + # TODO(Create util functions for overriding merged sys_cfg module freq) |
208 | + mod = 'set_passwords' |
209 | + sem_path = self.paths.get_ipath_cur('sem') |
210 | + sem_helper = helpers.FileSemaphores(sem_path) |
211 | + if sem_helper.clear('config_' + mod, None): |
212 | + LOG.debug('Overriding module set-passwords with frequency always') |
213 | |
214 | def wait_for_metadata_service(self): |
215 | """Wait for the metadata service to be reachable.""" |
216 | diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py |
217 | index 6e73f56..1cb0636 100644 |
218 | --- a/cloudinit/sources/DataSourceOracle.py |
219 | +++ b/cloudinit/sources/DataSourceOracle.py |
220 | @@ -51,8 +51,8 @@ def _add_network_config_from_opc_imds(network_config): |
221 | include the secondary VNICs. |
222 | |
223 | :param network_config: |
224 | - A v1 network config dict with the primary NIC already configured. This |
225 | - dict will be mutated. |
226 | + A v1 or v2 network config dict with the primary NIC already configured. |
227 | + This dict will be mutated. |
228 | |
229 | :raises: |
230 | Exceptions are not handled within this function. Likely exceptions are |
231 | @@ -88,20 +88,24 @@ def _add_network_config_from_opc_imds(network_config): |
232 | LOG.debug('Interface with MAC %s not found; skipping', mac_address) |
233 | continue |
234 | name = interfaces_by_mac[mac_address] |
235 | - subnet = { |
236 | - 'type': 'static', |
237 | - 'address': vnic_dict['privateIp'], |
238 | - 'netmask': vnic_dict['subnetCidrBlock'].split('/')[1], |
239 | - 'gateway': vnic_dict['virtualRouterIp'], |
240 | - 'control': 'manual', |
241 | - } |
242 | - network_config['config'].append({ |
243 | - 'name': name, |
244 | - 'type': 'physical', |
245 | - 'mac_address': mac_address, |
246 | - 'mtu': MTU, |
247 | - 'subnets': [subnet], |
248 | - }) |
249 | + |
250 | + if network_config['version'] == 1: |
251 | + subnet = { |
252 | + 'type': 'static', |
253 | + 'address': vnic_dict['privateIp'], |
254 | + } |
255 | + network_config['config'].append({ |
256 | + 'name': name, |
257 | + 'type': 'physical', |
258 | + 'mac_address': mac_address, |
259 | + 'mtu': MTU, |
260 | + 'subnets': [subnet], |
261 | + }) |
262 | + elif network_config['version'] == 2: |
263 | + network_config['ethernets'][name] = { |
264 | + 'addresses': [vnic_dict['privateIp']], |
265 | + 'mtu': MTU, 'dhcp4': False, 'dhcp6': False, |
266 | + 'match': {'macaddress': mac_address}} |
267 | |
268 | |
269 | class DataSourceOracle(sources.DataSource): |
270 | diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py |
271 | index 3ddf7df..2a70bbc 100644 |
272 | --- a/cloudinit/sources/tests/test_oracle.py |
273 | +++ b/cloudinit/sources/tests/test_oracle.py |
274 | @@ -526,6 +526,18 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase): |
275 | 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping', |
276 | self.logs.getvalue()) |
277 | |
278 | + def test_missing_mac_skipped_v2(self): |
279 | + self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE |
280 | + self.m_get_interfaces_by_mac.return_value = {} |
281 | + |
282 | + network_config = {'version': 2, 'ethernets': {'primary': {'nic': {}}}} |
283 | + oracle._add_network_config_from_opc_imds(network_config) |
284 | + |
285 | + self.assertEqual(1, len(network_config['ethernets'])) |
286 | + self.assertIn( |
287 | + 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping', |
288 | + self.logs.getvalue()) |
289 | + |
290 | def test_secondary_nic(self): |
291 | self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE |
292 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' |
293 | @@ -549,8 +561,29 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase): |
294 | subnet_cfg = secondary_nic_cfg['subnets'][0] |
295 | # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE |
296 | self.assertEqual('10.0.0.231', subnet_cfg['address']) |
297 | - self.assertEqual('24', subnet_cfg['netmask']) |
298 | - self.assertEqual('10.0.0.1', subnet_cfg['gateway']) |
299 | - self.assertEqual('manual', subnet_cfg['control']) |
300 | + |
301 | + def test_secondary_nic_v2(self): |
302 | + self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE |
303 | + mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' |
304 | + self.m_get_interfaces_by_mac.return_value = { |
305 | + mac_addr: nic_name, |
306 | + } |
307 | + |
308 | + network_config = {'version': 2, 'ethernets': {'primary': {'nic': {}}}} |
309 | + oracle._add_network_config_from_opc_imds(network_config) |
310 | + |
311 | + # The input is mutated |
312 | + self.assertEqual(2, len(network_config['ethernets'])) |
313 | + |
314 | + secondary_nic_cfg = network_config['ethernets']['ens3'] |
315 | + self.assertFalse(secondary_nic_cfg['dhcp4']) |
316 | + self.assertFalse(secondary_nic_cfg['dhcp6']) |
317 | + self.assertEqual(mac_addr, secondary_nic_cfg['match']['macaddress']) |
318 | + self.assertEqual(9000, secondary_nic_cfg['mtu']) |
319 | + |
320 | + self.assertEqual(1, len(secondary_nic_cfg['addresses'])) |
321 | + # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE |
322 | + self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0]) |
323 | + |
324 | |
325 | # vi: ts=4 expandtab |
326 | diff --git a/debian/changelog b/debian/changelog |
327 | index 0ac84a4..ba9606f 100644 |
328 | --- a/debian/changelog |
329 | +++ b/debian/changelog |
330 | @@ -1,3 +1,12 @@ |
331 | +cloud-init (19.2-24-ge7881d5c-0ubuntu1~19.04.1) disco; urgency=medium |
332 | + |
333 | + * New upstream snapshot. (LP: #1841099) |
334 | + - Oracle: Render secondary vnic IP and MTU values only |
335 | + - exoscale: fix sysconfig cloud_config_modules overrides |
336 | + - net/cmdline: refactor to allow multiple initramfs network config sources |
337 | + |
338 | + -- Chad Smith <chad.smith@canonical.com> Wed, 28 Aug 2019 15:02:21 -0600 |
339 | + |
340 | cloud-init (19.2-21-ge6383719-0ubuntu1~19.04.1) disco; urgency=medium |
341 | |
342 | * debian/cloud-init.templates: enable Exoscale cloud. |
343 | diff --git a/tests/unittests/test_datasource/test_exoscale.py b/tests/unittests/test_datasource/test_exoscale.py |
344 | index 350c330..f006119 100644 |
345 | --- a/tests/unittests/test_datasource/test_exoscale.py |
346 | +++ b/tests/unittests/test_datasource/test_exoscale.py |
347 | @@ -11,8 +11,10 @@ from cloudinit.sources.DataSourceExoscale import ( |
348 | PASSWORD_SERVER_PORT, |
349 | read_metadata) |
350 | from cloudinit.tests.helpers import HttprettyTestCase, mock |
351 | +from cloudinit import util |
352 | |
353 | import httpretty |
354 | +import os |
355 | import requests |
356 | |
357 | |
358 | @@ -63,6 +65,18 @@ class TestDatasourceExoscale(HttprettyTestCase): |
359 | password = get_password() |
360 | self.assertEqual(expected_password, password) |
361 | |
362 | + def test_activate_removes_set_passwords_semaphore(self): |
363 | + """Allow set_passwords to run every boot by removing the semaphore.""" |
364 | + path = helpers.Paths({'cloud_dir': self.tmp}) |
365 | + sem_dir = self.tmp_path('instance/sem', dir=self.tmp) |
366 | + util.ensure_dir(sem_dir) |
367 | + sem_file = os.path.join(sem_dir, 'config_set_passwords') |
368 | + with open(sem_file, 'w') as stream: |
369 | + stream.write('') |
370 | + ds = DataSourceExoscale({}, None, path) |
371 | + ds.activate(None, None) |
372 | + self.assertFalse(os.path.exists(sem_file)) |
373 | + |
374 | def test_get_data(self): |
375 | """The datasource conforms to expected behavior when supplied |
376 | full test data.""" |
377 | @@ -95,8 +109,6 @@ class TestDatasourceExoscale(HttprettyTestCase): |
378 | self.assertEqual(ds.get_config_obj(), |
379 | {'ssh_pwauth': True, |
380 | 'password': expected_password, |
381 | - 'cloud_config_modules': [ |
382 | - ["set-passwords", "always"]], |
383 | 'chpasswd': { |
384 | 'expire': False, |
385 | }}) |
386 | @@ -130,9 +142,7 @@ class TestDatasourceExoscale(HttprettyTestCase): |
387 | self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config") |
388 | self.assertEqual(ds.metadata, {"instance-id": expected_id, |
389 | "local-hostname": expected_hostname}) |
390 | - self.assertEqual(ds.get_config_obj(), |
391 | - {'cloud_config_modules': [ |
392 | - ["set-passwords", "always"]]}) |
393 | + self.assertEqual(ds.get_config_obj(), {}) |
394 | |
395 | def test_get_data_no_password(self): |
396 | """The datasource conforms to expected behavior when no password is |
397 | @@ -163,9 +173,7 @@ class TestDatasourceExoscale(HttprettyTestCase): |
398 | self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config") |
399 | self.assertEqual(ds.metadata, {"instance-id": expected_id, |
400 | "local-hostname": expected_hostname}) |
401 | - self.assertEqual(ds.get_config_obj(), |
402 | - {'cloud_config_modules': [ |
403 | - ["set-passwords", "always"]]}) |
404 | + self.assertEqual(ds.get_config_obj(), {}) |
405 | |
406 | @mock.patch('cloudinit.sources.DataSourceExoscale.get_password') |
407 | def test_read_metadata_when_password_server_unreachable(self, m_password): |
408 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
409 | index 4f7e420..e578992 100644 |
410 | --- a/tests/unittests/test_net.py |
411 | +++ b/tests/unittests/test_net.py |
412 | @@ -3591,7 +3591,7 @@ class TestCmdlineConfigParsing(CiTestCase): |
413 | self.assertEqual(found, self.simple_cfg) |
414 | |
415 | |
416 | -class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): |
417 | +class TestCmdlineKlibcNetworkConfigSource(FilesystemMockingTestCase): |
418 | macs = { |
419 | 'eth0': '14:02:ec:42:48:00', |
420 | 'eno1': '14:02:ec:42:48:01', |
421 | @@ -3607,8 +3607,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): |
422 | populate_dir(root, content) |
423 | self.reRoot(root) |
424 | |
425 | - found = cmdline.read_initramfs_config( |
426 | - cmdline='foo root=/root/bar', mac_addrs=self.macs) |
427 | + src = cmdline.KlibcNetworkConfigSource( |
428 | + _cmdline='foo root=/root/bar', _mac_addrs=self.macs, |
429 | + ) |
430 | + self.assertTrue(src.is_applicable()) |
431 | + found = src.render_config() |
432 | self.assertEqual(found['version'], 1) |
433 | self.assertEqual(found['config'], [exp1]) |
434 | |
435 | @@ -3621,8 +3624,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): |
436 | populate_dir(root, content) |
437 | self.reRoot(root) |
438 | |
439 | - found = cmdline.read_initramfs_config( |
440 | - cmdline='foo ip=dhcp', mac_addrs=self.macs) |
441 | + src = cmdline.KlibcNetworkConfigSource( |
442 | + _cmdline='foo ip=dhcp', _mac_addrs=self.macs, |
443 | + ) |
444 | + self.assertTrue(src.is_applicable()) |
445 | + found = src.render_config() |
446 | self.assertEqual(found['version'], 1) |
447 | self.assertEqual(found['config'], [exp1]) |
448 | |
449 | @@ -3632,9 +3638,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): |
450 | populate_dir(root, content) |
451 | self.reRoot(root) |
452 | |
453 | - found = cmdline.read_initramfs_config( |
454 | - cmdline='foo ip6=dhcp root=/dev/sda', |
455 | - mac_addrs=self.macs) |
456 | + src = cmdline.KlibcNetworkConfigSource( |
457 | + _cmdline='foo ip6=dhcp root=/dev/sda', _mac_addrs=self.macs, |
458 | + ) |
459 | + self.assertTrue(src.is_applicable()) |
460 | + found = src.render_config() |
461 | self.assertEqual( |
462 | found, |
463 | {'version': 1, 'config': [ |
464 | @@ -3648,9 +3656,10 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): |
465 | # if there is no ip= or ip6= on cmdline, return value should be None |
466 | content = {'net6-eno1.conf': DHCP6_CONTENT_1} |
467 | files = sorted(populate_dir(self.tmp_dir(), content)) |
468 | - found = cmdline.read_initramfs_config( |
469 | - files=files, cmdline='foo root=/dev/sda', mac_addrs=self.macs) |
470 | - self.assertIsNone(found) |
471 | + src = cmdline.KlibcNetworkConfigSource( |
472 | + _files=files, _cmdline='foo root=/dev/sda', _mac_addrs=self.macs, |
473 | + ) |
474 | + self.assertFalse(src.is_applicable()) |
475 | |
476 | def test_with_both_ip_ip6(self): |
477 | content = { |
478 | @@ -3667,13 +3676,77 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): |
479 | populate_dir(root, content) |
480 | self.reRoot(root) |
481 | |
482 | - found = cmdline.read_initramfs_config( |
483 | - cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs) |
484 | + src = cmdline.KlibcNetworkConfigSource( |
485 | + _cmdline='foo ip=dhcp ip6=dhcp', _mac_addrs=self.macs, |
486 | + ) |
487 | |
488 | + self.assertTrue(src.is_applicable()) |
489 | + found = src.render_config() |
490 | self.assertEqual(found['version'], 1) |
491 | self.assertEqual(found['config'], expected) |
492 | |
493 | |
494 | +class TestReadInitramfsConfig(CiTestCase): |
495 | + |
496 | + def _config_source_cls_mock(self, is_applicable, render_config=None): |
497 | + return lambda: mock.Mock( |
498 | + is_applicable=lambda: is_applicable, |
499 | + render_config=lambda: render_config, |
500 | + ) |
501 | + |
502 | + def test_no_sources(self): |
503 | + with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', []): |
504 | + self.assertIsNone(cmdline.read_initramfs_config()) |
505 | + |
506 | + def test_no_applicable_sources(self): |
507 | + sources = [ |
508 | + self._config_source_cls_mock(is_applicable=False), |
509 | + self._config_source_cls_mock(is_applicable=False), |
510 | + self._config_source_cls_mock(is_applicable=False), |
511 | + ] |
512 | + with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', |
513 | + sources): |
514 | + self.assertIsNone(cmdline.read_initramfs_config()) |
515 | + |
516 | + def test_one_applicable_source(self): |
517 | + expected_config = object() |
518 | + sources = [ |
519 | + self._config_source_cls_mock( |
520 | + is_applicable=True, render_config=expected_config, |
521 | + ), |
522 | + ] |
523 | + with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', |
524 | + sources): |
525 | + self.assertEqual(expected_config, cmdline.read_initramfs_config()) |
526 | + |
527 | + def test_one_applicable_source_after_inapplicable_sources(self): |
528 | + expected_config = object() |
529 | + sources = [ |
530 | + self._config_source_cls_mock(is_applicable=False), |
531 | + self._config_source_cls_mock(is_applicable=False), |
532 | + self._config_source_cls_mock( |
533 | + is_applicable=True, render_config=expected_config, |
534 | + ), |
535 | + ] |
536 | + with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', |
537 | + sources): |
538 | + self.assertEqual(expected_config, cmdline.read_initramfs_config()) |
539 | + |
540 | + def test_first_applicable_source_is_used(self): |
541 | + first_config, second_config = object(), object() |
542 | + sources = [ |
543 | + self._config_source_cls_mock( |
544 | + is_applicable=True, render_config=first_config, |
545 | + ), |
546 | + self._config_source_cls_mock( |
547 | + is_applicable=True, render_config=second_config, |
548 | + ), |
549 | + ] |
550 | + with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', |
551 | + sources): |
552 | + self.assertEqual(first_config, cmdline.read_initramfs_config()) |
553 | + |
554 | + |
555 | class TestNetplanRoundTrip(CiTestCase): |
556 | def _render_and_read(self, network_config=None, state=None, |
557 | netplan_path=None, target=None): |
FAILED: Continuous integration, rev:f0141fe08a3 0d6060351099756 96bb1bd6c1c54d /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1088/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
FAILED: Ubuntu LTS: Build
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1088//rebuild
https:/