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 | 5 | # | 5 | # |
7 | 6 | # This file is part of cloud-init. See LICENSE file for license information. | 6 | # This file is part of cloud-init. See LICENSE file for license information. |
8 | 7 | 7 | ||
9 | 8 | import abc | ||
10 | 8 | import base64 | 9 | import base64 |
11 | 9 | import glob | 10 | import glob |
12 | 10 | import gzip | 11 | import gzip |
13 | 11 | import io | 12 | import io |
14 | 12 | import os | 13 | import os |
15 | 13 | 14 | ||
18 | 14 | from . import get_devicelist | 15 | import six |
17 | 15 | from . import read_sys_net_safe | ||
19 | 16 | 16 | ||
20 | 17 | from cloudinit import util | 17 | from cloudinit import util |
21 | 18 | 18 | ||
22 | 19 | from . import get_devicelist | ||
23 | 20 | from . import read_sys_net_safe | ||
24 | 21 | |||
25 | 19 | _OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface" | 22 | _OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface" |
26 | 20 | 23 | ||
27 | 21 | 24 | ||
28 | 25 | @six.add_metaclass(abc.ABCMeta) | ||
29 | 26 | class InitramfsNetworkConfigSource(object): | ||
30 | 27 | """ABC for net config sources that read config written by initramfses""" | ||
31 | 28 | |||
32 | 29 | @abc.abstractmethod | ||
33 | 30 | def is_applicable(self): | ||
34 | 31 | # type: () -> bool | ||
35 | 32 | """Is this initramfs config source applicable to the current system?""" | ||
36 | 33 | pass | ||
37 | 34 | |||
38 | 35 | @abc.abstractmethod | ||
39 | 36 | def render_config(self): | ||
40 | 37 | # type: () -> dict | ||
41 | 38 | """Render a v1 network config from the initramfs configuration""" | ||
42 | 39 | pass | ||
43 | 40 | |||
44 | 41 | |||
45 | 42 | class KlibcNetworkConfigSource(InitramfsNetworkConfigSource): | ||
46 | 43 | """InitramfsNetworkConfigSource for klibc initramfs (i.e. Debian/Ubuntu) | ||
47 | 44 | |||
48 | 45 | Has three parameters, but they are intended to make testing simpler, _not_ | ||
49 | 46 | for use in production code. (This is indicated by the prepended | ||
50 | 47 | underscores.) | ||
51 | 48 | """ | ||
52 | 49 | |||
53 | 50 | def __init__(self, _files=None, _mac_addrs=None, _cmdline=None): | ||
54 | 51 | self._files = _files | ||
55 | 52 | self._mac_addrs = _mac_addrs | ||
56 | 53 | self._cmdline = _cmdline | ||
57 | 54 | |||
58 | 55 | # Set defaults here, as they require computation that we don't want to | ||
59 | 56 | # do at method definition time | ||
60 | 57 | if self._files is None: | ||
61 | 58 | self._files = _get_klibc_net_cfg_files() | ||
62 | 59 | if self._cmdline is None: | ||
63 | 60 | self._cmdline = util.get_cmdline() | ||
64 | 61 | if self._mac_addrs is None: | ||
65 | 62 | self._mac_addrs = {} | ||
66 | 63 | for k in get_devicelist(): | ||
67 | 64 | mac_addr = read_sys_net_safe(k, 'address') | ||
68 | 65 | if mac_addr: | ||
69 | 66 | self._mac_addrs[k] = mac_addr | ||
70 | 67 | |||
71 | 68 | def is_applicable(self): | ||
72 | 69 | # type: () -> bool | ||
73 | 70 | """ | ||
74 | 71 | Return whether this system has klibc initramfs network config or not | ||
75 | 72 | |||
76 | 73 | Will return True if: | ||
77 | 74 | (a) klibc files exist in /run, AND | ||
78 | 75 | (b) either: | ||
79 | 76 | (i) ip= or ip6= are on the kernel cmdline, OR | ||
80 | 77 | (ii) an open-iscsi interface file is present in the system | ||
81 | 78 | """ | ||
82 | 79 | if self._files: | ||
83 | 80 | if 'ip=' in self._cmdline or 'ip6=' in self._cmdline: | ||
84 | 81 | return True | ||
85 | 82 | if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE): | ||
86 | 83 | # iBft can configure networking without ip= | ||
87 | 84 | return True | ||
88 | 85 | return False | ||
89 | 86 | |||
90 | 87 | def render_config(self): | ||
91 | 88 | # type: () -> dict | ||
92 | 89 | return config_from_klibc_net_cfg( | ||
93 | 90 | files=self._files, mac_addrs=self._mac_addrs, | ||
94 | 91 | ) | ||
95 | 92 | |||
96 | 93 | |||
97 | 94 | _INITRAMFS_CONFIG_SOURCES = [KlibcNetworkConfigSource] | ||
98 | 95 | |||
99 | 96 | |||
100 | 22 | def _klibc_to_config_entry(content, mac_addrs=None): | 97 | def _klibc_to_config_entry(content, mac_addrs=None): |
101 | 23 | """Convert a klibc written shell content file to a 'config' entry | 98 | """Convert a klibc written shell content file to a 'config' entry |
102 | 24 | When ip= is seen on the kernel command line in debian initramfs | 99 | 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 | 137 | return {'config': entries, 'version': 1} | 212 | return {'config': entries, 'version': 1} |
105 | 138 | 213 | ||
106 | 139 | 214 | ||
107 | 215 | def read_initramfs_config(): | ||
108 | 216 | """ | ||
109 | 217 | Return v1 network config for initramfs-configured networking (or None) | ||
110 | 218 | |||
111 | 219 | This will consider each _INITRAMFS_CONFIG_SOURCES entry in turn, and return | ||
112 | 220 | v1 network configuration for the first one that is applicable. If none are | ||
113 | 221 | applicable, return None. | ||
114 | 222 | """ | ||
115 | 223 | for src_cls in _INITRAMFS_CONFIG_SOURCES: | ||
116 | 224 | cfg_source = src_cls() | ||
117 | 225 | |||
118 | 226 | if not cfg_source.is_applicable(): | ||
119 | 227 | continue | ||
120 | 228 | |||
121 | 229 | return cfg_source.render_config() | ||
122 | 230 | return None | ||
123 | 231 | |||
124 | 232 | |||
125 | 140 | def _decomp_gzip(blob, strict=True): | 233 | def _decomp_gzip(blob, strict=True): |
126 | 141 | # decompress blob. raise exception if not compressed unless strict=False. | 234 | # decompress blob. raise exception if not compressed unless strict=False. |
127 | 142 | with io.BytesIO(blob) as iobuf: | 235 | with io.BytesIO(blob) as iobuf: |
128 | @@ -167,36 +260,6 @@ def _b64dgz(b64str, gzipped="try"): | |||
129 | 167 | return _decomp_gzip(blob, strict=gzipped != "try") | 260 | return _decomp_gzip(blob, strict=gzipped != "try") |
130 | 168 | 261 | ||
131 | 169 | 262 | ||
132 | 170 | def _is_initramfs_netconfig(files, cmdline): | ||
133 | 171 | if files: | ||
134 | 172 | if 'ip=' in cmdline or 'ip6=' in cmdline: | ||
135 | 173 | return True | ||
136 | 174 | if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE): | ||
137 | 175 | # iBft can configure networking without ip= | ||
138 | 176 | return True | ||
139 | 177 | return False | ||
140 | 178 | |||
141 | 179 | |||
142 | 180 | def read_initramfs_config(files=None, mac_addrs=None, cmdline=None): | ||
143 | 181 | if cmdline is None: | ||
144 | 182 | cmdline = util.get_cmdline() | ||
145 | 183 | |||
146 | 184 | if files is None: | ||
147 | 185 | files = _get_klibc_net_cfg_files() | ||
148 | 186 | |||
149 | 187 | if not _is_initramfs_netconfig(files, cmdline): | ||
150 | 188 | return None | ||
151 | 189 | |||
152 | 190 | if mac_addrs is None: | ||
153 | 191 | mac_addrs = {} | ||
154 | 192 | for k in get_devicelist(): | ||
155 | 193 | mac_addr = read_sys_net_safe(k, 'address') | ||
156 | 194 | if mac_addr: | ||
157 | 195 | mac_addrs[k] = mac_addr | ||
158 | 196 | |||
159 | 197 | return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs) | ||
160 | 198 | |||
161 | 199 | |||
162 | 200 | def read_kernel_cmdline_config(cmdline=None): | 263 | def read_kernel_cmdline_config(cmdline=None): |
163 | 201 | if cmdline is None: | 264 | if cmdline is None: |
164 | 202 | cmdline = util.get_cmdline() | 265 | 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 | 6 | from cloudinit import ec2_utils as ec2 | 6 | from cloudinit import ec2_utils as ec2 |
171 | 7 | from cloudinit import log as logging | 7 | from cloudinit import log as logging |
172 | 8 | from cloudinit import sources | 8 | from cloudinit import sources |
173 | 9 | from cloudinit import helpers | ||
174 | 9 | from cloudinit import url_helper | 10 | from cloudinit import url_helper |
175 | 10 | from cloudinit import util | 11 | from cloudinit import util |
176 | 11 | 12 | ||
177 | @@ -20,13 +21,6 @@ URL_RETRIES = 6 | |||
178 | 20 | 21 | ||
179 | 21 | EXOSCALE_DMI_NAME = "Exoscale" | 22 | EXOSCALE_DMI_NAME = "Exoscale" |
180 | 22 | 23 | ||
181 | 23 | BUILTIN_DS_CONFIG = { | ||
182 | 24 | # We run the set password config module on every boot in order to enable | ||
183 | 25 | # resetting the instance's password via the exoscale console (and a | ||
184 | 26 | # subsequent instance reboot). | ||
185 | 27 | 'cloud_config_modules': [["set-passwords", "always"]] | ||
186 | 28 | } | ||
187 | 29 | |||
188 | 30 | 24 | ||
189 | 31 | class DataSourceExoscale(sources.DataSource): | 25 | class DataSourceExoscale(sources.DataSource): |
190 | 32 | 26 | ||
191 | @@ -42,8 +36,22 @@ class DataSourceExoscale(sources.DataSource): | |||
192 | 42 | self.ds_cfg.get('password_server_port', PASSWORD_SERVER_PORT)) | 36 | self.ds_cfg.get('password_server_port', PASSWORD_SERVER_PORT)) |
193 | 43 | self.url_timeout = self.ds_cfg.get('timeout', URL_TIMEOUT) | 37 | self.url_timeout = self.ds_cfg.get('timeout', URL_TIMEOUT) |
194 | 44 | self.url_retries = self.ds_cfg.get('retries', URL_RETRIES) | 38 | self.url_retries = self.ds_cfg.get('retries', URL_RETRIES) |
197 | 45 | 39 | self.extra_config = {} | |
198 | 46 | self.extra_config = BUILTIN_DS_CONFIG | 40 | |
199 | 41 | def activate(self, cfg, is_new_instance): | ||
200 | 42 | """Adjust set-passwords module to run 'always' during each boot""" | ||
201 | 43 | # We run the set password config module on every boot in order to | ||
202 | 44 | # enable resetting the instance's password via the exoscale console | ||
203 | 45 | # (and a subsequent instance reboot). | ||
204 | 46 | # Exoscale password server only provides set-passwords user-data if | ||
205 | 47 | # a user has triggered a password reset. So calling that password | ||
206 | 48 | # service generally results in no additional cloud-config. | ||
207 | 49 | # TODO(Create util functions for overriding merged sys_cfg module freq) | ||
208 | 50 | mod = 'set_passwords' | ||
209 | 51 | sem_path = self.paths.get_ipath_cur('sem') | ||
210 | 52 | sem_helper = helpers.FileSemaphores(sem_path) | ||
211 | 53 | if sem_helper.clear('config_' + mod, None): | ||
212 | 54 | LOG.debug('Overriding module set-passwords with frequency always') | ||
213 | 47 | 55 | ||
214 | 48 | def wait_for_metadata_service(self): | 56 | def wait_for_metadata_service(self): |
215 | 49 | """Wait for the metadata service to be reachable.""" | 57 | """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 | 51 | include the secondary VNICs. | 51 | include the secondary VNICs. |
222 | 52 | 52 | ||
223 | 53 | :param network_config: | 53 | :param network_config: |
226 | 54 | A v1 network config dict with the primary NIC already configured. This | 54 | A v1 or v2 network config dict with the primary NIC already configured. |
227 | 55 | dict will be mutated. | 55 | This dict will be mutated. |
228 | 56 | 56 | ||
229 | 57 | :raises: | 57 | :raises: |
230 | 58 | Exceptions are not handled within this function. Likely exceptions are | 58 | 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 | 88 | LOG.debug('Interface with MAC %s not found; skipping', mac_address) | 88 | LOG.debug('Interface with MAC %s not found; skipping', mac_address) |
233 | 89 | continue | 89 | continue |
234 | 90 | name = interfaces_by_mac[mac_address] | 90 | name = interfaces_by_mac[mac_address] |
249 | 91 | subnet = { | 91 | |
250 | 92 | 'type': 'static', | 92 | if network_config['version'] == 1: |
251 | 93 | 'address': vnic_dict['privateIp'], | 93 | subnet = { |
252 | 94 | 'netmask': vnic_dict['subnetCidrBlock'].split('/')[1], | 94 | 'type': 'static', |
253 | 95 | 'gateway': vnic_dict['virtualRouterIp'], | 95 | 'address': vnic_dict['privateIp'], |
254 | 96 | 'control': 'manual', | 96 | } |
255 | 97 | } | 97 | network_config['config'].append({ |
256 | 98 | network_config['config'].append({ | 98 | 'name': name, |
257 | 99 | 'name': name, | 99 | 'type': 'physical', |
258 | 100 | 'type': 'physical', | 100 | 'mac_address': mac_address, |
259 | 101 | 'mac_address': mac_address, | 101 | 'mtu': MTU, |
260 | 102 | 'mtu': MTU, | 102 | 'subnets': [subnet], |
261 | 103 | 'subnets': [subnet], | 103 | }) |
262 | 104 | }) | 104 | elif network_config['version'] == 2: |
263 | 105 | network_config['ethernets'][name] = { | ||
264 | 106 | 'addresses': [vnic_dict['privateIp']], | ||
265 | 107 | 'mtu': MTU, 'dhcp4': False, 'dhcp6': False, | ||
266 | 108 | 'match': {'macaddress': mac_address}} | ||
267 | 105 | 109 | ||
268 | 106 | 110 | ||
269 | 107 | class DataSourceOracle(sources.DataSource): | 111 | 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 | 526 | 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping', | 526 | 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping', |
276 | 527 | self.logs.getvalue()) | 527 | self.logs.getvalue()) |
277 | 528 | 528 | ||
278 | 529 | def test_missing_mac_skipped_v2(self): | ||
279 | 530 | self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE | ||
280 | 531 | self.m_get_interfaces_by_mac.return_value = {} | ||
281 | 532 | |||
282 | 533 | network_config = {'version': 2, 'ethernets': {'primary': {'nic': {}}}} | ||
283 | 534 | oracle._add_network_config_from_opc_imds(network_config) | ||
284 | 535 | |||
285 | 536 | self.assertEqual(1, len(network_config['ethernets'])) | ||
286 | 537 | self.assertIn( | ||
287 | 538 | 'Interface with MAC 00:00:17:02:2b:b1 not found; skipping', | ||
288 | 539 | self.logs.getvalue()) | ||
289 | 540 | |||
290 | 529 | def test_secondary_nic(self): | 541 | def test_secondary_nic(self): |
291 | 530 | self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE | 542 | self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE |
292 | 531 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' | 543 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' |
293 | @@ -549,8 +561,29 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase): | |||
294 | 549 | subnet_cfg = secondary_nic_cfg['subnets'][0] | 561 | subnet_cfg = secondary_nic_cfg['subnets'][0] |
295 | 550 | # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE | 562 | # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE |
296 | 551 | self.assertEqual('10.0.0.231', subnet_cfg['address']) | 563 | self.assertEqual('10.0.0.231', subnet_cfg['address']) |
300 | 552 | self.assertEqual('24', subnet_cfg['netmask']) | 564 | |
301 | 553 | self.assertEqual('10.0.0.1', subnet_cfg['gateway']) | 565 | def test_secondary_nic_v2(self): |
302 | 554 | self.assertEqual('manual', subnet_cfg['control']) | 566 | self.m_readurl.return_value = OPC_VM_SECONDARY_VNIC_RESPONSE |
303 | 567 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' | ||
304 | 568 | self.m_get_interfaces_by_mac.return_value = { | ||
305 | 569 | mac_addr: nic_name, | ||
306 | 570 | } | ||
307 | 571 | |||
308 | 572 | network_config = {'version': 2, 'ethernets': {'primary': {'nic': {}}}} | ||
309 | 573 | oracle._add_network_config_from_opc_imds(network_config) | ||
310 | 574 | |||
311 | 575 | # The input is mutated | ||
312 | 576 | self.assertEqual(2, len(network_config['ethernets'])) | ||
313 | 577 | |||
314 | 578 | secondary_nic_cfg = network_config['ethernets']['ens3'] | ||
315 | 579 | self.assertFalse(secondary_nic_cfg['dhcp4']) | ||
316 | 580 | self.assertFalse(secondary_nic_cfg['dhcp6']) | ||
317 | 581 | self.assertEqual(mac_addr, secondary_nic_cfg['match']['macaddress']) | ||
318 | 582 | self.assertEqual(9000, secondary_nic_cfg['mtu']) | ||
319 | 583 | |||
320 | 584 | self.assertEqual(1, len(secondary_nic_cfg['addresses'])) | ||
321 | 585 | # These values are hard-coded in OPC_VM_SECONDARY_VNIC_RESPONSE | ||
322 | 586 | self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0]) | ||
323 | 587 | |||
324 | 555 | 588 | ||
325 | 556 | # vi: ts=4 expandtab | 589 | # 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 | 1 | cloud-init (19.2-24-ge7881d5c-0ubuntu1~19.04.1) disco; urgency=medium | ||
332 | 2 | |||
333 | 3 | * New upstream snapshot. (LP: #1841099) | ||
334 | 4 | - Oracle: Render secondary vnic IP and MTU values only | ||
335 | 5 | - exoscale: fix sysconfig cloud_config_modules overrides | ||
336 | 6 | - net/cmdline: refactor to allow multiple initramfs network config sources | ||
337 | 7 | |||
338 | 8 | -- Chad Smith <chad.smith@canonical.com> Wed, 28 Aug 2019 15:02:21 -0600 | ||
339 | 9 | |||
340 | 1 | cloud-init (19.2-21-ge6383719-0ubuntu1~19.04.1) disco; urgency=medium | 10 | cloud-init (19.2-21-ge6383719-0ubuntu1~19.04.1) disco; urgency=medium |
341 | 2 | 11 | ||
342 | 3 | * debian/cloud-init.templates: enable Exoscale cloud. | 12 | * 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 | 11 | PASSWORD_SERVER_PORT, | 11 | PASSWORD_SERVER_PORT, |
349 | 12 | read_metadata) | 12 | read_metadata) |
350 | 13 | from cloudinit.tests.helpers import HttprettyTestCase, mock | 13 | from cloudinit.tests.helpers import HttprettyTestCase, mock |
351 | 14 | from cloudinit import util | ||
352 | 14 | 15 | ||
353 | 15 | import httpretty | 16 | import httpretty |
354 | 17 | import os | ||
355 | 16 | import requests | 18 | import requests |
356 | 17 | 19 | ||
357 | 18 | 20 | ||
358 | @@ -63,6 +65,18 @@ class TestDatasourceExoscale(HttprettyTestCase): | |||
359 | 63 | password = get_password() | 65 | password = get_password() |
360 | 64 | self.assertEqual(expected_password, password) | 66 | self.assertEqual(expected_password, password) |
361 | 65 | 67 | ||
362 | 68 | def test_activate_removes_set_passwords_semaphore(self): | ||
363 | 69 | """Allow set_passwords to run every boot by removing the semaphore.""" | ||
364 | 70 | path = helpers.Paths({'cloud_dir': self.tmp}) | ||
365 | 71 | sem_dir = self.tmp_path('instance/sem', dir=self.tmp) | ||
366 | 72 | util.ensure_dir(sem_dir) | ||
367 | 73 | sem_file = os.path.join(sem_dir, 'config_set_passwords') | ||
368 | 74 | with open(sem_file, 'w') as stream: | ||
369 | 75 | stream.write('') | ||
370 | 76 | ds = DataSourceExoscale({}, None, path) | ||
371 | 77 | ds.activate(None, None) | ||
372 | 78 | self.assertFalse(os.path.exists(sem_file)) | ||
373 | 79 | |||
374 | 66 | def test_get_data(self): | 80 | def test_get_data(self): |
375 | 67 | """The datasource conforms to expected behavior when supplied | 81 | """The datasource conforms to expected behavior when supplied |
376 | 68 | full test data.""" | 82 | full test data.""" |
377 | @@ -95,8 +109,6 @@ class TestDatasourceExoscale(HttprettyTestCase): | |||
378 | 95 | self.assertEqual(ds.get_config_obj(), | 109 | self.assertEqual(ds.get_config_obj(), |
379 | 96 | {'ssh_pwauth': True, | 110 | {'ssh_pwauth': True, |
380 | 97 | 'password': expected_password, | 111 | 'password': expected_password, |
381 | 98 | 'cloud_config_modules': [ | ||
382 | 99 | ["set-passwords", "always"]], | ||
383 | 100 | 'chpasswd': { | 112 | 'chpasswd': { |
384 | 101 | 'expire': False, | 113 | 'expire': False, |
385 | 102 | }}) | 114 | }}) |
386 | @@ -130,9 +142,7 @@ class TestDatasourceExoscale(HttprettyTestCase): | |||
387 | 130 | self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config") | 142 | self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config") |
388 | 131 | self.assertEqual(ds.metadata, {"instance-id": expected_id, | 143 | self.assertEqual(ds.metadata, {"instance-id": expected_id, |
389 | 132 | "local-hostname": expected_hostname}) | 144 | "local-hostname": expected_hostname}) |
393 | 133 | self.assertEqual(ds.get_config_obj(), | 145 | self.assertEqual(ds.get_config_obj(), {}) |
391 | 134 | {'cloud_config_modules': [ | ||
392 | 135 | ["set-passwords", "always"]]}) | ||
394 | 136 | 146 | ||
395 | 137 | def test_get_data_no_password(self): | 147 | def test_get_data_no_password(self): |
396 | 138 | """The datasource conforms to expected behavior when no password is | 148 | """The datasource conforms to expected behavior when no password is |
397 | @@ -163,9 +173,7 @@ class TestDatasourceExoscale(HttprettyTestCase): | |||
398 | 163 | self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config") | 173 | self.assertEqual(ds.userdata_raw.decode("utf-8"), "#cloud-config") |
399 | 164 | self.assertEqual(ds.metadata, {"instance-id": expected_id, | 174 | self.assertEqual(ds.metadata, {"instance-id": expected_id, |
400 | 165 | "local-hostname": expected_hostname}) | 175 | "local-hostname": expected_hostname}) |
404 | 166 | self.assertEqual(ds.get_config_obj(), | 176 | self.assertEqual(ds.get_config_obj(), {}) |
402 | 167 | {'cloud_config_modules': [ | ||
403 | 168 | ["set-passwords", "always"]]}) | ||
405 | 169 | 177 | ||
406 | 170 | @mock.patch('cloudinit.sources.DataSourceExoscale.get_password') | 178 | @mock.patch('cloudinit.sources.DataSourceExoscale.get_password') |
407 | 171 | def test_read_metadata_when_password_server_unreachable(self, m_password): | 179 | 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 | 3591 | self.assertEqual(found, self.simple_cfg) | 3591 | self.assertEqual(found, self.simple_cfg) |
414 | 3592 | 3592 | ||
415 | 3593 | 3593 | ||
417 | 3594 | class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): | 3594 | class TestCmdlineKlibcNetworkConfigSource(FilesystemMockingTestCase): |
418 | 3595 | macs = { | 3595 | macs = { |
419 | 3596 | 'eth0': '14:02:ec:42:48:00', | 3596 | 'eth0': '14:02:ec:42:48:00', |
420 | 3597 | 'eno1': '14:02:ec:42:48:01', | 3597 | 'eno1': '14:02:ec:42:48:01', |
421 | @@ -3607,8 +3607,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): | |||
422 | 3607 | populate_dir(root, content) | 3607 | populate_dir(root, content) |
423 | 3608 | self.reRoot(root) | 3608 | self.reRoot(root) |
424 | 3609 | 3609 | ||
427 | 3610 | found = cmdline.read_initramfs_config( | 3610 | src = cmdline.KlibcNetworkConfigSource( |
428 | 3611 | cmdline='foo root=/root/bar', mac_addrs=self.macs) | 3611 | _cmdline='foo root=/root/bar', _mac_addrs=self.macs, |
429 | 3612 | ) | ||
430 | 3613 | self.assertTrue(src.is_applicable()) | ||
431 | 3614 | found = src.render_config() | ||
432 | 3612 | self.assertEqual(found['version'], 1) | 3615 | self.assertEqual(found['version'], 1) |
433 | 3613 | self.assertEqual(found['config'], [exp1]) | 3616 | self.assertEqual(found['config'], [exp1]) |
434 | 3614 | 3617 | ||
435 | @@ -3621,8 +3624,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): | |||
436 | 3621 | populate_dir(root, content) | 3624 | populate_dir(root, content) |
437 | 3622 | self.reRoot(root) | 3625 | self.reRoot(root) |
438 | 3623 | 3626 | ||
441 | 3624 | found = cmdline.read_initramfs_config( | 3627 | src = cmdline.KlibcNetworkConfigSource( |
442 | 3625 | cmdline='foo ip=dhcp', mac_addrs=self.macs) | 3628 | _cmdline='foo ip=dhcp', _mac_addrs=self.macs, |
443 | 3629 | ) | ||
444 | 3630 | self.assertTrue(src.is_applicable()) | ||
445 | 3631 | found = src.render_config() | ||
446 | 3626 | self.assertEqual(found['version'], 1) | 3632 | self.assertEqual(found['version'], 1) |
447 | 3627 | self.assertEqual(found['config'], [exp1]) | 3633 | self.assertEqual(found['config'], [exp1]) |
448 | 3628 | 3634 | ||
449 | @@ -3632,9 +3638,11 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): | |||
450 | 3632 | populate_dir(root, content) | 3638 | populate_dir(root, content) |
451 | 3633 | self.reRoot(root) | 3639 | self.reRoot(root) |
452 | 3634 | 3640 | ||
456 | 3635 | found = cmdline.read_initramfs_config( | 3641 | src = cmdline.KlibcNetworkConfigSource( |
457 | 3636 | cmdline='foo ip6=dhcp root=/dev/sda', | 3642 | _cmdline='foo ip6=dhcp root=/dev/sda', _mac_addrs=self.macs, |
458 | 3637 | mac_addrs=self.macs) | 3643 | ) |
459 | 3644 | self.assertTrue(src.is_applicable()) | ||
460 | 3645 | found = src.render_config() | ||
461 | 3638 | self.assertEqual( | 3646 | self.assertEqual( |
462 | 3639 | found, | 3647 | found, |
463 | 3640 | {'version': 1, 'config': [ | 3648 | {'version': 1, 'config': [ |
464 | @@ -3648,9 +3656,10 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): | |||
465 | 3648 | # if there is no ip= or ip6= on cmdline, return value should be None | 3656 | # if there is no ip= or ip6= on cmdline, return value should be None |
466 | 3649 | content = {'net6-eno1.conf': DHCP6_CONTENT_1} | 3657 | content = {'net6-eno1.conf': DHCP6_CONTENT_1} |
467 | 3650 | files = sorted(populate_dir(self.tmp_dir(), content)) | 3658 | files = sorted(populate_dir(self.tmp_dir(), content)) |
471 | 3651 | found = cmdline.read_initramfs_config( | 3659 | src = cmdline.KlibcNetworkConfigSource( |
472 | 3652 | files=files, cmdline='foo root=/dev/sda', mac_addrs=self.macs) | 3660 | _files=files, _cmdline='foo root=/dev/sda', _mac_addrs=self.macs, |
473 | 3653 | self.assertIsNone(found) | 3661 | ) |
474 | 3662 | self.assertFalse(src.is_applicable()) | ||
475 | 3654 | 3663 | ||
476 | 3655 | def test_with_both_ip_ip6(self): | 3664 | def test_with_both_ip_ip6(self): |
477 | 3656 | content = { | 3665 | content = { |
478 | @@ -3667,13 +3676,77 @@ class TestCmdlineReadInitramfsConfig(FilesystemMockingTestCase): | |||
479 | 3667 | populate_dir(root, content) | 3676 | populate_dir(root, content) |
480 | 3668 | self.reRoot(root) | 3677 | self.reRoot(root) |
481 | 3669 | 3678 | ||
484 | 3670 | found = cmdline.read_initramfs_config( | 3679 | src = cmdline.KlibcNetworkConfigSource( |
485 | 3671 | cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs) | 3680 | _cmdline='foo ip=dhcp ip6=dhcp', _mac_addrs=self.macs, |
486 | 3681 | ) | ||
487 | 3672 | 3682 | ||
488 | 3683 | self.assertTrue(src.is_applicable()) | ||
489 | 3684 | found = src.render_config() | ||
490 | 3673 | self.assertEqual(found['version'], 1) | 3685 | self.assertEqual(found['version'], 1) |
491 | 3674 | self.assertEqual(found['config'], expected) | 3686 | self.assertEqual(found['config'], expected) |
492 | 3675 | 3687 | ||
493 | 3676 | 3688 | ||
494 | 3689 | class TestReadInitramfsConfig(CiTestCase): | ||
495 | 3690 | |||
496 | 3691 | def _config_source_cls_mock(self, is_applicable, render_config=None): | ||
497 | 3692 | return lambda: mock.Mock( | ||
498 | 3693 | is_applicable=lambda: is_applicable, | ||
499 | 3694 | render_config=lambda: render_config, | ||
500 | 3695 | ) | ||
501 | 3696 | |||
502 | 3697 | def test_no_sources(self): | ||
503 | 3698 | with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', []): | ||
504 | 3699 | self.assertIsNone(cmdline.read_initramfs_config()) | ||
505 | 3700 | |||
506 | 3701 | def test_no_applicable_sources(self): | ||
507 | 3702 | sources = [ | ||
508 | 3703 | self._config_source_cls_mock(is_applicable=False), | ||
509 | 3704 | self._config_source_cls_mock(is_applicable=False), | ||
510 | 3705 | self._config_source_cls_mock(is_applicable=False), | ||
511 | 3706 | ] | ||
512 | 3707 | with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', | ||
513 | 3708 | sources): | ||
514 | 3709 | self.assertIsNone(cmdline.read_initramfs_config()) | ||
515 | 3710 | |||
516 | 3711 | def test_one_applicable_source(self): | ||
517 | 3712 | expected_config = object() | ||
518 | 3713 | sources = [ | ||
519 | 3714 | self._config_source_cls_mock( | ||
520 | 3715 | is_applicable=True, render_config=expected_config, | ||
521 | 3716 | ), | ||
522 | 3717 | ] | ||
523 | 3718 | with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', | ||
524 | 3719 | sources): | ||
525 | 3720 | self.assertEqual(expected_config, cmdline.read_initramfs_config()) | ||
526 | 3721 | |||
527 | 3722 | def test_one_applicable_source_after_inapplicable_sources(self): | ||
528 | 3723 | expected_config = object() | ||
529 | 3724 | sources = [ | ||
530 | 3725 | self._config_source_cls_mock(is_applicable=False), | ||
531 | 3726 | self._config_source_cls_mock(is_applicable=False), | ||
532 | 3727 | self._config_source_cls_mock( | ||
533 | 3728 | is_applicable=True, render_config=expected_config, | ||
534 | 3729 | ), | ||
535 | 3730 | ] | ||
536 | 3731 | with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', | ||
537 | 3732 | sources): | ||
538 | 3733 | self.assertEqual(expected_config, cmdline.read_initramfs_config()) | ||
539 | 3734 | |||
540 | 3735 | def test_first_applicable_source_is_used(self): | ||
541 | 3736 | first_config, second_config = object(), object() | ||
542 | 3737 | sources = [ | ||
543 | 3738 | self._config_source_cls_mock( | ||
544 | 3739 | is_applicable=True, render_config=first_config, | ||
545 | 3740 | ), | ||
546 | 3741 | self._config_source_cls_mock( | ||
547 | 3742 | is_applicable=True, render_config=second_config, | ||
548 | 3743 | ), | ||
549 | 3744 | ] | ||
550 | 3745 | with mock.patch('cloudinit.net.cmdline._INITRAMFS_CONFIG_SOURCES', | ||
551 | 3746 | sources): | ||
552 | 3747 | self.assertEqual(first_config, cmdline.read_initramfs_config()) | ||
553 | 3748 | |||
554 | 3749 | |||
555 | 3677 | class TestNetplanRoundTrip(CiTestCase): | 3750 | class TestNetplanRoundTrip(CiTestCase): |
556 | 3678 | def _render_and_read(self, network_config=None, state=None, | 3751 | def _render_and_read(self, network_config=None, state=None, |
557 | 3679 | netplan_path=None, target=None): | 3752 | 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:/