Merge lp:~wesley-wiedenmeier/curtin/trunk.passthrough-netconfig into lp:~raharper/curtin/trunk.passthrough-netconfig

Proposed by Wesley Wiedenmeier
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
Reviewer Review Type Date Requested Status
Ryan Harper Pending
Review via email: mp+317691@code.launchpad.net

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.install_missing_packages (LP: #1665954):
   - Move logic for detecting custom config package requirements from
     install_missing_packages to detect_required_packages, as this logic may
     be useful in general
   - Extend requirement detection logic to handle v2 network config
   - Add unittests for curthooks.detect_required_packages

To post a comment you must log in.
Revision history for this message
Wesley Wiedenmeier (wesley-wiedenmeier) wrote :

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.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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']

Subscribers

People subscribed via source and target branches