Merge lp:~wesley-wiedenmeier/curtin/trunk.passthrough-netconfig into lp:~raharper/curtin/trunk.passthrough-netconfig
- trunk.passthrough-netconfig
- Merge into trunk.passthrough-netconfig
Status: | Merged |
---|---|
Merge reported by: | Ryan Harper |
Merged at revision: | not available |
Proposed branch: | lp:~wesley-wiedenmeier/curtin/trunk.passthrough-netconfig |
Merge into: | lp:~raharper/curtin/trunk.passthrough-netconfig |
Diff against target: |
692 lines (+481/-63) 8 files modified
curtin/commands/apply_net.py (+7/-2) curtin/commands/curthooks.py (+95/-42) curtin/net/__init__.py (+25/-0) examples/tests/network_v2_passthrough.yaml (+18/-0) tests/unittests/test_commands_apply_net.py (+109/-4) tests/unittests/test_curthooks.py (+181/-0) tests/vmtests/__init__.py (+24/-15) tests/vmtests/test_network_passthrough.py (+22/-0) |
To merge this branch: | bzr merge lp:~wesley-wiedenmeier/curtin/trunk.passthrough-netconfig |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper | Pending | ||
Review via email: mp+317691@code.launchpad.net |
Commit message
Description of the change
Update network config passthrough handling to support v2 netconfig:
- Update passthrough detect behavior:
- If 'passthrough: true' specified in config, use passthrough
- If 'passthrough' not in config and v1 netconfig, detect if in-target
cloud-init supports passthrough, use if available
- If 'passthrough' not in config and v2 netconfig, detect if in-target
cloud-init supports passthrough and v2 networking, use if available.
Even if curtin cannot handle a v2 netconfig, it is better to fail
installation than to use passthrough to a cloud-init that does not
support v2 netconfig as that will be a more silent failure.
- If 'passthrough: false' specified in config, do not use passthrough
- Add additional unit tests for passthrough behavior
- Move system_upgrade curthook to before apply_networking (LP: #1665962)
- Add a basic netconfig v2 passthrough vmtest
- Temporarily fix (LP: #1666058) using required_net_ifaces attr in vmtests
- Refactor curthooks.
- Move logic for detecting custom config package requirements from
install_
be useful in general
- Extend requirement detection logic to handle v2 network config
- Add unittests for curthooks.
Wesley Wiedenmeier (wesley-wiedenmeier) wrote : | # |
Preview Diff
1 | === modified file 'curtin/commands/apply_net.py' |
2 | --- curtin/commands/apply_net.py 2016-11-22 21:58:52 +0000 |
3 | +++ curtin/commands/apply_net.py 2017-02-24 02:26:43 +0000 |
4 | @@ -102,11 +102,16 @@ |
5 | # for rendering at runtime, unless: |
6 | # 1) target OS does not support (cloud-init too old) |
7 | # 2) config disables passthrough |
8 | + # 3) we have v2 netconfig and target OS does not support v2 |
9 | + # (cloud-init too old) |
10 | passthrough = netcfg.get('network', {}).get('passthrough', None) |
11 | LOG.debug('netcfg set passthrough to: %s', passthrough) |
12 | - if not passthrough: |
13 | + if passthrough is None: |
14 | + v2_required = netcfg.get('network', {}).get('version', None) == 2 |
15 | LOG.debug('testing in-target cloud-init version for support') |
16 | - passthrough = net.netconfig_passthrough_available(target) |
17 | + passthrough = (net.netconfig_passthrough_available(target) and |
18 | + (not v2_required or |
19 | + net.netconfig_passthrough_v2_available(target))) |
20 | LOG.debug('passthrough via in-target: %s', passthrough) |
21 | |
22 | if passthrough: |
23 | |
24 | === modified file 'curtin/commands/curthooks.py' |
25 | --- curtin/commands/curthooks.py 2016-08-22 16:20:23 +0000 |
26 | +++ curtin/commands/curthooks.py 2017-02-24 02:26:43 +0000 |
27 | @@ -546,53 +546,106 @@ |
28 | update_initramfs(target, all_kernels=True) |
29 | |
30 | |
31 | -def install_missing_packages(cfg, target): |
32 | - ''' describe which operation types will require specific packages |
33 | - |
34 | - 'custom_config_key': { |
35 | - 'pkg1': ['op_name_1', 'op_name_2', ...] |
36 | - } |
37 | - ''' |
38 | - custom_configs = { |
39 | - 'storage': { |
40 | - 'lvm2': ['lvm_volgroup', 'lvm_partition'], |
41 | - 'mdadm': ['raid'], |
42 | - 'bcache-tools': ['bcache']}, |
43 | - 'network': { |
44 | - 'vlan': ['vlan'], |
45 | - 'ifenslave': ['bond'], |
46 | - 'bridge-utils': ['bridge']}, |
47 | - } |
48 | - |
49 | - format_configs = { |
50 | - 'xfsprogs': ['xfs'], |
51 | - 'e2fsprogs': ['ext2', 'ext3', 'ext4'], |
52 | - 'btrfs-tools': ['btrfs'], |
53 | - } |
54 | - |
55 | - needed_packages = [] |
56 | - installed_packages = util.get_installed_packages(target) |
57 | - for cust_cfg, pkg_reqs in custom_configs.items(): |
58 | - if cust_cfg not in cfg: |
59 | - continue |
60 | - |
61 | +def detect_required_packages(cfg): |
62 | + """ |
63 | + detect packages that will be required in-target by custom config items |
64 | + """ |
65 | + # NOTE: this functionality may be useful in general, and when multi-distro |
66 | + # curtooks is developed could be moved into its own module with a cli |
67 | + |
68 | + def detect_v1_reqs(cust_cfg, pkg_reqs): |
69 | + """ |
70 | + detect required packages for v1 config |
71 | + """ |
72 | + result = [] |
73 | + |
74 | + # get reqs by operation type |
75 | all_types = set( |
76 | operation['type'] |
77 | for operation in cfg[cust_cfg]['config'] |
78 | ) |
79 | for pkg, types in pkg_reqs.items(): |
80 | - if set(types).intersection(all_types) and \ |
81 | - pkg not in installed_packages: |
82 | - needed_packages.append(pkg) |
83 | + if set(types).intersection(all_types): |
84 | + result.append(pkg) |
85 | |
86 | + # check deps required by format operations |
87 | + # NOTE: it may make sense to move this into block.mkfs and extended |
88 | + # it at some point, to keep info about formats in one place |
89 | + v1_storage_format_configs = { |
90 | + 'xfsprogs': ['xfs'], |
91 | + 'e2fsprogs': ['ext2', 'ext3', 'ext4'], |
92 | + 'btrfs-tools': ['btrfs'], |
93 | + } |
94 | format_types = set( |
95 | [operation['fstype'] |
96 | for operation in cfg[cust_cfg]['config'] |
97 | if operation['type'] == 'format']) |
98 | - for pkg, fstypes in format_configs.items(): |
99 | - if set(fstypes).intersection(format_types) and \ |
100 | - pkg not in installed_packages: |
101 | - needed_packages.append(pkg) |
102 | + for pkg, fstypes in v1_storage_format_configs.items(): |
103 | + if set(fstypes).intersection(format_types): |
104 | + result.append(pkg) |
105 | + |
106 | + return result |
107 | + |
108 | + def detect_v2_net_reqs(cust_cfg, pkg_reqs): |
109 | + """ |
110 | + detect required packages for v2 net config |
111 | + """ |
112 | + return set(pkg_reqs.get(dev_type) for dev_type in cfg[cust_cfg] |
113 | + if dev_type in pkg_reqs and cfg[cust_cfg][dev_type]) |
114 | + |
115 | + custom_configs = { |
116 | + 'storage': { |
117 | + 1: {'handler': detect_v1_reqs, |
118 | + 'pkg_reqs': { |
119 | + 'lvm2': ['lvm_volgroup', 'lvm_partition'], |
120 | + 'mdadm': ['raid'], |
121 | + 'bcache-tools': ['bcache']}}}, |
122 | + 'network': { |
123 | + 1: {'handler': detect_v1_reqs, |
124 | + 'pkg_reqs': { |
125 | + 'vlan': ['vlan'], |
126 | + 'ifenslave': ['bond'], |
127 | + 'bridge-utils': ['bridge']}}, |
128 | + 2: {'handler': detect_v2_net_reqs, |
129 | + 'pkg_reqs': { |
130 | + 'bridges': 'bridge-utils', |
131 | + 'vlans': 'vlan', |
132 | + 'bonds': 'ifenslave'}}} |
133 | + } |
134 | + needed_packages = [] |
135 | + |
136 | + for cust_cfg, version_pkg_reqs in custom_configs.items(): |
137 | + # skip missing or invalid custom config items |
138 | + if not isinstance(cfg.get(cust_cfg), dict): |
139 | + continue |
140 | + |
141 | + # ensure that version is supported |
142 | + version = cfg[cust_cfg].get('version') |
143 | + if not isinstance(version, int) or version not in version_pkg_reqs: |
144 | + LOG.warning('skipping pkg req detection for cfg item: %s ' |
145 | + 'because version: %s not supported by ' |
146 | + 'detect_required_packages', cust_cfg, version) |
147 | + continue |
148 | + |
149 | + # make call to handler |
150 | + req_data = version_pkg_reqs[version] |
151 | + found_reqs = req_data['handler'](cust_cfg, req_data['pkg_reqs']) |
152 | + needed_packages.extend(found_reqs) |
153 | + |
154 | + return needed_packages |
155 | + |
156 | + |
157 | +def install_missing_packages(cfg, target): |
158 | + ''' describe which operation types will require specific packages |
159 | + |
160 | + 'custom_config_key': { |
161 | + 'pkg1': ['op_name_1', 'op_name_2', ...] |
162 | + } |
163 | + ''' |
164 | + |
165 | + installed_packages = util.get_installed_packages(target) |
166 | + needed_packages = [pkg for pkg in detect_required_packages(cfg) |
167 | + if pkg not in installed_packages] |
168 | |
169 | if needed_packages: |
170 | state = util.load_command_environment() |
171 | @@ -679,6 +732,11 @@ |
172 | |
173 | with events.ReportEventStack( |
174 | name=stack_prefix, reporting_enabled=True, level="INFO", |
175 | + description="updating packages on target system"): |
176 | + system_upgrade(cfg, target) |
177 | + |
178 | + with events.ReportEventStack( |
179 | + name=stack_prefix, reporting_enabled=True, level="INFO", |
180 | description="setting up swap"): |
181 | add_swap(cfg, target, state.get('fstab')) |
182 | |
183 | @@ -697,11 +755,6 @@ |
184 | description="configuring multipath"): |
185 | detect_and_handle_multipath(cfg, target) |
186 | |
187 | - with events.ReportEventStack( |
188 | - name=stack_prefix, reporting_enabled=True, level="INFO", |
189 | - description="updating packages on target system"): |
190 | - system_upgrade(cfg, target) |
191 | - |
192 | # If a crypttab file was created by block_meta than it needs to be copied |
193 | # onto the target system, and update_initramfs() needs to be run, so that |
194 | # the cryptsetup hooks are properly configured on the installed system and |
195 | |
196 | === modified file 'curtin/net/__init__.py' |
197 | --- curtin/net/__init__.py 2016-11-22 21:58:52 +0000 |
198 | +++ curtin/net/__init__.py 2017-02-24 02:26:43 +0000 |
199 | @@ -531,6 +531,31 @@ |
200 | return True |
201 | |
202 | |
203 | +def netconfig_passthrough_v2_available(target, feature='NETWORK_CONFIG_V2'): |
204 | + """ |
205 | + Determine if curtin can pass v2 network config to in target cloud-init |
206 | + """ |
207 | + LOG.debug('Checking in-target cloud-init features') |
208 | + cmd = ("from cloudinit import version;" |
209 | + "print('{}' in getattr(version, 'FEATURES', []))" |
210 | + .format(feature)) |
211 | + with util.ChrootableTarget(target) as in_chroot: |
212 | + |
213 | + def run_cmd(cmd): |
214 | + (out, _) = in_chroot.subp(cmd, capture=True) |
215 | + return out.strip() |
216 | + |
217 | + cloud_init_path = util.which('cloud-init', target=target) |
218 | + if not cloud_init_path: |
219 | + LOG.debug('cloud-init not available in target=%s', target) |
220 | + return False |
221 | + |
222 | + script_shebang = run_cmd(['head', '-n1', cloud_init_path]) |
223 | + python = script_shebang.split('/')[-1] |
224 | + feature_available = run_cmd([python, '-c', cmd]) |
225 | + return config.value_as_boolean(feature_available) |
226 | + |
227 | + |
228 | def render_netconfig_passthrough(target, netconfig=None): |
229 | """ |
230 | Extract original network config and pass it |
231 | |
232 | === added file 'examples/tests/network_v2_passthrough.yaml' |
233 | --- examples/tests/network_v2_passthrough.yaml 1970-01-01 00:00:00 +0000 |
234 | +++ examples/tests/network_v2_passthrough.yaml 2017-02-24 02:26:43 +0000 |
235 | @@ -0,0 +1,18 @@ |
236 | +showtrace: true |
237 | +system_upgrade: |
238 | + enabled: true |
239 | +apt: |
240 | + preserve_sources_list: false |
241 | + sources: |
242 | + cloud-init-test-repo.list: |
243 | + # FIXME: this should be switched to curtin-dev or cloud-init-dev |
244 | + # for use on jenkins, and should not be signed with my key |
245 | + source: "deb http://ppa.launchpad.net/wesley-wiedenmeier/test/ubuntu xenial main" |
246 | + keyid: 2B91B36D |
247 | +network: |
248 | + version: 2 |
249 | + ethernets: |
250 | + interface0: |
251 | + match: |
252 | + mac_address: "52:54:00:12:34:00" |
253 | + dhcp4: true |
254 | |
255 | === modified file 'tests/unittests/test_commands_apply_net.py' |
256 | --- tests/unittests/test_commands_apply_net.py 2016-11-22 21:58:52 +0000 |
257 | +++ tests/unittests/test_commands_apply_net.py 2017-02-24 02:26:43 +0000 |
258 | @@ -31,6 +31,8 @@ |
259 | 'mock_ipv6_mtu') |
260 | self.add_patch('curtin.net.netconfig_passthrough_available', |
261 | 'mock_netpass_avail') |
262 | + self.add_patch('curtin.net.netconfig_passthrough_v2_available', |
263 | + 'mock_netpass_v2_avail') |
264 | self.add_patch('curtin.net.render_netconfig_passthrough', |
265 | 'mock_netpass_render') |
266 | self.add_patch('curtin.net.parse_net_config_data', |
267 | @@ -54,7 +56,7 @@ |
268 | 'dns': { |
269 | 'nameservers': [], |
270 | 'search': [], |
271 | - } |
272 | + } |
273 | } |
274 | |
275 | # def tearDown(self): |
276 | @@ -89,6 +91,7 @@ |
277 | network_config=self.network_config) |
278 | |
279 | self.mock_netpass_avail.assert_called_with(self.target) |
280 | + self.assertFalse(self.mock_netpass_v2_avail.called) |
281 | |
282 | self.mock_net_renderstate.assert_called_with(target=self.target, |
283 | network_state=self.ns) |
284 | @@ -107,6 +110,7 @@ |
285 | self.assertFalse(self.mock_ns_from_file.called) |
286 | self.mock_load_config.assert_called_with(netcfg) |
287 | self.mock_netpass_avail.assert_called_with(self.target) |
288 | + self.assertFalse(self.mock_netpass_v2_avail.called) |
289 | nc = self.network_config |
290 | self.mock_netpass_render.assert_called_with(self.target, netconfig=nc) |
291 | |
292 | @@ -128,6 +132,7 @@ |
293 | self.assertFalse(self.mock_ns_from_file.called) |
294 | self.mock_load_config.assert_called_with(netcfg) |
295 | self.assertFalse(self.mock_netpass_avail.called) |
296 | + self.assertFalse(self.mock_netpass_v2_avail.called) |
297 | self.mock_netpass_render.assert_called_with(self.target, netconfig=cfg) |
298 | |
299 | self.assertFalse(self.mock_net_renderstate.called) |
300 | @@ -148,9 +153,109 @@ |
301 | self.assertFalse(self.mock_ns_from_file.called) |
302 | self.mock_load_config.assert_called_with(netcfg) |
303 | self.mock_netpass_avail.assert_called_with(self.target) |
304 | - self.mock_netpass_render.assert_called_with(self.target, netconfig=nc) |
305 | - |
306 | - self.assertFalse(self.mock_net_renderstate.called) |
307 | + self.assertFalse(self.mock_netpass_v2_avail.called) |
308 | + self.mock_netpass_render.assert_called_with(self.target, netconfig=nc) |
309 | + |
310 | + self.assertFalse(self.mock_net_renderstate.called) |
311 | + self.mock_legacy.assert_called_with(self.target) |
312 | + self.mock_ipv6_priv.assert_called_with(self.target) |
313 | + self.mock_ipv6_mtu.assert_called_with(self.target) |
314 | + |
315 | + def test_apply_net_target_and_config_passthrough_v2(self): |
316 | + nc = copy.deepcopy(self.network_config) |
317 | + nc['network']['version'] = 2 |
318 | + self.mock_load_config.return_value = nc |
319 | + self.mock_netpass_avail.return_value = True |
320 | + self.mock_netpass_v2_avail.return_value = True |
321 | + |
322 | + netcfg = "network_config.yaml" |
323 | + |
324 | + apply_net.apply_net(self.target, network_state=None, |
325 | + network_config=netcfg) |
326 | + |
327 | + self.assertFalse(self.mock_ns_from_file.called) |
328 | + self.mock_load_config.assert_called_with(netcfg) |
329 | + self.mock_netpass_avail.assert_called_with(self.target) |
330 | + self.mock_netpass_v2_avail.assert_called_with(self.target) |
331 | + self.mock_netpass_render.assert_called_with(self.target, netconfig=nc) |
332 | + |
333 | + self.assertFalse(self.mock_net_renderstate.called) |
334 | + self.mock_legacy.assert_called_with(self.target) |
335 | + self.mock_ipv6_priv.assert_called_with(self.target) |
336 | + self.mock_ipv6_mtu.assert_called_with(self.target) |
337 | + |
338 | + def test_apply_net_target_and_config_passthrough_v2_not_available(self): |
339 | + nc = copy.deepcopy(self.network_config) |
340 | + nc['network']['version'] = 2 |
341 | + self.mock_load_config.return_value = nc |
342 | + self.mock_netpass_avail.return_value = True |
343 | + self.mock_netpass_v2_avail.return_value = False |
344 | + self.mock_net_parsedata.return_value = self.ns |
345 | + |
346 | + netcfg = "network_config.yaml" |
347 | + |
348 | + apply_net.apply_net(self.target, network_state=None, |
349 | + network_config=netcfg) |
350 | + |
351 | + self.assertFalse(self.mock_ns_from_file.called) |
352 | + self.mock_load_config.assert_called_with(netcfg) |
353 | + self.mock_netpass_avail.assert_called_with(self.target) |
354 | + self.mock_netpass_v2_avail.assert_called_with(self.target) |
355 | + self.assertFalse(self.mock_netpass_render.called) |
356 | + self.mock_net_parsedata.assert_called_with(nc['network']) |
357 | + |
358 | + self.mock_net_renderstate.assert_called_with( |
359 | + target=self.target, network_state=self.ns) |
360 | + self.mock_legacy.assert_called_with(self.target) |
361 | + self.mock_ipv6_priv.assert_called_with(self.target) |
362 | + self.mock_ipv6_mtu.assert_called_with(self.target) |
363 | + |
364 | + def test_apply_net_target_and_config_passthrough_v2_force(self): |
365 | + nc = copy.deepcopy(self.network_config) |
366 | + nc['network']['version'] = 2 |
367 | + nc['network']['passthrough'] = True |
368 | + self.mock_load_config.return_value = nc |
369 | + self.mock_netpass_avail.return_value = False |
370 | + self.mock_netpass_v2_avail.return_value = False |
371 | + |
372 | + netcfg = "network_config.yaml" |
373 | + |
374 | + apply_net.apply_net(self.target, network_state=None, |
375 | + network_config=netcfg) |
376 | + |
377 | + self.assertFalse(self.mock_ns_from_file.called) |
378 | + self.mock_load_config.assert_called_with(netcfg) |
379 | + self.assertFalse(self.mock_netpass_avail.called) |
380 | + self.assertFalse(self.mock_netpass_v2_avail.called) |
381 | + self.mock_netpass_render.assert_called_with(self.target, netconfig=nc) |
382 | + |
383 | + self.assertFalse(self.mock_net_renderstate.called) |
384 | + self.mock_legacy.assert_called_with(self.target) |
385 | + self.mock_ipv6_priv.assert_called_with(self.target) |
386 | + self.mock_ipv6_mtu.assert_called_with(self.target) |
387 | + |
388 | + def test_apply_net_disable_passthrough(self): |
389 | + nc = copy.deepcopy(self.network_config) |
390 | + nc['network']['passthrough'] = False |
391 | + self.mock_load_config.return_value = nc |
392 | + self.mock_netpass_avail.return_value = False |
393 | + self.mock_netpass_v2_avail.return_value = False |
394 | + self.mock_net_parsedata.return_value = self.ns |
395 | + |
396 | + netcfg = "network_config.yaml" |
397 | + |
398 | + apply_net.apply_net(self.target, network_state=None, |
399 | + network_config=netcfg) |
400 | + |
401 | + self.assertFalse(self.mock_ns_from_file.called) |
402 | + self.mock_load_config.assert_called_with(netcfg) |
403 | + self.assertFalse(self.mock_netpass_avail.called) |
404 | + self.assertFalse(self.mock_netpass_v2_avail.called) |
405 | + self.assertFalse(self.mock_netpass_render.called) |
406 | + self.mock_net_parsedata.assert_called_with(nc['network']) |
407 | + |
408 | + self.mock_net_renderstate.assert_called_with( |
409 | + target=self.target, network_state=self.ns) |
410 | self.mock_legacy.assert_called_with(self.target) |
411 | self.mock_ipv6_priv.assert_called_with(self.target) |
412 | self.mock_ipv6_mtu.assert_called_with(self.target) |
413 | |
414 | === added file 'tests/unittests/test_curthooks.py' |
415 | --- tests/unittests/test_curthooks.py 1970-01-01 00:00:00 +0000 |
416 | +++ tests/unittests/test_curthooks.py 2017-02-24 02:26:43 +0000 |
417 | @@ -0,0 +1,181 @@ |
418 | +import unittest |
419 | +from curtin.commands import curthooks |
420 | + |
421 | + |
422 | +class TestDetectRequiredPackages(unittest.TestCase): |
423 | + config = { |
424 | + 'storage': { |
425 | + 1: { |
426 | + 'bcache': { |
427 | + 'type': 'bcache', 'name': 'bcache0', 'id': 'cache0', |
428 | + 'backing_device': 'sda3', 'cache_device': 'sdb'}, |
429 | + 'lvm_partition': { |
430 | + 'id': 'lvol1', 'name': 'lv1', 'volgroup': 'vg1', |
431 | + 'type': 'lvm_partition'}, |
432 | + 'lvm_volgroup': { |
433 | + 'id': 'vol1', 'name': 'vg1', 'devices': ['sda', 'sdb'], |
434 | + 'type': 'lvm_volgroup'}, |
435 | + 'raid': { |
436 | + 'id': 'mddevice', 'name': 'md0', 'type': 'raid', |
437 | + 'raidlevel': 5, 'devices': ['sda1', 'sdb1', 'sdc1']}, |
438 | + 'ext2': { |
439 | + 'id': 'format0', 'fstype': 'ext2', 'type': 'format'}, |
440 | + 'ext3': { |
441 | + 'id': 'format1', 'fstype': 'ext3', 'type': 'format'}, |
442 | + 'ext4': { |
443 | + 'id': 'format2', 'fstype': 'ext4', 'type': 'format'}, |
444 | + 'btrfs': { |
445 | + 'id': 'format3', 'fstype': 'btrfs', 'type': 'format'}, |
446 | + 'xfs': { |
447 | + 'id': 'format4', 'fstype': 'xfs', 'type': 'format'}} |
448 | + }, |
449 | + 'network': { |
450 | + 1: { |
451 | + 'bond': { |
452 | + 'name': 'bond0', 'type': 'bond', |
453 | + 'bond_interfaces': ['interface0', 'interface1'], |
454 | + 'params': {'bond-mode': 'active-backup'}, |
455 | + 'subnets': [ |
456 | + {'type': 'static', 'address': '10.23.23.2/24'}, |
457 | + {'type': 'static', 'address': '10.23.24.2/24'}]}, |
458 | + 'vlan': { |
459 | + 'id': 'interface1.2667', 'mtu': 1500, 'name': |
460 | + 'interface1.2667', 'type': 'vlan', 'vlan_id': 2667, |
461 | + 'vlan_link': 'interface1', |
462 | + 'subnets': [{'address': '10.245.184.2/24', |
463 | + 'dns_nameservers': [], 'type': 'static'}]}, |
464 | + 'bridge': { |
465 | + 'name': 'br0', 'bridge_interfaces': ['eth0', 'eth1'], |
466 | + 'type': 'bridge', 'params': { |
467 | + 'bridge_stp': 'off', 'bridge_fd': 0, |
468 | + 'bridge_maxwait': 0}, |
469 | + 'subnets': [ |
470 | + {'type': 'static', 'address': '192.168.14.2/24'}, |
471 | + {'type': 'static', 'address': '2001:1::1/64'}]}}, |
472 | + 2: { |
473 | + 'vlan': { |
474 | + 'vlans': { |
475 | + 'en-intra': {'id': 1, 'link': 'eno1', 'dhcp4': 'yes'}, |
476 | + 'en-vpn': {'id': 2, 'link': 'eno1'}}}, |
477 | + 'bridge': { |
478 | + 'bridges': { |
479 | + 'br0': { |
480 | + 'interfaces': ['wlp1s0', 'switchports'], |
481 | + 'dhcp4': True}}}} |
482 | + }, |
483 | + } |
484 | + |
485 | + def _fmt_config(self, config_items): |
486 | + res = {} |
487 | + for item, item_confs in config_items.items(): |
488 | + version = item_confs['version'] |
489 | + res[item] = {'version': version} |
490 | + if version == 1: |
491 | + res[item]['config'] = [self.config[item][version][i] |
492 | + for i in item_confs['items']] |
493 | + elif version == 2 and item == 'network': |
494 | + for cfg_item in item_confs['items']: |
495 | + res[item].update(self.config[item][version][cfg_item]) |
496 | + else: |
497 | + raise NotImplementedError |
498 | + return res |
499 | + |
500 | + def _test_req_mappings(self, req_mappings): |
501 | + for (config_items, expected_reqs) in req_mappings: |
502 | + config = self._fmt_config(config_items) |
503 | + actual_reqs = curthooks.detect_required_packages(config) |
504 | + self.assertEqual(set(actual_reqs), set(expected_reqs), |
505 | + 'failed for config: {}'.format(config_items)) |
506 | + |
507 | + def test_storage_v1_detect(self): |
508 | + self._test_req_mappings(( |
509 | + ({'storage': { |
510 | + 'version': 1, |
511 | + 'items': ('lvm_partition', 'lvm_volgroup', 'btrfs', 'xfs')}}, |
512 | + ('lvm2', 'xfsprogs', 'btrfs-tools')), |
513 | + ({'storage': { |
514 | + 'version': 1, |
515 | + 'items': ('raid', 'bcache', 'ext3', 'xfs')}}, |
516 | + ('mdadm', 'bcache-tools', 'e2fsprogs', 'xfsprogs')), |
517 | + ({'storage': { |
518 | + 'version': 1, |
519 | + 'items': ('raid', 'lvm_volgroup', 'lvm_partition', 'ext3', |
520 | + 'ext4', 'btrfs')}}, |
521 | + ('lvm2', 'mdadm', 'e2fsprogs', 'btrfs-tools')), |
522 | + ({'storage': { |
523 | + 'version': 1, |
524 | + 'items': ('bcache', 'lvm_volgroup', 'lvm_partition', 'ext2')}}, |
525 | + ('bcache-tools', 'lvm2', 'e2fsprogs')), |
526 | + )) |
527 | + |
528 | + def test_network_v1_detect(self): |
529 | + self._test_req_mappings(( |
530 | + ({'network': { |
531 | + 'version': 1, |
532 | + 'items': ('bridge',)}}, |
533 | + ('bridge-utils',)), |
534 | + ({'network': { |
535 | + 'version': 1, |
536 | + 'items': ('vlan', 'bond')}}, |
537 | + ('vlan', 'ifenslave')), |
538 | + ({'network': { |
539 | + 'version': 1, |
540 | + 'items': ('bond', 'bridge')}}, |
541 | + ('ifenslave', 'bridge-utils')), |
542 | + ({'network': { |
543 | + 'version': 1, |
544 | + 'items': ('vlan', 'bridge', 'bond')}}, |
545 | + ('ifenslave', 'bridge-utils', 'vlan')), |
546 | + )) |
547 | + |
548 | + def test_mixed_v1_detect(self): |
549 | + self._test_req_mappings(( |
550 | + ({'storage': { |
551 | + 'version': 1, |
552 | + 'items': ('raid', 'bcache', 'ext4')}, |
553 | + 'network': { |
554 | + 'version': 1, |
555 | + 'items': ('vlan',)}}, |
556 | + ('mdadm', 'bcache-tools', 'e2fsprogs', 'vlan')), |
557 | + ({'storage': { |
558 | + 'version': 1, |
559 | + 'items': ('lvm_partition', 'lvm_volgroup', 'xfs')}, |
560 | + 'network': { |
561 | + 'version': 1, |
562 | + 'items': ('bridge', 'bond')}}, |
563 | + ('lvm2', 'xfsprogs', 'bridge-utils', 'ifenslave')), |
564 | + ({'storage': { |
565 | + 'version': 1, |
566 | + 'items': ('ext3', 'ext4', 'btrfs')}, |
567 | + 'network': { |
568 | + 'version': 1, |
569 | + 'items': ('bond', 'vlan')}}, |
570 | + ('e2fsprogs', 'btrfs-tools', 'vlan', 'ifenslave')), |
571 | + )) |
572 | + |
573 | + def test_network_v2_detect(self): |
574 | + self._test_req_mappings(( |
575 | + ({'network': { |
576 | + 'version': 2, |
577 | + 'items': ('bridge',)}}, |
578 | + ('bridge-utils',)), |
579 | + ({'network': { |
580 | + 'version': 2, |
581 | + 'items': ('vlan',)}}, |
582 | + ('vlan',)), |
583 | + ({'network': { |
584 | + 'version': 2, |
585 | + 'items': ('vlan', 'bridge')}}, |
586 | + ('vlan', 'bridge-utils')), |
587 | + )) |
588 | + |
589 | + def test_mixed_storage_v1_network_v2_detect(self): |
590 | + self._test_req_mappings(( |
591 | + ({'network': { |
592 | + 'version': 2, |
593 | + 'items': ('bridge', 'vlan')}, |
594 | + 'storage': { |
595 | + 'version': 1, |
596 | + 'items': ('raid', 'bcache', 'ext4')}}, |
597 | + ('vlan', 'bridge-utils', 'mdadm', 'bcache-tools', 'e2fsprogs')), |
598 | + )) |
599 | |
600 | === modified file 'tests/vmtests/__init__.py' |
601 | --- tests/vmtests/__init__.py 2017-01-11 16:04:36 +0000 |
602 | +++ tests/vmtests/__init__.py 2017-02-24 02:26:43 +0000 |
603 | @@ -330,6 +330,7 @@ |
604 | nvme_disks = [] |
605 | recorded_errors = 0 |
606 | recorded_failures = 0 |
607 | + required_net_ifaces = None |
608 | uefi = False |
609 | proxy = None |
610 | |
611 | @@ -404,21 +405,29 @@ |
612 | install_src = "PUBURL/" + os.path.basename(ftypes['vmtest.root-tgz']) |
613 | cmd.append("--publish=%s" % ftypes['vmtest.root-tgz']) |
614 | |
615 | - # check for network configuration |
616 | - cls.network_state = curtin_net.parse_net_config(cls.conf_file) |
617 | - logger.debug("Network state: {}".format(cls.network_state)) |
618 | - |
619 | - # build -n arg list with macaddrs from net_config physical config |
620 | - macs = [] |
621 | - interfaces = {} |
622 | - if cls.network_state: |
623 | - interfaces = cls.network_state.get('interfaces') |
624 | - for ifname in interfaces: |
625 | - logger.debug("Interface name: {}".format(ifname)) |
626 | - iface = interfaces.get(ifname) |
627 | - hwaddr = iface.get('mac_address') |
628 | - if hwaddr: |
629 | - macs.append(hwaddr) |
630 | + # build network device list, either from required_net_ifaces or from |
631 | + # detection of mac_addresses in storage config |
632 | + if isinstance(cls.required_net_ifaces, (list, tuple)): |
633 | + # get mac addresses from required_net_ifaces |
634 | + macs = cls.required_net_ifaces |
635 | + else: |
636 | + # check for network configuration |
637 | + cls.network_state = curtin_net.parse_net_config(cls.conf_file) |
638 | + logger.debug("Network state: {}".format(cls.network_state)) |
639 | + |
640 | + # build -n arg list with macaddrs from net_config physical config |
641 | + macs = [] |
642 | + interfaces = {} |
643 | + if cls.network_state: |
644 | + interfaces = cls.network_state.get('interfaces') |
645 | + for ifname in interfaces: |
646 | + logger.debug("Interface name: {}".format(ifname)) |
647 | + iface = interfaces.get(ifname) |
648 | + hwaddr = iface.get('mac_address') |
649 | + if hwaddr: |
650 | + macs.append(hwaddr) |
651 | + |
652 | + # construct netdev args list |
653 | netdevs = [] |
654 | if len(macs) > 0: |
655 | for mac in macs: |
656 | |
657 | === modified file 'tests/vmtests/test_network_passthrough.py' |
658 | --- tests/vmtests/test_network_passthrough.py 2017-01-11 16:04:36 +0000 |
659 | +++ tests/vmtests/test_network_passthrough.py 2017-02-24 02:26:43 +0000 |
660 | @@ -12,6 +12,21 @@ |
661 | pass |
662 | |
663 | |
664 | +class TestNetworkV2PassthroughAbs(TestNetworkPassthroughAbs): |
665 | + """Test network passthrough with v2 netconfig""" |
666 | + conf_file = "examples/tests/network_v2_passthrough.yaml" |
667 | + |
668 | + # FIXME: need methods here and in TestNetworkPassthroughAbs to verify |
669 | + # correctness of cloud-init's network config v2 support |
670 | + # FIXME: need to create a network state object from v2 config in order to |
671 | + # verify everything properly here |
672 | + def test_etc_resolvconf(self): |
673 | + pass |
674 | + |
675 | + def test_ip_output(self): |
676 | + pass |
677 | + |
678 | + |
679 | class PreciseHWETTestNetworkPassthrough(relbase.precise_hwe_t, |
680 | TestNetworkPassthroughAbs): |
681 | # cloud-init too old |
682 | @@ -30,3 +45,10 @@ |
683 | class YakketyTestNetworkPassthrough(relbase.yakkety, |
684 | TestNetworkPassthroughAbs): |
685 | __test__ = True |
686 | + |
687 | + |
688 | +class XenialTestNetworkV2Passthrough( |
689 | + relbase.xenial, TestNetworkV2PassthroughAbs): |
690 | + # test for v2 only available on xenial due to repo add syntax |
691 | + __test__ = True |
692 | + required_net_ifaces = ['52:54:00:12:34:00'] |
This should be almost ready to merge. The vmtest for v2 netconfig passthrough currently is using a build of cloud-init with feature flag support in my ppa. Once the cloud-init feature flags are merged in: https:/ /code.launchpad .net/~wesley- wiedenmeier/ cloud-init/ +git/cloud- init/+merge/ 317589 we should probably switch it to a build of the netconfig- v2-passthrough devel branch in a ppa under one of the qa accounts.