Merge lp:~smoser/curtin/centos-passthrough into lp:~curtin-dev/curtin/trunk

Proposed by Scott Moser
Status: Merged
Merged at revision: 511
Proposed branch: lp:~smoser/curtin/centos-passthrough
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 2786 lines (+1729/-180)
31 files modified
curtin/__init__.py (+2/-0)
curtin/block/__init__.py (+69/-20)
curtin/commands/apply_net.py (+34/-8)
curtin/commands/curthooks.py (+161/-53)
curtin/net/__init__.py (+106/-0)
curtin/util.py (+38/-0)
examples/network-ipv6-bond-vlan.yaml (+2/-2)
examples/tests/bonding_network.yaml (+1/-4)
examples/tests/centos_basic.yaml (+2/-1)
examples/tests/centos_defaults.yaml (+26/-0)
examples/tests/network_alias.yaml (+29/-29)
examples/tests/network_v2_passthrough.yaml (+8/-0)
tests/unittests/test_commands_apply_net.py (+346/-0)
tests/unittests/test_curthooks.py (+185/-1)
tests/unittests/test_feature.py (+3/-0)
tests/unittests/test_net.py (+91/-10)
tests/vmtests/__init__.py (+7/-1)
tests/vmtests/test_centos_basic.py (+35/-0)
tests/vmtests/test_network.py (+190/-27)
tests/vmtests/test_network_alias.py (+33/-0)
tests/vmtests/test_network_bonding.py (+35/-0)
tests/vmtests/test_network_bridging.py (+67/-13)
tests/vmtests/test_network_ipv6.py (+40/-0)
tests/vmtests/test_network_ipv6_enisource.py (+26/-0)
tests/vmtests/test_network_ipv6_static.py (+17/-1)
tests/vmtests/test_network_ipv6_vlan.py (+23/-1)
tests/vmtests/test_network_mtu.py (+57/-2)
tests/vmtests/test_network_static.py (+30/-0)
tests/vmtests/test_network_static_routes.py (+19/-1)
tests/vmtests/test_network_vlan.py (+44/-5)
tools/build-deb (+3/-1)
To merge this branch: bzr merge lp:~smoser/curtin/centos-passthrough
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
curtin developers Pending
Review via email: mp+328145@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
lp:~smoser/curtin/centos-passthrough updated
582. By Scott Moser

skip_by_date on mtu tests for CentOS.

This does a skip by date for bug 1706973 on the individual tests:
    test_ipv6_mtu_smaller_than_ipv4_v6_iface_first(cls):
    test_ipv6_mtu_smaller_than_ipv4_non_default(cls):
    test_ipv6_mtu_higher_than_default_no_ipv4_iface_up(cls):
    test_ipv6_mtu_higher_than_default_no_ipv4_iface_v6_iface_first(cls):

It also adds a 'pass' for test_ip_output, which just currently run
correctly.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'curtin/__init__.py'
2--- curtin/__init__.py 2017-03-21 16:41:33 +0000
3+++ curtin/__init__.py 2017-07-27 13:56:24 +0000
4@@ -23,6 +23,8 @@
5 # can determine which features are supported. Each entry should have
6 # a consistent meaning.
7 FEATURES = [
8+ # curtin can configure centos networking via curthooks function
9+ 'CENTOS_NETWORK_CURTHOOKS',
10 # install supports the 'network' config version 1
11 'NETWORK_CONFIG_V1',
12 # reporter supports 'webhook' type
13
14=== modified file 'curtin/block/__init__.py'
15--- curtin/block/__init__.py 2017-05-19 18:52:21 +0000
16+++ curtin/block/__init__.py 2017-07-27 13:56:24 +0000
17@@ -19,7 +19,6 @@
18 import errno
19 import itertools
20 import os
21-import shlex
22 import stat
23 import sys
24 import tempfile
25@@ -204,30 +203,13 @@
26 return [path_to_kname(device)]
27
28
29-def _shlex_split(str_in):
30- # shlex.split takes a string
31- # but in python2 if input here is a unicode, encode it to a string.
32- # http://stackoverflow.com/questions/2365411/
33- # python-convert-unicode-to-ascii-without-errors
34- if sys.version_info.major == 2:
35- try:
36- if isinstance(str_in, unicode):
37- str_in = str_in.encode('utf-8')
38- except NameError:
39- pass
40-
41- return shlex.split(str_in)
42- else:
43- return shlex.split(str_in)
44-
45-
46 def _lsblock_pairs_to_dict(lines):
47 """
48 parse lsblock output and convert to dict
49 """
50 ret = {}
51 for line in lines.splitlines():
52- toks = _shlex_split(line)
53+ toks = util.shlex_split(line)
54 cur = {}
55 for tok in toks:
56 k, v = tok.split("=", 1)
57@@ -468,7 +450,7 @@
58 for line in out.splitlines():
59 curdev, curdata = line.split(":", 1)
60 data[curdev] = dict(tok.split('=', 1)
61- for tok in _shlex_split(curdata))
62+ for tok in util.shlex_split(curdata))
63 return data
64
65
66@@ -978,4 +960,71 @@
67 else:
68 raise ValueError("wipe mode %s not supported" % mode)
69
70+
71+def storage_config_required_packages(storage_config, mapping):
72+ """Read storage configuration dictionary and determine
73+ which packages are required for the supplied configuration
74+ to function. Return a list of packaged to install.
75+ """
76+
77+ if not storage_config or not isinstance(storage_config, dict):
78+ raise ValueError('Invalid storage configuration. '
79+ 'Must be a dict:\n %s' % storage_config)
80+
81+ if not mapping or not isinstance(mapping, dict):
82+ raise ValueError('Invalid storage mapping. Must be a dict')
83+
84+ if 'storage' in storage_config:
85+ storage_config = storage_config.get('storage')
86+
87+ needed_packages = []
88+
89+ # get reqs by device operation type
90+ dev_configs = set(operation['type']
91+ for operation in storage_config['config'])
92+
93+ for dev_type in dev_configs:
94+ if dev_type in mapping:
95+ needed_packages.extend(mapping[dev_type])
96+
97+ # for any format operations, check the fstype and
98+ # determine if we need any mkfs tools as well.
99+ format_configs = set([operation['fstype']
100+ for operation in storage_config['config']
101+ if operation['type'] == 'format'])
102+ for format_type in format_configs:
103+ if format_type in mapping:
104+ needed_packages.extend(mapping[format_type])
105+
106+ return needed_packages
107+
108+
109+def detect_required_packages_mapping():
110+ """Return a dictionary providing a versioned configuration which maps
111+ storage configuration elements to the packages which are required
112+ for functionality.
113+
114+ The mapping key is either a config type value, or an fstype value.
115+
116+ """
117+ version = 1
118+ mapping = {
119+ version: {
120+ 'handler': storage_config_required_packages,
121+ 'mapping': {
122+ 'bcache': ['bcache-tools'],
123+ 'btrfs': ['btrfs-tools'],
124+ 'ext2': ['e2fsprogs'],
125+ 'ext3': ['e2fsprogs'],
126+ 'ext4': ['e2fsprogs'],
127+ 'lvm_partition': ['lvm2'],
128+ 'lvm_volgroup': ['lvm2'],
129+ 'raid': ['mdadm'],
130+ 'xfs': ['xfsprogs']
131+ },
132+ },
133+ }
134+ return mapping
135+
136+
137 # vi: ts=4 expandtab syntax=python
138
139=== modified file 'curtin/commands/apply_net.py'
140--- curtin/commands/apply_net.py 2017-02-08 05:56:12 +0000
141+++ curtin/commands/apply_net.py 2017-07-27 13:56:24 +0000
142@@ -21,6 +21,7 @@
143 from .. import log
144 import curtin.net as net
145 import curtin.util as util
146+from curtin import config
147 from . import populate_one_subcmd
148
149
150@@ -89,15 +90,38 @@
151 sys.stderr.write(msg + "\n")
152 raise Exception(msg)
153
154+ passthrough = False
155 if network_state:
156+ # NB: we cannot support passthrough until curtin can convert from
157+ # network_state to network-config yaml
158 ns = net.network_state.from_state_file(network_state)
159+ raise ValueError('Not Supported; curtin lacks a network_state to '
160+ 'network_config converter.')
161 elif network_config:
162- ns = net.parse_net_config(network_config)
163-
164- net.render_network_state(target=target, network_state=ns)
165+ netcfg = config.load_config(network_config)
166+
167+ # curtin will pass-through the netconfig into the target
168+ # for rendering at runtime unless the target OS does not
169+ # support NETWORK_CONFIG_V2 feature.
170+ LOG.info('Checking cloud-init in target [%s] for network '
171+ 'configuration passthrough support.', target)
172+ try:
173+ passthrough = net.netconfig_passthrough_available(target)
174+ except util.ProcessExecutionError:
175+ LOG.warning('Failed to determine if passthrough is available')
176+
177+ if passthrough:
178+ LOG.info('Passing network configuration through to target: %s',
179+ target)
180+ net.render_netconfig_passthrough(target, netconfig=netcfg)
181+ else:
182+ ns = net.parse_net_config_data(netcfg.get('network', {}))
183+
184+ if not passthrough:
185+ LOG.info('Rendering network configuration in target')
186+ net.render_network_state(target=target, network_state=ns)
187
188 _maybe_remove_legacy_eth0(target)
189- LOG.info('Attempting to remove ipv6 privacy extensions')
190 _disable_ipv6_privacy_extensions(target)
191 _patch_ifupdown_ipv6_mtu_hook(target)
192
193@@ -130,6 +154,7 @@
194 by default; this races with the cloud-image desire to disable them.
195 Resolve this by allowing the cloud-image setting to win. """
196
197+ LOG.debug('Attempting to remove ipv6 privacy extensions')
198 cfg = util.target_path(target, path=path)
199 if not os.path.exists(cfg):
200 LOG.warn('Failed to find ipv6 privacy conf file %s', cfg)
201@@ -143,7 +168,7 @@
202 lines = [f.strip() for f in contents.splitlines()
203 if not f.startswith("#")]
204 if lines == known_contents:
205- LOG.info('deleting file: %s', cfg)
206+ LOG.info('Removing ipv6 privacy extension config file: %s', cfg)
207 util.del_file(cfg)
208 msg = "removed %s with known contents" % cfg
209 curtin_contents = '\n'.join(
210@@ -153,9 +178,10 @@
211 "# net.ipv6.conf.default.use_tempaddr = 2"])
212 util.write_file(cfg, curtin_contents)
213 else:
214- LOG.info('skipping, content didnt match')
215- LOG.debug("found content:\n%s", lines)
216- LOG.debug("expected contents:\n%s", known_contents)
217+ LOG.debug('skipping removal of %s, expected content not found',
218+ cfg)
219+ LOG.debug("Found content in file %s:\n%s", cfg, lines)
220+ LOG.debug("Expected contents in file %s:\n%s", cfg, known_contents)
221 msg = (bmsg + " '%s' exists with user configured content." % cfg)
222 except Exception as e:
223 msg = bmsg + " %s exists, but could not be read. %s" % (cfg, e)
224
225=== modified file 'curtin/commands/curthooks.py'
226--- curtin/commands/curthooks.py 2017-05-12 00:49:43 +0000
227+++ curtin/commands/curthooks.py 2017-07-27 13:56:24 +0000
228@@ -16,6 +16,7 @@
229 # along with Curtin. If not, see <http://www.gnu.org/licenses/>.
230
231 import copy
232+import glob
233 import os
234 import platform
235 import re
236@@ -25,6 +26,7 @@
237
238 from curtin import config
239 from curtin import block
240+from curtin import net
241 from curtin import futil
242 from curtin.log import LOG
243 from curtin import swap
244@@ -65,6 +67,19 @@
245 }
246 }
247
248+CLOUD_INIT_YUM_REPO_TEMPLATE = """
249+[group_cloud-init-el-stable]
250+name=Copr repo for el-stable owned by @cloud-init
251+baseurl=https://copr-be.cloud.fedoraproject.org/results/@cloud-init/el-stable/epel-%s-$basearch/
252+type=rpm-md
253+skip_if_unavailable=True
254+gpgcheck=1
255+gpgkey=https://copr-be.cloud.fedoraproject.org/results/@cloud-init/el-stable/pubkey.gpg
256+repo_gpgcheck=0
257+enabled=1
258+enabled_metadata=1
259+"""
260+
261
262 def write_files(cfg, target):
263 # this takes 'write_files' entry in config and writes files in the target
264@@ -648,6 +663,38 @@
265 update_initramfs(target, all_kernels=True)
266
267
268+def detect_required_packages(cfg):
269+ """
270+ detect packages that will be required in-target by custom config items
271+ """
272+
273+ mapping = {
274+ 'storage': block.detect_required_packages_mapping(),
275+ 'network': net.detect_required_packages_mapping(),
276+ }
277+
278+ needed_packages = []
279+ for cfg_type, cfg_map in mapping.items():
280+
281+ # skip missing or invalid config items, configs may
282+ # only have network or storage, not always both
283+ if not isinstance(cfg.get(cfg_type), dict):
284+ continue
285+
286+ cfg_version = cfg[cfg_type].get('version')
287+ if not isinstance(cfg_version, int) or cfg_version not in cfg_map:
288+ msg = ('Supplied configuration version "%s", for config type'
289+ '"%s" is not present in the known mapping.' % (cfg_version,
290+ cfg_type))
291+ raise ValueError(msg)
292+
293+ mapped_config = cfg_map[cfg_version]
294+ found_reqs = mapped_config['handler'](cfg, mapped_config['mapping'])
295+ needed_packages.extend(found_reqs)
296+
297+ return needed_packages
298+
299+
300 def install_missing_packages(cfg, target):
301 ''' describe which operation types will require specific packages
302
303@@ -655,46 +702,10 @@
304 'pkg1': ['op_name_1', 'op_name_2', ...]
305 }
306 '''
307- custom_configs = {
308- 'storage': {
309- 'lvm2': ['lvm_volgroup', 'lvm_partition'],
310- 'mdadm': ['raid'],
311- 'bcache-tools': ['bcache']},
312- 'network': {
313- 'vlan': ['vlan'],
314- 'ifenslave': ['bond'],
315- 'bridge-utils': ['bridge']},
316- }
317-
318- format_configs = {
319- 'xfsprogs': ['xfs'],
320- 'e2fsprogs': ['ext2', 'ext3', 'ext4'],
321- 'btrfs-tools': ['btrfs'],
322- }
323-
324- needed_packages = []
325+
326 installed_packages = util.get_installed_packages(target)
327- for cust_cfg, pkg_reqs in custom_configs.items():
328- if cust_cfg not in cfg:
329- continue
330-
331- all_types = set(
332- operation['type']
333- for operation in cfg[cust_cfg]['config']
334- )
335- for pkg, types in pkg_reqs.items():
336- if set(types).intersection(all_types) and \
337- pkg not in installed_packages:
338- needed_packages.append(pkg)
339-
340- format_types = set(
341- [operation['fstype']
342- for operation in cfg[cust_cfg]['config']
343- if operation['type'] == 'format'])
344- for pkg, fstypes in format_configs.items():
345- if set(fstypes).intersection(format_types) and \
346- pkg not in installed_packages:
347- needed_packages.append(pkg)
348+ needed_packages = [pkg for pkg in detect_required_packages(cfg)
349+ if pkg not in installed_packages]
350
351 arch_packages = {
352 's390x': [('s390-tools', 'zipl')],
353@@ -801,11 +812,87 @@
354 if netconfig:
355 LOG.info('Writing network configuration')
356 ubuntu_core_netconfig = os.path.join(cc_target,
357- "50-network-config.cfg")
358+ "50-curtin-networking.cfg")
359 util.write_file(ubuntu_core_netconfig,
360 content=config.dump_config({'network': netconfig}))
361
362
363+def rpm_get_dist_id(target):
364+ """Use rpm command to extract the '%rhel' distro macro which returns
365+ the major os version id (6, 7, 8). This works for centos or rhel
366+ """
367+ with util.ChrootableTarget(target) as in_chroot:
368+ dist, _ = in_chroot.subp(['rpm', '-E', '%rhel'], capture=True)
369+ return dist.rstrip()
370+
371+
372+def centos_network_curthooks(cfg, target=None):
373+ """ CentOS images execute built-in curthooks which only supports
374+ simple networking configuration. This hook enables advanced
375+ network configuration via config passthrough to the target.
376+ """
377+ def cloud_init_repo(version):
378+ if not version:
379+ raise ValueError('Missing required version parameter')
380+
381+ return CLOUD_INIT_YUM_REPO_TEMPLATE % version
382+
383+ cloudconfig = cfg.get('cloudconfig', None)
384+ if cloudconfig:
385+ cc_target = util.target_path(target, 'etc/cloud/cloud.cfg.d')
386+ handle_cloudconfig(cloudconfig, target=cc_target)
387+
388+ netcfg = cfg.get('network', None)
389+ if netcfg:
390+ LOG.info('Removing embedded network configuration (if present)')
391+ ifcfgs = glob.glob(util.target_path(target,
392+ 'etc/sysconfig/network-scripts') +
393+ '/ifcfg-*')
394+ # remove ifcfg-* (except ifcfg-lo)
395+ for ifcfg in ifcfgs:
396+ if os.path.basename(ifcfg) != "ifcfg-lo":
397+ util.del_file(ifcfg)
398+
399+ LOG.info('Checking cloud-init in target [%s] for network '
400+ 'configuration passthrough support.', target)
401+ passthrough = net.netconfig_passthrough_available(target)
402+ LOG.debug('passthrough available via in-target: %s', passthrough)
403+
404+ # if in-target cloud-init is not updated, upgrade via cloud-init repo
405+ if not passthrough:
406+ cloud_init_yum_repo = (
407+ util.target_path(target,
408+ 'etc/yum.repos.d/curtin-cloud-init.repo'))
409+ # Inject cloud-init daily yum repo
410+ util.write_file(cloud_init_yum_repo,
411+ content=cloud_init_repo(rpm_get_dist_id(target)))
412+
413+ # we separate the installation of epel from cloud-init as
414+ # cloud-init will depend on packages included in epel and yum needs
415+ # to add the repository before we can install cloud-init
416+ with util.ChrootableTarget(target) as in_chroot:
417+ in_chroot.subp(['yum', '-y', 'install', 'epel-release'])
418+ in_chroot.subp(['yum', '-y', 'install',
419+ 'cloud-init-el-release'])
420+ in_chroot.subp(['yum', '-y', 'install', 'cloud-init'])
421+
422+ # remove cloud-init el-stable bootstrap repo config as the
423+ # cloud-init-el-release package points to the correct repo
424+ util.del_file(cloud_init_yum_repo)
425+
426+ # install bridge-utils if needed
427+ with util.ChrootableTarget(target) as in_chroot:
428+ try:
429+ in_chroot.subp(['rpm', '-q', 'bridge-utils'],
430+ capture=False, rcs=[0])
431+ except util.ProcessExecutionError:
432+ LOG.debug('Image missing bridge-utils package, installing')
433+ in_chroot.subp(['yum', '-y', 'install', 'bridge-utils'])
434+
435+ LOG.info('Passing network configuration through to target')
436+ net.render_netconfig_passthrough(target, netconfig={'network': netcfg})
437+
438+
439 def target_is_ubuntu_core(target):
440 """Check if Ubuntu-Core specific directory is present at target"""
441 if target:
442@@ -814,6 +901,22 @@
443 return False
444
445
446+def target_is_centos(target):
447+ """Check if CentOS specific file is present at target"""
448+ if target:
449+ return os.path.exists(util.target_path(target, 'etc/centos-release'))
450+
451+ return False
452+
453+
454+def target_is_rhel(target):
455+ """Check if RHEL specific file is present at target"""
456+ if target:
457+ return os.path.exists(util.target_path(target, 'etc/redhat-release'))
458+
459+ return False
460+
461+
462 def curthooks(args):
463 state = util.load_command_environment()
464
465@@ -827,14 +930,22 @@
466 "Use --target or set TARGET_MOUNT_POINT\n")
467 sys.exit(2)
468
469- # if network-config hook exists in target,
470- # we do not run the builtin
471- if util.run_hook_if_exists(target, 'curtin-hooks'):
472- sys.exit(0)
473-
474 cfg = config.load_command_config(args, state)
475 stack_prefix = state.get('report_stack_prefix', '')
476
477+ # if curtin-hooks hook exists in target we can defer to the in-target hooks
478+ if util.run_hook_if_exists(target, 'curtin-hooks'):
479+ # XXX: For vmtest use only
480+ if cfg.get('override_centos_curthooks', {}):
481+ if target_is_centos(target) or target_is_rhel(target):
482+ LOG.info('Detected RHEL/CentOS image, running extra hooks')
483+ with events.ReportEventStack(
484+ name=stack_prefix, reporting_enabled=True,
485+ level="INFO",
486+ description="Configuring CentOS for first boot"):
487+ centos_network_curthooks(cfg, target)
488+ sys.exit(0)
489+
490 if target_is_ubuntu_core(target):
491 LOG.info('Detected Ubuntu-Core image, running hooks')
492 with events.ReportEventStack(
493@@ -852,7 +963,11 @@
494 disable_overlayroot(cfg, target)
495
496 # packages may be needed prior to installing kernel
497- install_missing_packages(cfg, target)
498+ with events.ReportEventStack(
499+ name=stack_prefix + '/installing-missing-packages',
500+ reporting_enabled=True, level="INFO",
501+ description="installing missing packages"):
502+ install_missing_packages(cfg, target)
503
504 # If a /etc/iscsi/nodes/... file was created by block_meta then it
505 # needs to be copied onto the target system
506@@ -880,7 +995,6 @@
507 setup_zipl(cfg, target)
508 install_kernel(cfg, target)
509 run_zipl(cfg, target)
510-
511 restore_dist_interfaces(cfg, target)
512
513 with events.ReportEventStack(
514@@ -908,12 +1022,6 @@
515 detect_and_handle_multipath(cfg, target)
516
517 with events.ReportEventStack(
518- name=stack_prefix + '/installing-missing-packages',
519- reporting_enabled=True, level="INFO",
520- description="installing missing packages"):
521- install_missing_packages(cfg, target)
522-
523- with events.ReportEventStack(
524 name=stack_prefix + '/system-upgrade',
525 reporting_enabled=True, level="INFO",
526 description="updating packages on target system"):
527
528=== modified file 'curtin/net/__init__.py'
529--- curtin/net/__init__.py 2017-02-06 20:58:37 +0000
530+++ curtin/net/__init__.py 2017-07-27 13:56:24 +0000
531@@ -520,7 +520,52 @@
532 return content
533
534
535+def netconfig_passthrough_available(target, feature='NETWORK_CONFIG_V2'):
536+ """
537+ Determine if curtin can pass v2 network config to in target cloud-init
538+ """
539+ LOG.debug('Checking in-target cloud-init for feature: %s', feature)
540+ with util.ChrootableTarget(target) as in_chroot:
541+
542+ cloudinit = util.which('cloud-init', target=target)
543+ if not cloudinit:
544+ LOG.warning('Target does not have cloud-init installed')
545+ return False
546+
547+ available = False
548+ try:
549+ out, _ = in_chroot.subp([cloudinit, 'features'], capture=True)
550+ available = feature in out.splitlines()
551+ except util.ProcessExecutionError:
552+ # we explicitly don't dump the exception as this triggers
553+ # vmtest failures when parsing the installation log file
554+ LOG.warning("Failed to probe cloudinit features")
555+ return False
556+
557+ LOG.debug('cloud-init feature %s available? %s', feature, available)
558+ return available
559+
560+
561+def render_netconfig_passthrough(target, netconfig=None):
562+ """
563+ Extract original network config and pass it
564+ through to cloud-init in target
565+ """
566+ cc = 'etc/cloud/cloud.cfg.d/50-curtin-networking.cfg'
567+ if not isinstance(netconfig, dict):
568+ raise ValueError('Network config must be a dictionary')
569+
570+ if 'network' not in netconfig:
571+ raise ValueError("Network config must contain the key 'network'")
572+
573+ content = config.dump_config(netconfig)
574+ cc_passthrough = os.path.sep.join((target, cc,))
575+ LOG.info('Writing network config to %s: %s', cc, cc_passthrough)
576+ util.write_file(cc_passthrough, content=content)
577+
578+
579 def render_network_state(target, network_state):
580+ LOG.debug("rendering eni from netconfig")
581 eni = 'etc/network/interfaces'
582 netrules = 'etc/udev/rules.d/70-persistent-net.rules'
583 cc = 'etc/cloud/cloud.cfg.d/curtin-disable-cloudinit-networking.cfg'
584@@ -542,4 +587,65 @@
585 """Returns the string value of an interface's MAC Address"""
586 return read_sys_net(ifname, "address", enoent=False)
587
588+
589+def network_config_required_packages(network_config, mapping=None):
590+
591+ if network_config is None:
592+ network_config = {}
593+
594+ if not isinstance(network_config, dict):
595+ raise ValueError('Invalid network configuration. Must be a dict')
596+
597+ if mapping is None:
598+ mapping = {}
599+
600+ if not isinstance(mapping, dict):
601+ raise ValueError('Invalid network mapping. Must be a dict')
602+
603+ # allow top-level 'network' key
604+ if 'network' in network_config:
605+ network_config = network_config.get('network')
606+
607+ # v1 has 'config' key and uses type: devtype elements
608+ if 'config' in network_config:
609+ dev_configs = set(device['type']
610+ for device in network_config['config'])
611+ else:
612+ # v2 has no config key
613+ dev_configs = set(cfgtype for (cfgtype, cfg) in
614+ network_config.items() if cfgtype not in ['version'])
615+
616+ needed_packages = []
617+ for dev_type in dev_configs:
618+ if dev_type in mapping:
619+ needed_packages.extend(mapping[dev_type])
620+
621+ return needed_packages
622+
623+
624+def detect_required_packages_mapping():
625+ """Return a dictionary providing a versioned configuration which maps
626+ network configuration elements to the packages which are required
627+ for functionality.
628+ """
629+ mapping = {
630+ 1: {
631+ 'handler': network_config_required_packages,
632+ 'mapping': {
633+ 'bond': ['ifenslave'],
634+ 'bridge': ['bridge-utils'],
635+ 'vlan': ['vlan']},
636+ },
637+ 2: {
638+ 'handler': network_config_required_packages,
639+ 'mapping': {
640+ 'bonds': ['ifenslave'],
641+ 'bridges': ['bridge-utils'],
642+ 'vlans': ['vlan']}
643+ },
644+ }
645+
646+ return mapping
647+
648+
649 # vi: ts=4 expandtab syntax=python
650
651=== modified file 'curtin/util.py'
652--- curtin/util.py 2017-06-05 02:55:31 +0000
653+++ curtin/util.py 2017-07-27 13:56:24 +0000
654@@ -23,6 +23,7 @@
655 import os
656 import platform
657 import re
658+import shlex
659 import shutil
660 import socket
661 import subprocess
662@@ -1347,6 +1348,9 @@
663 if not path:
664 return target
665
666+ if not isinstance(path, string_types):
667+ raise ValueError("Unexpected input for path: %s" % path)
668+
669 # os.path.join("/etc", "/foo") returns "/foo". Chomp all leading /.
670 while len(path) and path[0] == "/":
671 path = path[1:]
672@@ -1362,4 +1366,38 @@
673 __call__ = ChrootableTarget.subp
674
675
676+def shlex_split(str_in):
677+ # shlex.split takes a string
678+ # but in python2 if input here is a unicode, encode it to a string.
679+ # http://stackoverflow.com/questions/2365411/
680+ # python-convert-unicode-to-ascii-without-errors
681+ if sys.version_info.major == 2:
682+ try:
683+ if isinstance(str_in, unicode):
684+ str_in = str_in.encode('utf-8')
685+ except NameError:
686+ pass
687+
688+ return shlex.split(str_in)
689+ else:
690+ return shlex.split(str_in)
691+
692+
693+def load_shell_content(content, add_empty=False, empty_val=None):
694+ """Given shell like syntax (key=value\nkey2=value2\n) in content
695+ return the data in dictionary form. If 'add_empty' is True
696+ then add entries in to the returned dictionary for 'VAR='
697+ variables. Set their value to empty_val."""
698+
699+ data = {}
700+ for line in shlex_split(content):
701+ key, value = line.split("=", 1)
702+ if not value:
703+ value = empty_val
704+ if add_empty or value:
705+ data[key] = value
706+
707+ return data
708+
709+
710 # vi: ts=4 expandtab syntax=python
711
712=== modified file 'examples/network-ipv6-bond-vlan.yaml'
713--- examples/network-ipv6-bond-vlan.yaml 2016-08-12 17:24:13 +0000
714+++ examples/network-ipv6-bond-vlan.yaml 2017-07-27 13:56:24 +0000
715@@ -3,10 +3,10 @@
716 config:
717 - name: interface0
718 type: physical
719- mac_address: BC:76:4E:06:96:B3
720+ mac_address: bc:76:4e:06:96:b3
721 - name: interface1
722 type: physical
723- mac_address: BC:76:4E:04:88:41
724+ mac_address: bc:76:4e:04:88:41
725 - type: bond
726 bond_interfaces:
727 - interface0
728
729=== modified file 'examples/tests/bonding_network.yaml'
730--- examples/tests/bonding_network.yaml 2016-06-03 19:53:18 +0000
731+++ examples/tests/bonding_network.yaml 2017-07-27 13:56:24 +0000
732@@ -16,8 +16,7 @@
733 mac_address: "52:54:00:12:34:04"
734 # Bond.
735 - type: bond
736- name: bond0
737- mac_address: "52:54:00:12:34:06"
738+ name: bond1
739 bond_interfaces:
740 - interface1
741 - interface2
742@@ -26,8 +25,6 @@
743 subnets:
744 - type: static
745 address: 10.23.23.2/24
746- - type: static
747- address: 10.23.24.2/24
748
749 curthooks_commands:
750 # use curtin to disable open-iscsi ifupdown hooks for precise; they're
751
752=== modified file 'examples/tests/centos_basic.yaml'
753--- examples/tests/centos_basic.yaml 2016-10-03 05:06:16 +0000
754+++ examples/tests/centos_basic.yaml 2017-07-27 13:56:24 +0000
755@@ -9,5 +9,6 @@
756 mac_address: "52:54:00:12:34:00"
757 subnets:
758 - type: static
759- address: 10.0.2.15/24
760+ address: 10.0.2.15
761+ netmask: 255.255.255.0
762 gateway: 10.0.2.2
763
764=== added file 'examples/tests/centos_defaults.yaml'
765--- examples/tests/centos_defaults.yaml 1970-01-01 00:00:00 +0000
766+++ examples/tests/centos_defaults.yaml 2017-07-27 13:56:24 +0000
767@@ -0,0 +1,26 @@
768+hook_commands:
769+ builtin: null
770+
771+# Force curtin to run centos_network_curthooks for vmtest
772+override_centos_curthooks: True
773+
774+late_commands:
775+
776+# centos66 images include grub 0.97 which will detect vmtests ephemeral disk
777+# and the install disk which leaves grub configured with two disks. When
778+# vmtest reboots into installed disk, there is only one disk and the grub
779+# map is no longer valid. Here in 00_grub, we switch hd1 to hd0. MAAS
780+# is not affected as their ephemeral image (iscsi or http) is not discovered
781+# by grub and therefor the device.map doesn't contain a second device. Cent7
782+# has grub2 which uses root by UUID
783+#
784+ 00_grub: [curtin, in-target, --, sed, -i.curtin, -e, 's|(hd1,0)|(hd0,0)|g',
785+ -e, '$a serial --unit=0 --speed=115200\nterminal --timeout=5 serial console',
786+ /boot/grub/grub.conf]
787+
788+# vmtest requires a serial console output
789+ 01_serial: [curtin, in-target, --, sh, -c,
790+ "echo 'GRUB_CMDLINE_LINUX_DEFAULT=\"console=tty0 crashkernel=auto
791+ console=ttyS0,115200\" >> /etc/default/grub';
792+ if command -v grub2-mkconfig &>/dev/null; then
793+ grub2-mkconfig -o /boot/grub2/grub.cfg; fi;"]
794
795=== modified file 'examples/tests/network_alias.yaml'
796--- examples/tests/network_alias.yaml 2016-08-25 23:09:51 +0000
797+++ examples/tests/network_alias.yaml 2017-07-27 13:56:24 +0000
798@@ -8,7 +8,7 @@
799 mac_address: "52:54:00:12:34:00"
800 subnets:
801 - type: static
802- address: 192.168.1.2/24
803+ address: 10.47.98.1/24
804 mtu: 1501
805 - type: static
806 address: 2001:4800:78ff:1b:be76:4eff:fe06:ffac
807@@ -20,17 +20,17 @@
808 mac_address: "52:54:00:12:34:02"
809 subnets:
810 - type: static
811- address: 192.168.2.2/22
812+ address: 192.168.20.2/24
813 routes:
814- - network: 192.168.0.0
815- netmask: 255.255.252.0
816- gateway: 192.168.2.1
817+ - gateway: 192.168.20.1
818+ netmask: 255.255.255.0
819+ network: 10.242.47.0
820 - type: static
821- address: 10.23.23.7/23
822+ address: 10.23.22.7/23
823 routes:
824- - gateway: 10.23.23.1
825- netmask: 255.255.254.0
826- network: 10.23.22.0
827+ - gateway: 10.23.22.2
828+ netmask: 255.255.255.0
829+ network: 10.49.253.0
830 # multi_v6_alias: multiple v6 addrs on same interface
831 - type: physical
832 name: interface2
833@@ -51,17 +51,17 @@
834 mac_address: "52:54:00:12:34:06"
835 subnets:
836 - type: static
837- address: 192.168.7.7/22
838+ address: 192.168.80.8/24
839 routes:
840- - network: 192.168.0.0
841- netmask: 255.255.252.0
842- gateway: 192.168.7.1
843+ - gateway: 192.168.80.1
844+ netmask: 255.255.255.0
845+ network: 10.189.34.0
846 - type: static
847- address: 10.99.99.23/23
848+ address: 10.99.10.23/23
849 routes:
850- - gateway: 10.99.99.1
851- netmask: 255.255.254.0
852- network: 10.99.98.0
853+ - gateway: 10.99.10.1
854+ netmask: 255.255.255.0
855+ network: 10.77.154.0
856 - type: static
857 address: 2001:4800:78ff:1b:be76:4eff:beef:4000
858 netmask: 'ffff:ffff:ffff:ffff::'
859@@ -86,17 +86,17 @@
860 address: 2001:4800:78ff:1b:be76:4eff:debe:9000
861 netmask: 'ffff:ffff:ffff:ffff::'
862 - type: static
863- address: 192.168.100.100/22
864+ address: 192.168.100.100/24
865 routes:
866- - network: 192.168.0.0
867- netmask: 255.255.252.0
868- gateway: 192.168.100.1
869+ - gateway: 192.168.100.1
870+ netmask: 255.255.255.0
871+ network: 10.28.219.0
872 - type: static
873 address: 10.17.142.2/23
874 routes:
875 - gateway: 10.17.142.1
876- netmask: 255.255.254.0
877- network: 10.17.142.0
878+ netmask: 255.255.255.0
879+ network: 10.82.49.0
880 # multi_v6_and_v4_mix_order: multiple v4 and v6 addr, mixed order
881 - type: physical
882 name: interface5
883@@ -109,17 +109,17 @@
884 address: 2001:4800:78ff:1b:be76:4eff:baaf:c000
885 netmask: 'ffff:ffff:ffff:ffff::'
886 - type: static
887- address: 192.168.200.200/22
888+ address: 192.168.200.200/24
889 routes:
890- - network: 192.168.0.0
891- netmask: 255.255.252.0
892- gateway: 192.168.200.1
893+ - gateway: 192.168.200.1
894+ netmask: 255.255.255.0
895+ network: 10.71.23.0
896 - type: static
897 address: 10.252.2.2/23
898 routes:
899 - gateway: 10.252.2.1
900- netmask: 255.255.254.0
901- network: 10.252.2.0
902+ netmask: 255.255.255.0
903+ network: 10.3.7.0
904 - type: static
905 address: 2001:4800:78ff:1b:be76:4eff:baaf:b000
906 netmask: 'ffff:ffff:ffff:ffff::'
907
908=== added file 'examples/tests/network_v2_passthrough.yaml'
909--- examples/tests/network_v2_passthrough.yaml 1970-01-01 00:00:00 +0000
910+++ examples/tests/network_v2_passthrough.yaml 2017-07-27 13:56:24 +0000
911@@ -0,0 +1,8 @@
912+showtrace: true
913+network:
914+ version: 2
915+ ethernets:
916+ interface0:
917+ match:
918+ mac_address: "52:54:00:12:34:00"
919+ dhcp4: true
920
921=== added file 'tests/unittests/test_commands_apply_net.py'
922--- tests/unittests/test_commands_apply_net.py 1970-01-01 00:00:00 +0000
923+++ tests/unittests/test_commands_apply_net.py 2017-07-27 13:56:24 +0000
924@@ -0,0 +1,346 @@
925+from unittest import TestCase
926+from mock import patch, call
927+import copy
928+import os
929+
930+from curtin.commands import apply_net
931+from curtin import util
932+
933+
934+class ApplyNetTestBase(TestCase):
935+ def setUp(self):
936+ super(ApplyNetTestBase, self).setUp()
937+
938+ def add_patch(self, target, attr):
939+ """Patches specified target object and sets it as attr on test
940+ instance also schedules cleanup"""
941+ m = patch(target, autospec=True)
942+ p = m.start()
943+ self.addCleanup(m.stop)
944+ setattr(self, attr, p)
945+
946+
947+class TestApplyNet(ApplyNetTestBase):
948+ def setUp(self):
949+ super(TestApplyNet, self).setUp()
950+
951+ base = 'curtin.commands.apply_net.'
952+ patches = [
953+ (base + '_maybe_remove_legacy_eth0', 'm_legacy'),
954+ (base + '_disable_ipv6_privacy_extensions', 'm_ipv6_priv'),
955+ (base + '_patch_ifupdown_ipv6_mtu_hook', 'm_ipv6_mtu'),
956+ ('curtin.net.netconfig_passthrough_available', 'm_netpass_avail'),
957+ ('curtin.net.render_netconfig_passthrough', 'm_netpass_render'),
958+ ('curtin.net.parse_net_config_data', 'm_net_parsedata'),
959+ ('curtin.net.render_network_state', 'm_net_renderstate'),
960+ ('curtin.net.network_state.from_state_file', 'm_ns_from_file'),
961+ ('curtin.config.load_config', 'm_load_config'),
962+ ]
963+ for (tgt, attr) in patches:
964+ self.add_patch(tgt, attr)
965+
966+ self.target = "my_target"
967+ self.network_config = {
968+ 'network': {
969+ 'version': 1,
970+ 'config': {},
971+ }
972+ }
973+ self.ns = {
974+ 'interfaces': {},
975+ 'routes': [],
976+ 'dns': {
977+ 'nameservers': [],
978+ 'search': [],
979+ }
980+ }
981+
982+ def test_apply_net_notarget(self):
983+ self.assertRaises(Exception,
984+ apply_net.apply_net, None, "", "")
985+
986+ def test_apply_net_nostate_or_config(self):
987+ self.assertRaises(Exception,
988+ apply_net.apply_net, "")
989+
990+ def test_apply_net_target_and_state(self):
991+ self.m_ns_from_file.return_value = self.ns
992+
993+ self.assertRaises(ValueError,
994+ apply_net.apply_net, self.target,
995+ network_state=self.ns, network_config=None)
996+
997+ def test_apply_net_target_and_config(self):
998+ self.m_load_config.return_value = self.network_config
999+ self.m_netpass_avail.return_value = False
1000+ self.m_net_parsedata.return_value = self.ns
1001+
1002+ apply_net.apply_net(self.target, network_state=None,
1003+ network_config=self.network_config)
1004+
1005+ self.m_netpass_avail.assert_called_with(self.target)
1006+
1007+ self.m_net_renderstate.assert_called_with(target=self.target,
1008+ network_state=self.ns)
1009+ self.m_legacy.assert_called_with(self.target)
1010+ self.m_ipv6_priv.assert_called_with(self.target)
1011+ self.m_ipv6_mtu.assert_called_with(self.target)
1012+
1013+ def test_apply_net_target_and_config_passthrough(self):
1014+ self.m_load_config.return_value = self.network_config
1015+ self.m_netpass_avail.return_value = True
1016+
1017+ netcfg = "network_config.yaml"
1018+ apply_net.apply_net(self.target, network_state=None,
1019+ network_config=netcfg)
1020+
1021+ self.assertFalse(self.m_ns_from_file.called)
1022+ self.m_load_config.assert_called_with(netcfg)
1023+ self.m_netpass_avail.assert_called_with(self.target)
1024+ nc = self.network_config
1025+ self.m_netpass_render.assert_called_with(self.target, netconfig=nc)
1026+
1027+ self.assertFalse(self.m_net_renderstate.called)
1028+ self.m_legacy.assert_called_with(self.target)
1029+ self.m_ipv6_priv.assert_called_with(self.target)
1030+ self.m_ipv6_mtu.assert_called_with(self.target)
1031+
1032+ def test_apply_net_target_and_config_passthrough_nonet(self):
1033+ nc = {'storage': {}}
1034+ self.m_load_config.return_value = nc
1035+ self.m_netpass_avail.return_value = True
1036+
1037+ netcfg = "network_config.yaml"
1038+
1039+ apply_net.apply_net(self.target, network_state=None,
1040+ network_config=netcfg)
1041+
1042+ self.assertFalse(self.m_ns_from_file.called)
1043+ self.m_load_config.assert_called_with(netcfg)
1044+ self.m_netpass_avail.assert_called_with(self.target)
1045+ self.m_netpass_render.assert_called_with(self.target, netconfig=nc)
1046+
1047+ self.assertFalse(self.m_net_renderstate.called)
1048+ self.m_legacy.assert_called_with(self.target)
1049+ self.m_ipv6_priv.assert_called_with(self.target)
1050+ self.m_ipv6_mtu.assert_called_with(self.target)
1051+
1052+ def test_apply_net_target_and_config_passthrough_v2_not_available(self):
1053+ nc = copy.deepcopy(self.network_config)
1054+ nc['network']['version'] = 2
1055+ self.m_load_config.return_value = nc
1056+ self.m_netpass_avail.return_value = False
1057+ self.m_net_parsedata.return_value = self.ns
1058+
1059+ netcfg = "network_config.yaml"
1060+
1061+ apply_net.apply_net(self.target, network_state=None,
1062+ network_config=netcfg)
1063+
1064+ self.assertFalse(self.m_ns_from_file.called)
1065+ self.m_load_config.assert_called_with(netcfg)
1066+ self.m_netpass_avail.assert_called_with(self.target)
1067+ self.assertFalse(self.m_netpass_render.called)
1068+ self.m_net_parsedata.assert_called_with(nc['network'])
1069+
1070+ self.m_net_renderstate.assert_called_with(
1071+ target=self.target, network_state=self.ns)
1072+ self.m_legacy.assert_called_with(self.target)
1073+ self.m_ipv6_priv.assert_called_with(self.target)
1074+ self.m_ipv6_mtu.assert_called_with(self.target)
1075+
1076+
1077+class TestApplyNetPatchIfupdown(ApplyNetTestBase):
1078+
1079+ @patch('curtin.util.write_file')
1080+ def test_apply_ipv6_mtu_hook(self, mock_write):
1081+ target = 'mytarget'
1082+ prehookfn = 'if-pre-up.d/mtuipv6'
1083+ posthookfn = 'if-up.d/mtuipv6'
1084+ mode = 0o755
1085+
1086+ apply_net._patch_ifupdown_ipv6_mtu_hook(target,
1087+ prehookfn=prehookfn,
1088+ posthookfn=posthookfn)
1089+
1090+ precfg = util.target_path(target, path=prehookfn)
1091+ postcfg = util.target_path(target, path=posthookfn)
1092+ precontents = apply_net.IFUPDOWN_IPV6_MTU_PRE_HOOK
1093+ postcontents = apply_net.IFUPDOWN_IPV6_MTU_POST_HOOK
1094+
1095+ hook_calls = [
1096+ call(precfg, precontents, mode=mode),
1097+ call(postcfg, postcontents, mode=mode),
1098+ ]
1099+ mock_write.assert_has_calls(hook_calls)
1100+
1101+ @patch('curtin.util.write_file')
1102+ def test_apply_ipv6_mtu_hook_write_fail(self, mock_write):
1103+ """Write failure raises IOError"""
1104+ target = 'mytarget'
1105+ prehookfn = 'if-pre-up.d/mtuipv6'
1106+ posthookfn = 'if-up.d/mtuipv6'
1107+ mock_write.side_effect = (IOError)
1108+
1109+ self.assertRaises(IOError,
1110+ apply_net._patch_ifupdown_ipv6_mtu_hook,
1111+ target,
1112+ prehookfn=prehookfn,
1113+ posthookfn=posthookfn)
1114+ self.assertEqual(1, mock_write.call_count)
1115+
1116+ @patch('curtin.util.write_file')
1117+ def test_apply_ipv6_mtu_hook_invalid_target(self, mock_write):
1118+ """Invalid target path fail before calling util.write_file"""
1119+ invalid_target = {}
1120+ prehookfn = 'if-pre-up.d/mtuipv6'
1121+ posthookfn = 'if-up.d/mtuipv6'
1122+
1123+ self.assertRaises(ValueError,
1124+ apply_net._patch_ifupdown_ipv6_mtu_hook,
1125+ invalid_target,
1126+ prehookfn=prehookfn,
1127+ posthookfn=posthookfn)
1128+ self.assertEqual(0, mock_write.call_count)
1129+
1130+ @patch('curtin.util.write_file')
1131+ def test_apply_ipv6_mtu_hook_invalid_prepost_fn(self, mock_write):
1132+ """Invalid prepost filenames fail before calling util.write_file"""
1133+ target = "mytarget"
1134+ invalid_prehookfn = {'a': 1}
1135+ invalid_posthookfn = {'b': 2}
1136+
1137+ self.assertRaises(ValueError,
1138+ apply_net._patch_ifupdown_ipv6_mtu_hook,
1139+ target,
1140+ prehookfn=invalid_prehookfn,
1141+ posthookfn=invalid_posthookfn)
1142+ self.assertEqual(0, mock_write.call_count)
1143+
1144+
1145+class TestApplyNetPatchIpv6Priv(ApplyNetTestBase):
1146+
1147+ @patch('curtin.util.del_file')
1148+ @patch('curtin.util.load_file')
1149+ @patch('os.path')
1150+ @patch('curtin.util.write_file')
1151+ def test_disable_ipv6_priv_extentions(self, mock_write, mock_ospath,
1152+ mock_load, mock_del):
1153+ target = 'mytarget'
1154+ path = 'etc/sysctl.d/10-ipv6-privacy.conf'
1155+ ipv6_priv_contents = (
1156+ 'net.ipv6.conf.all.use_tempaddr = 2\n'
1157+ 'net.ipv6.conf.default.use_tempaddr = 2')
1158+ expected_ipv6_priv_contents = '\n'.join(
1159+ ["# IPv6 Privacy Extensions (RFC 4941)",
1160+ "# Disabled by curtin",
1161+ "# net.ipv6.conf.all.use_tempaddr = 2",
1162+ "# net.ipv6.conf.default.use_tempaddr = 2"])
1163+ mock_ospath.exists.return_value = True
1164+ mock_load.side_effect = [ipv6_priv_contents]
1165+
1166+ apply_net._disable_ipv6_privacy_extensions(target)
1167+
1168+ cfg = util.target_path(target, path=path)
1169+ mock_write.assert_called_with(cfg, expected_ipv6_priv_contents)
1170+
1171+ @patch('curtin.util.load_file')
1172+ @patch('os.path')
1173+ def test_disable_ipv6_priv_extentions_decoderror(self, mock_ospath,
1174+ mock_load):
1175+ target = 'mytarget'
1176+ mock_ospath.exists.return_value = True
1177+
1178+ # simulate loading of binary data
1179+ mock_load.side_effect = (Exception)
1180+
1181+ self.assertRaises(Exception,
1182+ apply_net._disable_ipv6_privacy_extensions,
1183+ target)
1184+
1185+ @patch('curtin.util.load_file')
1186+ @patch('os.path')
1187+ def test_disable_ipv6_priv_extentions_notfound(self, mock_ospath,
1188+ mock_load):
1189+ target = 'mytarget'
1190+ path = 'foo.conf'
1191+ mock_ospath.exists.return_value = False
1192+
1193+ apply_net._disable_ipv6_privacy_extensions(target, path=path)
1194+
1195+ # source file not found
1196+ cfg = util.target_path(target, path)
1197+ mock_ospath.exists.assert_called_with(cfg)
1198+ self.assertEqual(0, mock_load.call_count)
1199+
1200+
1201+class TestApplyNetRemoveLegacyEth0(ApplyNetTestBase):
1202+
1203+ @patch('curtin.util.del_file')
1204+ @patch('curtin.util.load_file')
1205+ @patch('os.path')
1206+ def test_remove_legacy_eth0(self, mock_ospath, mock_load, mock_del):
1207+ target = 'mytarget'
1208+ path = 'eth0.cfg'
1209+ cfg = util.target_path(target, path)
1210+ legacy_eth0_contents = (
1211+ 'auto eth0\n'
1212+ 'iface eth0 inet dhcp')
1213+
1214+ mock_ospath.exists.return_value = True
1215+ mock_load.side_effect = [legacy_eth0_contents]
1216+
1217+ apply_net._maybe_remove_legacy_eth0(target, path)
1218+
1219+ mock_del.assert_called_with(cfg)
1220+
1221+ @patch('curtin.util.del_file')
1222+ @patch('curtin.util.load_file')
1223+ @patch('os.path')
1224+ def test_remove_legacy_eth0_nomatch(self, mock_ospath, mock_load,
1225+ mock_del):
1226+ target = 'mytarget'
1227+ path = 'eth0.cfg'
1228+ legacy_eth0_contents = "nomatch"
1229+ mock_ospath.join.side_effect = os.path.join
1230+ mock_ospath.exists.return_value = True
1231+ mock_load.side_effect = [legacy_eth0_contents]
1232+
1233+ self.assertRaises(Exception,
1234+ apply_net._maybe_remove_legacy_eth0,
1235+ target, path)
1236+
1237+ self.assertEqual(0, mock_del.call_count)
1238+
1239+ @patch('curtin.util.del_file')
1240+ @patch('curtin.util.load_file')
1241+ @patch('os.path')
1242+ def test_remove_legacy_eth0_badload(self, mock_ospath, mock_load,
1243+ mock_del):
1244+ target = 'mytarget'
1245+ path = 'eth0.cfg'
1246+ mock_ospath.exists.return_value = True
1247+ mock_load.side_effect = (Exception)
1248+
1249+ self.assertRaises(Exception,
1250+ apply_net._maybe_remove_legacy_eth0,
1251+ target, path)
1252+
1253+ self.assertEqual(0, mock_del.call_count)
1254+
1255+ @patch('curtin.util.del_file')
1256+ @patch('curtin.util.load_file')
1257+ @patch('os.path')
1258+ def test_remove_legacy_eth0_notfound(self, mock_ospath, mock_load,
1259+ mock_del):
1260+ target = 'mytarget'
1261+ path = 'eth0.conf'
1262+ mock_ospath.exists.return_value = False
1263+
1264+ apply_net._maybe_remove_legacy_eth0(target, path)
1265+
1266+ # source file not found
1267+ cfg = util.target_path(target, path)
1268+ mock_ospath.exists.assert_called_with(cfg)
1269+ self.assertEqual(0, mock_load.call_count)
1270+ self.assertEqual(0, mock_del.call_count)
1271
1272=== modified file 'tests/unittests/test_curthooks.py'
1273--- tests/unittests/test_curthooks.py 2017-05-12 14:35:53 +0000
1274+++ tests/unittests/test_curthooks.py 2017-07-27 13:56:24 +0000
1275@@ -541,7 +541,7 @@
1276 netcfg_path = os.path.join(self.target,
1277 'system-data',
1278 'etc/cloud/cloud.cfg.d',
1279- '50-network-config.cfg')
1280+ '50-curtin-networking.cfg')
1281 netcfg = config.dump_config({'network': cfg.get('network')})
1282 mock_write_file.assert_called_with(netcfg_path,
1283 content=netcfg)
1284@@ -577,4 +577,188 @@
1285 with self.assertRaises(ValueError):
1286 curthooks.handle_cloudconfig([], target="foobar")
1287
1288+
1289+class TestDetectRequiredPackages(TestCase):
1290+ test_config = {
1291+ 'storage': {
1292+ 1: {
1293+ 'bcache': {
1294+ 'type': 'bcache', 'name': 'bcache0', 'id': 'cache0',
1295+ 'backing_device': 'sda3', 'cache_device': 'sdb'},
1296+ 'lvm_partition': {
1297+ 'id': 'lvol1', 'name': 'lv1', 'volgroup': 'vg1',
1298+ 'type': 'lvm_partition'},
1299+ 'lvm_volgroup': {
1300+ 'id': 'vol1', 'name': 'vg1', 'devices': ['sda', 'sdb'],
1301+ 'type': 'lvm_volgroup'},
1302+ 'raid': {
1303+ 'id': 'mddevice', 'name': 'md0', 'type': 'raid',
1304+ 'raidlevel': 5, 'devices': ['sda1', 'sdb1', 'sdc1']},
1305+ 'ext2': {
1306+ 'id': 'format0', 'fstype': 'ext2', 'type': 'format'},
1307+ 'ext3': {
1308+ 'id': 'format1', 'fstype': 'ext3', 'type': 'format'},
1309+ 'ext4': {
1310+ 'id': 'format2', 'fstype': 'ext4', 'type': 'format'},
1311+ 'btrfs': {
1312+ 'id': 'format3', 'fstype': 'btrfs', 'type': 'format'},
1313+ 'xfs': {
1314+ 'id': 'format4', 'fstype': 'xfs', 'type': 'format'}}
1315+ },
1316+ 'network': {
1317+ 1: {
1318+ 'bond': {
1319+ 'name': 'bond0', 'type': 'bond',
1320+ 'bond_interfaces': ['interface0', 'interface1'],
1321+ 'params': {'bond-mode': 'active-backup'},
1322+ 'subnets': [
1323+ {'type': 'static', 'address': '10.23.23.2/24'},
1324+ {'type': 'static', 'address': '10.23.24.2/24'}]},
1325+ 'vlan': {
1326+ 'id': 'interface1.2667', 'mtu': 1500, 'name':
1327+ 'interface1.2667', 'type': 'vlan', 'vlan_id': 2667,
1328+ 'vlan_link': 'interface1',
1329+ 'subnets': [{'address': '10.245.184.2/24',
1330+ 'dns_nameservers': [], 'type': 'static'}]},
1331+ 'bridge': {
1332+ 'name': 'br0', 'bridge_interfaces': ['eth0', 'eth1'],
1333+ 'type': 'bridge', 'params': {
1334+ 'bridge_stp': 'off', 'bridge_fd': 0,
1335+ 'bridge_maxwait': 0},
1336+ 'subnets': [
1337+ {'type': 'static', 'address': '192.168.14.2/24'},
1338+ {'type': 'static', 'address': '2001:1::1/64'}]}},
1339+ 2: {
1340+ 'vlan': {
1341+ 'vlans': {
1342+ 'en-intra': {'id': 1, 'link': 'eno1', 'dhcp4': 'yes'},
1343+ 'en-vpn': {'id': 2, 'link': 'eno1'}}},
1344+ 'bridge': {
1345+ 'bridges': {
1346+ 'br0': {
1347+ 'interfaces': ['wlp1s0', 'switchports'],
1348+ 'dhcp4': True}}}}
1349+ },
1350+ }
1351+
1352+ def _fmt_config(self, config_items):
1353+ res = {}
1354+ for item, item_confs in config_items.items():
1355+ version = item_confs['version']
1356+ res[item] = {'version': version}
1357+ if version == 1:
1358+ res[item]['config'] = [self.test_config[item][version][i]
1359+ for i in item_confs['items']]
1360+ elif version == 2 and item == 'network':
1361+ for cfg_item in item_confs['items']:
1362+ res[item].update(self.test_config[item][version][cfg_item])
1363+ else:
1364+ raise NotImplementedError
1365+ return res
1366+
1367+ def _test_req_mappings(self, req_mappings):
1368+ for (config_items, expected_reqs) in req_mappings:
1369+ cfg = self._fmt_config(config_items)
1370+ actual_reqs = curthooks.detect_required_packages(cfg)
1371+ self.assertEqual(set(actual_reqs), set(expected_reqs),
1372+ 'failed for config: {}'.format(config_items))
1373+
1374+ def test_storage_v1_detect(self):
1375+ self._test_req_mappings((
1376+ ({'storage': {
1377+ 'version': 1,
1378+ 'items': ('lvm_partition', 'lvm_volgroup', 'btrfs', 'xfs')}},
1379+ ('lvm2', 'xfsprogs', 'btrfs-tools')),
1380+ ({'storage': {
1381+ 'version': 1,
1382+ 'items': ('raid', 'bcache', 'ext3', 'xfs')}},
1383+ ('mdadm', 'bcache-tools', 'e2fsprogs', 'xfsprogs')),
1384+ ({'storage': {
1385+ 'version': 1,
1386+ 'items': ('raid', 'lvm_volgroup', 'lvm_partition', 'ext3',
1387+ 'ext4', 'btrfs')}},
1388+ ('lvm2', 'mdadm', 'e2fsprogs', 'btrfs-tools')),
1389+ ({'storage': {
1390+ 'version': 1,
1391+ 'items': ('bcache', 'lvm_volgroup', 'lvm_partition', 'ext2')}},
1392+ ('bcache-tools', 'lvm2', 'e2fsprogs')),
1393+ ))
1394+
1395+ def test_network_v1_detect(self):
1396+ self._test_req_mappings((
1397+ ({'network': {
1398+ 'version': 1,
1399+ 'items': ('bridge',)}},
1400+ ('bridge-utils',)),
1401+ ({'network': {
1402+ 'version': 1,
1403+ 'items': ('vlan', 'bond')}},
1404+ ('vlan', 'ifenslave')),
1405+ ({'network': {
1406+ 'version': 1,
1407+ 'items': ('bond', 'bridge')}},
1408+ ('ifenslave', 'bridge-utils')),
1409+ ({'network': {
1410+ 'version': 1,
1411+ 'items': ('vlan', 'bridge', 'bond')}},
1412+ ('ifenslave', 'bridge-utils', 'vlan')),
1413+ ))
1414+
1415+ def test_mixed_v1_detect(self):
1416+ self._test_req_mappings((
1417+ ({'storage': {
1418+ 'version': 1,
1419+ 'items': ('raid', 'bcache', 'ext4')},
1420+ 'network': {
1421+ 'version': 1,
1422+ 'items': ('vlan',)}},
1423+ ('mdadm', 'bcache-tools', 'e2fsprogs', 'vlan')),
1424+ ({'storage': {
1425+ 'version': 1,
1426+ 'items': ('lvm_partition', 'lvm_volgroup', 'xfs')},
1427+ 'network': {
1428+ 'version': 1,
1429+ 'items': ('bridge', 'bond')}},
1430+ ('lvm2', 'xfsprogs', 'bridge-utils', 'ifenslave')),
1431+ ({'storage': {
1432+ 'version': 1,
1433+ 'items': ('ext3', 'ext4', 'btrfs')},
1434+ 'network': {
1435+ 'version': 1,
1436+ 'items': ('bond', 'vlan')}},
1437+ ('e2fsprogs', 'btrfs-tools', 'vlan', 'ifenslave')),
1438+ ))
1439+
1440+ def test_network_v2_detect(self):
1441+ self._test_req_mappings((
1442+ ({'network': {
1443+ 'version': 2,
1444+ 'items': ('bridge',)}},
1445+ ('bridge-utils',)),
1446+ ({'network': {
1447+ 'version': 2,
1448+ 'items': ('vlan',)}},
1449+ ('vlan',)),
1450+ ({'network': {
1451+ 'version': 2,
1452+ 'items': ('vlan', 'bridge')}},
1453+ ('vlan', 'bridge-utils')),
1454+ ))
1455+
1456+ def test_mixed_storage_v1_network_v2_detect(self):
1457+ self._test_req_mappings((
1458+ ({'network': {
1459+ 'version': 2,
1460+ 'items': ('bridge', 'vlan')},
1461+ 'storage': {
1462+ 'version': 1,
1463+ 'items': ('raid', 'bcache', 'ext4')}},
1464+ ('vlan', 'bridge-utils', 'mdadm', 'bcache-tools', 'e2fsprogs')),
1465+ ))
1466+
1467+ def test_invalid_version_in_config(self):
1468+ with self.assertRaises(ValueError):
1469+ curthooks.detect_required_packages({'network': {'version': 3}})
1470+
1471+
1472 # vi: ts=4 expandtab syntax=python
1473
1474=== modified file 'tests/unittests/test_feature.py'
1475--- tests/unittests/test_feature.py 2017-03-21 17:19:12 +0000
1476+++ tests/unittests/test_feature.py 2017-07-27 13:56:24 +0000
1477@@ -15,3 +15,6 @@
1478
1479 def test_has_reporting_events_webhook(self):
1480 self.assertIn('REPORTING_EVENTS_WEBHOOK', curtin.FEATURES)
1481+
1482+ def test_has_centos_network_curthooks(self):
1483+ self.assertIn('CENTOS_NETWORK_CURTHOOKS', curtin.FEATURES)
1484
1485=== modified file 'tests/unittests/test_net.py'
1486--- tests/unittests/test_net.py 2017-02-09 16:58:23 +0000
1487+++ tests/unittests/test_net.py 2017-07-27 13:56:24 +0000
1488@@ -1,10 +1,11 @@
1489 from unittest import TestCase
1490+import mock
1491 import os
1492 import shutil
1493 import tempfile
1494 import yaml
1495
1496-from curtin import net
1497+from curtin import config, net, util
1498 import curtin.net.network_state as network_state
1499 from textwrap import dedent
1500
1501@@ -503,24 +504,19 @@
1502 auto interface1
1503 iface interface1 inet manual
1504 bond-mode active-backup
1505- bond-master bond0
1506+ bond-master bond1
1507
1508 auto interface2
1509 iface interface2 inet manual
1510 bond-mode active-backup
1511- bond-master bond0
1512+ bond-master bond1
1513
1514- auto bond0
1515- iface bond0 inet static
1516+ auto bond1
1517+ iface bond1 inet static
1518 address 10.23.23.2/24
1519 bond-mode active-backup
1520- hwaddress ether 52:54:00:12:34:06
1521 bond-slaves none
1522
1523- # control-alias bond0
1524- iface bond0 inet static
1525- address 10.23.24.2/24
1526-
1527 source /etc/network/interfaces.d/*.cfg
1528 """)
1529 net_ifaces = net.render_interfaces(ns.network_state)
1530@@ -654,6 +650,91 @@
1531 self.assertEqual(sorted(ifaces.split('\n')),
1532 sorted(net_ifaces.split('\n')))
1533
1534+ @mock.patch('curtin.util.subp')
1535+ @mock.patch('curtin.util.which')
1536+ @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1537+ def test_netconfig_passthrough_available(self, mock_which, mock_subp):
1538+ cloud_init = '/usr/bin/cloud-init'
1539+ mock_which.return_value = cloud_init
1540+ mock_subp.return_value = ("NETWORK_CONFIG_V1\nNETWORK_CONFIG_V2\n", '')
1541+
1542+ available = net.netconfig_passthrough_available(self.target)
1543+
1544+ self.assertEqual(True, available,
1545+ "netconfig passthrough was NOT available")
1546+ mock_which.assert_called_with('cloud-init', target=self.target)
1547+ mock_subp.assert_called_with([cloud_init, 'features'],
1548+ capture=True, target=self.target)
1549+
1550+ @mock.patch('curtin.net.LOG')
1551+ @mock.patch('curtin.util.subp')
1552+ @mock.patch('curtin.util.which')
1553+ @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1554+ def test_netconfig_passthrough_available_no_cloudinit(self, mock_which,
1555+ mock_subp, mock_log):
1556+ mock_which.return_value = None
1557+
1558+ available = net.netconfig_passthrough_available(self.target)
1559+
1560+ self.assertEqual(False, available,
1561+ "netconfig passthrough was available")
1562+ self.assertTrue(mock_log.warning.called)
1563+ self.assertFalse(mock_subp.called)
1564+
1565+ @mock.patch('curtin.util.subp')
1566+ @mock.patch('curtin.util.which')
1567+ @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1568+ def test_netconfig_passthrough_available_feature_not_found(self,
1569+ mock_which,
1570+ mock_subp):
1571+ cloud_init = '/usr/bin/cloud-init'
1572+ mock_which.return_value = cloud_init
1573+ mock_subp.return_value = ('NETWORK_CONFIG_V1\n', '')
1574+
1575+ available = net.netconfig_passthrough_available(self.target)
1576+
1577+ self.assertEqual(False, available,
1578+ "netconfig passthrough was available")
1579+ mock_which.assert_called_with('cloud-init', target=self.target)
1580+ mock_subp.assert_called_with([cloud_init, 'features'],
1581+ capture=True, target=self.target)
1582+
1583+ @mock.patch('curtin.net.LOG')
1584+ @mock.patch('curtin.util.subp')
1585+ @mock.patch('curtin.util.which')
1586+ @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1587+ def test_netconfig_passthrough_available_exc(self, mock_which, mock_subp,
1588+ mock_log):
1589+ cloud_init = '/usr/bin/cloud-init'
1590+ mock_which.return_value = cloud_init
1591+ mock_subp.side_effect = util.ProcessExecutionError
1592+
1593+ available = net.netconfig_passthrough_available(self.target)
1594+
1595+ self.assertEqual(False, available,
1596+ "netconfig passthrough was available")
1597+ mock_which.assert_called_with('cloud-init', target=self.target)
1598+ mock_subp.assert_called_with([cloud_init, 'features'],
1599+ capture=True, target=self.target)
1600+ self.assertTrue(mock_log.warning.called)
1601+
1602+ @mock.patch('curtin.util.write_file')
1603+ def test_render_netconfig_passthrough(self, mock_writefile):
1604+ netcfg = yaml.safe_load(self.config)
1605+ pt_config = 'etc/cloud/cloud.cfg.d/50-curtin-networking.cfg'
1606+ target_config = os.path.sep.join((self.target, pt_config),)
1607+
1608+ net.render_netconfig_passthrough(self.target, netconfig=netcfg)
1609+
1610+ content = config.dump_config(netcfg)
1611+ mock_writefile.assert_called_with(target_config, content=content)
1612+
1613+ def test_render_netconfig_passthrough_nonetcfg(self):
1614+ netcfg = None
1615+ self.assertRaises(ValueError,
1616+ net.render_netconfig_passthrough,
1617+ self.target, netconfig=netcfg)
1618+
1619 def test_routes_rendered(self):
1620 # as reported in bug 1649652
1621 conf = [
1622
1623=== modified file 'tests/vmtests/__init__.py'
1624--- tests/vmtests/__init__.py 2017-05-19 21:40:48 +0000
1625+++ tests/vmtests/__init__.py 2017-07-27 13:56:24 +0000
1626@@ -593,7 +593,7 @@
1627 logger.debug("Interface name: {}".format(ifname))
1628 iface = interfaces.get(ifname)
1629 hwaddr = iface.get('mac_address')
1630- if hwaddr:
1631+ if iface['type'] == 'physical' and hwaddr:
1632 macs.append(hwaddr)
1633 netdevs = []
1634 if len(macs) > 0:
1635@@ -685,6 +685,12 @@
1636 configs.append(excfg)
1637 logger.debug('Added extra config {}'.format(excfg))
1638
1639+ if cls.target_distro == "centos":
1640+ centos_default = 'examples/tests/centos_defaults.yaml'
1641+ configs.append(centos_default)
1642+ logger.info('Detected centos, adding default config %s',
1643+ centos_default)
1644+
1645 if cls.multipath:
1646 disks = disks * cls.multipath_num_paths
1647
1648
1649=== modified file 'tests/vmtests/test_centos_basic.py'
1650--- tests/vmtests/test_centos_basic.py 2016-10-04 23:20:44 +0000
1651+++ tests/vmtests/test_centos_basic.py 2017-07-27 13:56:24 +0000
1652@@ -1,5 +1,6 @@
1653 from . import VMBaseClass
1654 from .releases import centos_base_vm_classes as relbase
1655+from .test_network import TestNetworkBaseTestsAbs
1656
1657 import textwrap
1658
1659@@ -9,10 +10,20 @@
1660 __test__ = False
1661 conf_file = "examples/tests/centos_basic.yaml"
1662 extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
1663+ # XXX: command | tee output is required for Centos under SELinux
1664+ # http://danwalsh.livejournal.com/22860.html
1665 collect_scripts = [textwrap.dedent(
1666 """
1667 cd OUTPUT_COLLECT_D
1668 cat /etc/fstab > fstab
1669+ rpm -qa | cat >rpm_qa
1670+ ifconfig -a | cat >ifconfig_a
1671+ ip a | cat >ip_a
1672+ cp -a /etc/sysconfig/network-scripts .
1673+ cp -a /var/log/messages .
1674+ cp -a /var/log/cloud-init* .
1675+ cp -a /var/lib/cloud ./var_lib_cloud
1676+ cp -a /run/cloud-init ./run_cloud-init
1677 """)]
1678 fstab_expected = {
1679 'LABEL=cloudimg-rootfs': '/',
1680@@ -40,3 +51,27 @@
1681 # FIXME: test is disabled because the grub config script in target
1682 # specifies drive using hd(1,0) syntax, which breaks when the
1683 # installation medium is removed. other than this, the install works
1684+
1685+
1686+class CentosTestBasicNetworkAbs(TestNetworkBaseTestsAbs):
1687+ conf_file = "examples/tests/centos_basic.yaml"
1688+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
1689+ collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
1690+ textwrap.dedent("""
1691+ cd OUTPUT_COLLECT_D
1692+ cp -a /etc/sysconfig/network-scripts .
1693+ cp -a /var/log/cloud-init* .
1694+ cp -a /var/lib/cloud ./var_lib_cloud
1695+ cp -a /run/cloud-init ./run_cloud-init
1696+ """)]
1697+
1698+ def test_etc_network_interfaces(self):
1699+ pass
1700+
1701+ def test_etc_resolvconf(self):
1702+ pass
1703+
1704+
1705+class Centos70BasicNetworkFromXenialTestBasic(relbase.centos70fromxenial,
1706+ CentosTestBasicNetworkAbs):
1707+ __test__ = True
1708
1709=== modified file 'tests/vmtests/test_network.py'
1710--- tests/vmtests/test_network.py 2017-07-26 14:35:50 +0000
1711+++ tests/vmtests/test_network.py 2017-07-27 13:56:24 +0000
1712@@ -1,7 +1,13 @@
1713 from . import VMBaseClass, logger, helpers
1714 from .releases import base_vm_classes as relbase
1715-
1716+from .releases import centos_base_vm_classes as centos_relbase
1717+
1718+from unittest import SkipTest
1719+from curtin import config
1720+
1721+import glob
1722 import ipaddress
1723+import os
1724 import re
1725 import textwrap
1726 import yaml
1727@@ -11,50 +17,167 @@
1728 interactive = False
1729 extra_disks = []
1730 extra_nics = []
1731+ # XXX: command | tee output is required for Centos under SELinux
1732+ # http://danwalsh.livejournal.com/22860.html
1733 collect_scripts = [textwrap.dedent("""
1734 cd OUTPUT_COLLECT_D
1735 echo "waiting for ipv6 to settle" && sleep 5
1736- ifconfig -a > ifconfig_a
1737- ip link show > ip_link_show
1738- ip a > ip_a
1739+ route -n | tee first_route_n
1740+ ifconfig -a | tee ifconfig_a
1741+ ip link show | tee ip_link_show
1742+ ip a | tee ip_a
1743 find /etc/network/interfaces.d > find_interfacesd
1744 cp -av /etc/network/interfaces .
1745 cp -av /etc/network/interfaces.d .
1746 cp /etc/resolv.conf .
1747- cp -av /etc/udev/rules.d/70-persistent-net.rules .
1748- ip -o route show > ip_route_show
1749- ip -6 -o route show > ip_6_route_show
1750- route -n > route_n
1751- route -6 -n > route_6_n
1752+ cp -av /etc/udev/rules.d/70-persistent-net.rules . ||:
1753+ ip -o route show | tee ip_route_show
1754+ ip -6 -o route show | tee ip_6_route_show
1755+ route -n |tee route_n
1756+ route -n -A inet6 |tee route_6_n
1757 cp -av /run/network ./run_network
1758 cp -av /var/log/upstart ./upstart ||:
1759- sleep 10 && ip a > ip_a
1760+ cp -av /etc/cloud ./etc_cloud
1761+ cp -av /var/log/cloud*.log ./
1762+ dpkg-query -W -f '${Version}' cloud-init |tee dpkg_cloud-init_version
1763+ dpkg-query -W -f '${Version}' nplan |tee dpkg_nplan_version
1764+ dpkg-query -W -f '${Version}' systemd |tee dpkg_systemd_version
1765+ rpm -q --queryformat '%{VERSION}\n' cloud-init |tee rpm_ci_version
1766+ V=/usr/lib/python*/*-packages/cloudinit/version.py;
1767+ grep -c NETWORK_CONFIG_V2 $V |tee cloudinit_passthrough_available
1768+ mkdir -p etc_netplan
1769+ cp -av /etc/netplan/* ./etc_netplan/ ||:
1770+ networkctl |tee networkctl
1771+ mkdir -p run_systemd_network
1772+ cp -a /run/systemd/network/* ./run_systemd_network/ ||:
1773+ cp -a /run/systemd/netif ./run_systemd_netif ||:
1774+ cp -a /run/systemd/resolve ./run_systemd_resolve ||:
1775+ cp -a /etc/systemd ./etc_systemd ||:
1776+ journalctl --no-pager -b -x | tee journalctl_out
1777+ sleep 10 && ip a | tee ip_a
1778 """)]
1779
1780 def test_output_files_exist(self):
1781 self.output_files_exist([
1782- "70-persistent-net.rules",
1783 "find_interfacesd",
1784 "ifconfig_a",
1785- "interfaces",
1786 "ip_a",
1787 "ip_route_show",
1788- "resolv.conf",
1789 "route_6_n",
1790 "route_n",
1791 ])
1792
1793- def test_etc_network_interfaces(self):
1794+ def read_eni(self):
1795+ eni = ""
1796+ eni_cfg = ""
1797+
1798 eni = self.load_collect_file("interfaces")
1799 logger.debug('etc/network/interfaces:\n{}'.format(eni))
1800
1801+ # we don't use collect_path as we're building a glob
1802+ eni_dir = os.path.join(self.td.collect, "interfaces.d", "*.cfg")
1803+ eni_cfg = '\n'.join([self.load_collect_file(cfg)
1804+ for cfg in glob.glob(eni_dir)])
1805+
1806+ return (eni, eni_cfg)
1807+
1808+ def _network_renderer(self):
1809+ """ Determine if target uses eni/ifupdown or netplan/networkd """
1810+
1811+ etc_netplan = self.collect_path('etc_netplan')
1812+ networkd = self.collect_path('run_systemd_network')
1813+
1814+ if len(os.listdir(etc_netplan)) > 0 and len(os.listdir(networkd)) > 0:
1815+ print('Network Renderer: systemd-networkd')
1816+ return 'systemd-networkd'
1817+
1818+ print('Network Renderer: ifupdown')
1819+ return 'ifupdown'
1820+
1821+ def test_etc_network_interfaces(self):
1822+ avail_str = self.load_collect_file('cloudinit_passthrough_available')
1823+ pt_available = int(avail_str) == 1
1824+ print('avail_str=%s pt_available=%s' % (avail_str, pt_available))
1825+
1826+ if self._network_renderer() != "ifupdown" or pt_available:
1827+ reason = ("{}: using net-passthrough; "
1828+ "deferring to cloud-init".format(self.__class__))
1829+ raise SkipTest(reason)
1830+
1831+ if not pt_available:
1832+ raise SkipTest(
1833+ 'network passthrough not available on %s' % self.__class__)
1834+
1835+ eni, eni_cfg = self.read_eni()
1836+ logger.debug('etc/network/interfaces:\n{}'.format(eni))
1837 expected_eni = self.get_expected_etc_network_interfaces()
1838- eni_lines = eni.split('\n')
1839- for line in expected_eni.split('\n'):
1840- self.assertTrue(line in eni_lines, msg="missing line: %s" % line)
1841+
1842+ eni_lines = eni.split('\n') + eni_cfg.split('\n')
1843+ print("\n".join(eni_lines))
1844+ for line in [l for l in expected_eni.split('\n') if len(l) > 0]:
1845+ if line.startswith("#"):
1846+ continue
1847+ if "hwaddress ether" in line:
1848+ continue
1849+ print('expected line:\n%s' % line)
1850+ self.assertTrue(line in eni_lines, "not in eni: %s" % line)
1851+
1852+ def test_cloudinit_network_passthrough(self):
1853+ cc_passthrough = "cloud.cfg.d/50-curtin-networking.cfg"
1854+
1855+ avail_str = self.load_collect_file('cloudinit_passthrough_available')
1856+ available = int(avail_str) == 1
1857+ print('avail_str=%s available=%s' % (avail_str, available))
1858+
1859+ if not available:
1860+ raise SkipTest('not available on %s' % self.__class__)
1861+
1862+ print('passthrough was available')
1863+ pt_file = os.path.join(self.td.collect, 'etc_cloud',
1864+ cc_passthrough)
1865+ print('checking if passthrough file written: %s' % pt_file)
1866+ self.assertTrue(os.path.exists(pt_file))
1867+
1868+ # compare
1869+ original = {'network':
1870+ config.load_config(self.conf_file).get('network')}
1871+ intarget = config.load_config(pt_file)
1872+ self.assertEqual(original, intarget)
1873+
1874+ def test_cloudinit_network_disabled(self):
1875+ cc_disabled = 'cloud.cfg.d/curtin-disable-cloudinit-networking.cfg'
1876+
1877+ avail_str = self.load_collect_file('cloudinit_passthrough_available')
1878+ available = int(avail_str) == 1
1879+ print('avail_str=%s available=%s' % (avail_str, available))
1880+
1881+ if available:
1882+ raise SkipTest('passthrough available on %s' % self.__class__)
1883+
1884+ print('passthrough not available')
1885+ cc_disable_file = os.path.join(self.td.collect, 'etc_cloud',
1886+ cc_disabled)
1887+ print('checking if network:disable file written: %s' %
1888+ cc_disable_file)
1889+ self.assertTrue(os.path.exists(cc_disable_file))
1890+
1891+ # compare
1892+ original = {'network': {'config': 'disabled'}}
1893+ intarget = config.load_config(cc_disable_file)
1894+
1895+ print('checking cloud-init network-cfg content')
1896+ self.assertEqual(original, intarget)
1897
1898 def test_etc_resolvconf(self):
1899- resolvconf = self.load_collect_file("resolv.conf")
1900+ render2resolvconf = {
1901+ 'ifupdown': "resolv.conf",
1902+ 'systemd-networkd': "run_systemd_resolve/resolv.conf"
1903+ }
1904+ resolvconfpath = render2resolvconf.get(self._network_renderer(), None)
1905+ self.assertTrue(resolvconfpath is not None)
1906+ logger.debug('Selected path to resolvconf: %s', resolvconfpath)
1907+
1908+ resolvconf = self.load_collect_file(resolvconfpath)
1909 logger.debug('etc/resolv.conf:\n{}'.format(resolvconf))
1910
1911 resolv_lines = resolvconf.split('\n')
1912@@ -111,12 +234,21 @@
1913 def test_static_routes(self):
1914 '''check routing table'''
1915 network_state = self.get_network_state()
1916+
1917+ # if we're using passthrough then we can't load state
1918+ cc_passthrough = "cloud.cfg.d/50-curtin-networking.cfg"
1919+ pt_file = os.path.join(self.td.collect, 'etc_cloud', cc_passthrough)
1920+ print('checking if passthrough file written: %s' % pt_file)
1921+ if not network_state and os.path.exists(pt_file):
1922+ raise SkipTest('passthrough enabled, skipping %s' % self.__class__)
1923+
1924 ip_route_show = self.load_collect_file("ip_route_show")
1925 route_n = self.load_collect_file("route_n")
1926
1927 print("ip route show:\n%s" % ip_route_show)
1928 print("route -n:\n%s" % route_n)
1929- routes = network_state.get('routes')
1930+ routes = network_state.get('routes', [])
1931+ print("found routes: [%s]" % routes)
1932 for route in routes:
1933 print('Checking static route: %s' % route)
1934 destnet = (
1935@@ -144,6 +276,12 @@
1936 print('parsed ip_a dict:\n{}'.format(
1937 yaml.dump(ip_dict, default_flow_style=False, indent=4)))
1938
1939+ route_n = self.load_collect_file("route_n")
1940+ logger.debug("route -n:\n{}".format(route_n))
1941+
1942+ route_6_n = self.load_collect_file("route_6_n")
1943+ logger.debug("route -6 -n:\n{}".format(route_6_n))
1944+
1945 ip_route_show = self.load_collect_file("ip_route_show")
1946 logger.debug("ip route show:\n{}".format(ip_route_show))
1947 for line in [line for line in ip_route_show.split('\n')
1948@@ -156,12 +294,6 @@
1949 route_info = m.groupdict('')
1950 logger.debug(route_info)
1951
1952- route_n = self.load_collect_file("route_n")
1953- logger.debug("route -n:\n{}".format(route_n))
1954-
1955- route_6_n = self.load_collect_file("route_6_n")
1956- logger.debug("route -6 -n:\n{}".format(route_6_n))
1957-
1958 routes = {
1959 '4': route_n,
1960 '6': route_6_n,
1961@@ -170,9 +302,10 @@
1962 for iface in interfaces.values():
1963 print("\nnetwork_state iface: %s" % (
1964 yaml.dump(iface, default_flow_style=False, indent=4)))
1965+ ipcfg = ip_dict.get(iface['name'], {})
1966 self.check_interface(iface['name'],
1967 iface,
1968- ip_dict.get(iface['name']),
1969+ ipcfg,
1970 routes)
1971
1972 def check_interface(self, ifname, iface, ipcfg, routes):
1973@@ -182,8 +315,9 @@
1974 # FIXME: remove check?
1975 # initial check, do we have the correct iface ?
1976 print('ifname={}'.format(ifname))
1977+ self.assertTrue(type(ipcfg) == dict, "%s is not dict" % (ipcfg))
1978+ print("ipcfg['interface']={}".format(ipcfg['interface']))
1979 self.assertEqual(ifname, ipcfg['interface'])
1980- print("ipcfg['interface']={}".format(ipcfg['interface']))
1981
1982 # check physical interface attributes (skip bond members, macs change)
1983 if iface['type'] in ['physical'] and 'bond-master' not in iface:
1984@@ -292,6 +426,25 @@
1985 conf_file = "examples/tests/basic_network.yaml"
1986
1987
1988+class CentosTestNetworkBasicAbs(TestNetworkBaseTestsAbs):
1989+ conf_file = "examples/tests/centos_basic.yaml"
1990+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
1991+ collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
1992+ textwrap.dedent("""
1993+ cd OUTPUT_COLLECT_D
1994+ cp -a /etc/sysconfig/network-scripts .
1995+ cp -a /var/log/cloud-init* .
1996+ cp -a /var/lib/cloud ./var_lib_cloud
1997+ cp -a /run/cloud-init ./run_cloud-init
1998+ """)]
1999+
2000+ def test_etc_network_interfaces(self):
2001+ pass
2002+
2003+ def test_etc_resolvconf(self):
2004+ pass
2005+
2006+
2007 class PreciseHWETTestNetworkBasic(relbase.precise_hwe_t, TestNetworkBasicAbs):
2008 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2009 __test__ = False
2010@@ -334,3 +487,13 @@
2011
2012 class ArtfulTestNetworkBasic(relbase.artful, TestNetworkBasicAbs):
2013 __test__ = True
2014+
2015+
2016+class Centos66TestNetworkBasic(centos_relbase.centos66fromxenial,
2017+ CentosTestNetworkBasicAbs):
2018+ __test__ = True
2019+
2020+
2021+class Centos70TestNetworkBasic(centos_relbase.centos70fromxenial,
2022+ CentosTestNetworkBasicAbs):
2023+ __test__ = True
2024
2025=== modified file 'tests/vmtests/test_network_alias.py'
2026--- tests/vmtests/test_network_alias.py 2017-07-26 14:35:50 +0000
2027+++ tests/vmtests/test_network_alias.py 2017-07-27 13:56:24 +0000
2028@@ -1,5 +1,8 @@
2029 from .releases import base_vm_classes as relbase
2030+from .releases import centos_base_vm_classes as centos_relbase
2031 from .test_network import TestNetworkBaseTestsAbs
2032+from unittest import SkipTest
2033+import textwrap
2034
2035
2036 class TestNetworkAliasAbs(TestNetworkBaseTestsAbs):
2037@@ -7,6 +10,36 @@
2038 """
2039 conf_file = "examples/tests/network_alias.yaml"
2040
2041+ def test_etc_network_interfaces(self):
2042+ reason = ("%s: cloud-init and curtin eni rendering"
2043+ " differ" % (self.__class__))
2044+ raise SkipTest(reason)
2045+
2046+
2047+class CentosTestNetworkAliasAbs(TestNetworkAliasAbs):
2048+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2049+ collect_scripts = TestNetworkAliasAbs.collect_scripts + [
2050+ textwrap.dedent("""
2051+ cd OUTPUT_COLLECT_D
2052+ cp -a /etc/sysconfig/network-scripts .
2053+ cp -a /var/log/cloud-init* .
2054+ cp -a /var/lib/cloud ./var_lib_cloud
2055+ cp -a /run/cloud-init ./run_cloud-init
2056+ """)]
2057+
2058+ def test_etc_resolvconf(self):
2059+ pass
2060+
2061+
2062+class Centos66TestNetworkAlias(centos_relbase.centos66fromxenial,
2063+ CentosTestNetworkAliasAbs):
2064+ __test__ = True
2065+
2066+
2067+class Centos70TestNetworkAlias(centos_relbase.centos70fromxenial,
2068+ CentosTestNetworkAliasAbs):
2069+ __test__ = True
2070+
2071
2072 class PreciseHWETTestNetworkAlias(relbase.precise_hwe_t, TestNetworkAliasAbs):
2073 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2074
2075=== modified file 'tests/vmtests/test_network_bonding.py'
2076--- tests/vmtests/test_network_bonding.py 2017-07-26 14:35:50 +0000
2077+++ tests/vmtests/test_network_bonding.py 2017-07-27 13:56:24 +0000
2078@@ -1,6 +1,7 @@
2079 from . import logger
2080 from .releases import base_vm_classes as relbase
2081 from .test_network import TestNetworkBaseTestsAbs
2082+from .releases import centos_base_vm_classes as centos_relbase
2083
2084 import textwrap
2085
2086@@ -22,6 +23,30 @@
2087 self.assertEqual('install ok installed', status)
2088
2089
2090+class CentosTestNetworkBondingAbs(TestNetworkBondingAbs):
2091+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2092+ collect_scripts = TestNetworkBondingAbs.collect_scripts + [
2093+ textwrap.dedent("""
2094+ cd OUTPUT_COLLECT_D
2095+ cp -a /etc/sysconfig/network-scripts .
2096+ cp -a /var/log/cloud-init* .
2097+ cp -a /var/lib/cloud ./var_lib_cloud
2098+ cp -a /run/cloud-init ./run_cloud-init
2099+ rpm -qf `which ifenslave` |tee ifenslave_installed
2100+ """)]
2101+
2102+ def test_ifenslave_installed(self):
2103+ status = self.load_collect_file("ifenslave_installed")
2104+ logger.debug('ifenslave installed: {}'.format(status))
2105+ self.assertTrue('iputils' in status)
2106+
2107+ def test_etc_network_interfaces(self):
2108+ pass
2109+
2110+ def test_etc_resolvconf(self):
2111+ pass
2112+
2113+
2114 class PreciseHWETTestBonding(relbase.precise_hwe_t, TestNetworkBondingAbs):
2115 __test__ = True
2116 # package names on precise are different, need to check on ifenslave-2.6
2117@@ -71,3 +96,13 @@
2118
2119 class ArtfulTestBonding(relbase.artful, TestNetworkBondingAbs):
2120 __test__ = True
2121+
2122+
2123+class Centos66TestNetworkBonding(centos_relbase.centos66fromxenial,
2124+ CentosTestNetworkBondingAbs):
2125+ __test__ = True
2126+
2127+
2128+class Centos70TestNetworkBonding(centos_relbase.centos70fromxenial,
2129+ CentosTestNetworkBondingAbs):
2130+ __test__ = True
2131
2132=== modified file 'tests/vmtests/test_network_bridging.py'
2133--- tests/vmtests/test_network_bridging.py 2017-07-26 14:35:50 +0000
2134+++ tests/vmtests/test_network_bridging.py 2017-07-27 13:56:24 +0000
2135@@ -1,5 +1,6 @@
2136 from . import logger
2137 from .releases import base_vm_classes as relbase
2138+from .releases import centos_base_vm_classes as centos_relbase
2139 from .test_network import TestNetworkBaseTestsAbs
2140 from curtin import util
2141
2142@@ -39,6 +40,10 @@
2143
2144 # attrs we cannot validate
2145 release_to_bridge_params_uncheckable = {
2146+ 'centos66': ['bridge_fd', 'bridge_hello', 'bridge_hw', 'bridge_maxage',
2147+ 'bridge_pathcost', 'bridge_portprio'],
2148+ 'centos70': ['bridge_fd', 'bridge_hello', 'bridge_hw', 'bridge_maxage',
2149+ 'bridge_pathcost', 'bridge_portprio'],
2150 'xenial': ['bridge_ageing'],
2151 'yakkety': ['bridge_ageing'],
2152 }
2153@@ -73,7 +78,7 @@
2154 # Some of the kernel parameters are non-human values, in that
2155 # case convert them back to match values from the input YAML
2156 if param in bridge_param_divfactor:
2157- sys_file_val = (sys_file_val / bridge_param_divfactor[param])
2158+ sys_file_val = round(sys_file_val / bridge_param_divfactor[param])
2159
2160 return sys_file_val
2161
2162@@ -100,18 +105,17 @@
2163 """)]
2164
2165 def test_output_files_exist_bridge(self):
2166- self.output_files_exist(["bridge-utils_installed",
2167- "sysfs_br0",
2168+ self.output_files_exist(["sysfs_br0",
2169 "sysfs_br0_eth1",
2170 "sysfs_br0_eth2"])
2171
2172 def test_bridge_utils_installed(self):
2173+ self.output_files_exist(["bridge-utils_installed"])
2174 status = self.load_collect_file("bridge-utils_installed").strip()
2175 logger.debug('bridge-utils installed: {}'.format(status))
2176 self.assertEqual('install ok installed', status)
2177
2178 def test_bridge_params(self):
2179- """ Test if configure bridge params match values on the device """
2180
2181 def _load_sysfs_bridge_data():
2182 sysfs_br0 = sysfs_to_dict(self.collect_path("sysfs_br0"))
2183@@ -130,14 +134,17 @@
2184 return br0
2185
2186 def _get_bridge_params(br):
2187+ release = (
2188+ self.target_release if self.target_release else self.release)
2189 bridge_params_uncheckable = default_bridge_params_uncheckable
2190 bridge_params_uncheckable.extend(
2191- release_to_bridge_params_uncheckable.get(self.release, []))
2192+ release_to_bridge_params_uncheckable.get(release, []))
2193 return [p for p in br.keys()
2194 if (p.startswith('bridge_') and
2195 p not in bridge_params_uncheckable)]
2196
2197 def _check_bridge_param(sysfs_vals, p, br):
2198+ print('Checking bridge %s param %s' % (br, p))
2199 value = br.get(param)
2200 if param in ['bridge_stp']:
2201 if value in ['off', '0']:
2202@@ -146,37 +153,84 @@
2203 value = 1
2204 else:
2205 print('bridge_stp not in known val list')
2206+ elif param in ['bridge_portprio']:
2207+ if self._network_renderer() == "systemd-networkd":
2208+ reason = ("%s: skip until lp#1668347"
2209+ " is fixed" % self.__class__)
2210+ logger.warn('Skipping: %s', reason)
2211+ print(reason)
2212+ return
2213
2214 print('key=%s value=%s' % (param, value))
2215 if type(value) == list:
2216 for subval in value:
2217 (port, pval) = subval.split(" ")
2218- print('key=%s port=%s pval=%s' % (param, port, pval))
2219+ print('param=%s port=%s pval=%s' % (param, port, pval))
2220 sys_file_val = _get_sysfs_value(sysfs_vals, br0['name'],
2221 param, port)
2222
2223- self.assertEqual(int(pval), int(sys_file_val))
2224+ msg = "Source cfg: %s=%s on port %s" % (param, value, port)
2225+ self.assertEqual(int(pval), int(sys_file_val), msg)
2226 else:
2227 sys_file_val = _get_sysfs_value(sysfs_vals, br0['name'],
2228 param, port=None)
2229- self.assertEqual(int(value), int(sys_file_val))
2230+ self.assertEqual(int(value), int(sys_file_val),
2231+ "Source cfg: %s=%s" % (param, value))
2232
2233 sysfs_vals = _load_sysfs_bridge_data()
2234- print(sysfs_vals)
2235+ # print(sysfs_vals)
2236 br0 = _get_bridge_config()
2237 for param in _get_bridge_params(br0):
2238+ print('Checking param %s' % param)
2239 _check_bridge_param(sysfs_vals, param, br0)
2240
2241
2242+class CentosTestBridgeNetworkAbs(TestBridgeNetworkAbs):
2243+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2244+ collect_scripts = TestBridgeNetworkAbs.collect_scripts + [
2245+ textwrap.dedent("""
2246+ cd OUTPUT_COLLECT_D
2247+ cp -a /etc/sysconfig/network-scripts .
2248+ cp -a /var/log/cloud-init* .
2249+ cp -a /var/lib/cloud ./var_lib_cloud
2250+ cp -a /run/cloud-init ./run_cloud-init
2251+ rpm -qf `which brctl` |tee bridge-utils_installed
2252+ """)]
2253+
2254+ def test_etc_network_interfaces(self):
2255+ pass
2256+
2257+ def test_etc_resolvconf(self):
2258+ pass
2259+
2260+ def test_bridge_utils_installed(self):
2261+ self.output_files_exist(["bridge-utils_installed"])
2262+ status = self.load_collect_file("bridge-utils_installed").strip()
2263+ logger.debug('bridge-utils installed: {}'.format(status))
2264+ self.assertTrue('bridge' in status)
2265+
2266+
2267+class Centos66TestBridgeNetwork(centos_relbase.centos66fromxenial,
2268+ CentosTestBridgeNetworkAbs):
2269+ __test__ = True
2270+
2271+
2272+class Centos70TestBridgeNetwork(centos_relbase.centos70fromxenial,
2273+ CentosTestBridgeNetworkAbs):
2274+ __test__ = True
2275+
2276+
2277 # only testing Yakkety or newer as older releases do not yet
2278 # have updated ifupdown/bridge-utils packages;
2279-class YakketyTestBridging(relbase.yakkety, TestBridgeNetworkAbs):
2280- __test__ = False
2281-
2282-
2283 class ZestyTestBridging(relbase.zesty, TestBridgeNetworkAbs):
2284 __test__ = True
2285
2286+ @classmethod
2287+ def setUpClass(cls):
2288+ cls.skip_by_date(cls.__name__, cls.release, "1706752",
2289+ fixby=(2017, 8, 10), removeby=(2017, 8, 31))
2290+ super().setUpClass()
2291+
2292
2293 class ArtfulTestBridging(relbase.artful, TestBridgeNetworkAbs):
2294 __test__ = True
2295
2296=== modified file 'tests/vmtests/test_network_ipv6.py'
2297--- tests/vmtests/test_network_ipv6.py 2017-07-26 14:35:50 +0000
2298+++ tests/vmtests/test_network_ipv6.py 2017-07-27 13:56:24 +0000
2299@@ -1,4 +1,5 @@
2300 from .releases import base_vm_classes as relbase
2301+from .releases import centos_base_vm_classes as centos_relbase
2302 from .test_network import TestNetworkBaseTestsAbs
2303
2304 import textwrap
2305@@ -21,6 +22,24 @@
2306 """)]
2307
2308
2309+class CentosTestNetworkIPV6Abs(TestNetworkIPV6Abs):
2310+ extra_kern_args = "BOOTIF=eth0-bc:76:4e:06:96:b3"
2311+ collect_scripts = TestNetworkIPV6Abs.collect_scripts + [
2312+ textwrap.dedent("""
2313+ cd OUTPUT_COLLECT_D
2314+ cp -a /etc/sysconfig/network-scripts .
2315+ cp -a /var/log/cloud-init* .
2316+ cp -a /var/lib/cloud ./var_lib_cloud
2317+ cp -a /run/cloud-init ./run_cloud-init
2318+ """)]
2319+
2320+ def test_etc_network_interfaces(self):
2321+ pass
2322+
2323+ def test_etc_resolvconf(self):
2324+ pass
2325+
2326+
2327 class PreciseHWETTestNetwork(relbase.precise_hwe_t, TestNetworkIPV6Abs):
2328 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2329 __test__ = False
2330@@ -48,6 +67,11 @@
2331 class XenialTestNetworkIPV6(relbase.xenial, TestNetworkIPV6Abs):
2332 __test__ = True
2333
2334+ @classmethod
2335+ def test_ip_output(cls):
2336+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1701097",
2337+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2338+
2339
2340 class YakketyTestNetworkIPV6(relbase.yakkety, TestNetworkIPV6Abs):
2341 __test__ = False
2342@@ -56,6 +80,22 @@
2343 class ZestyTestNetworkIPV6(relbase.zesty, TestNetworkIPV6Abs):
2344 __test__ = True
2345
2346+ @classmethod
2347+ def setUpClass(cls):
2348+ cls.skip_by_date(cls.__name__, cls.release, "ci-003c6678e",
2349+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2350+ super().setUpClass()
2351+
2352
2353 class ArtfulTestNetworkIPV6(relbase.artful, TestNetworkIPV6Abs):
2354 __test__ = True
2355+
2356+
2357+class Centos66TestNetworkIPV6(centos_relbase.centos66fromxenial,
2358+ CentosTestNetworkIPV6Abs):
2359+ __test__ = True
2360+
2361+
2362+class Centos70TestNetworkIPV6(centos_relbase.centos70fromxenial,
2363+ CentosTestNetworkIPV6Abs):
2364+ __test__ = True
2365
2366=== modified file 'tests/vmtests/test_network_ipv6_enisource.py'
2367--- tests/vmtests/test_network_ipv6_enisource.py 2017-07-26 14:35:50 +0000
2368+++ tests/vmtests/test_network_ipv6_enisource.py 2017-07-27 13:56:24 +0000
2369@@ -1,10 +1,16 @@
2370 from .releases import base_vm_classes as relbase
2371 from .test_network_enisource import TestNetworkENISource
2372
2373+import unittest
2374+
2375
2376 class TestNetworkIPV6ENISource(TestNetworkENISource):
2377 conf_file = "examples/tests/network_source_ipv6.yaml"
2378
2379+ @unittest.skip("FIXME: cloud-init.net needs update")
2380+ def test_etc_network_interfaces(self):
2381+ pass
2382+
2383
2384 class PreciseTestNetworkIPV6ENISource(relbase.precise,
2385 TestNetworkIPV6ENISource):
2386@@ -25,15 +31,35 @@
2387 class XenialTestNetworkIPV6ENISource(relbase.xenial, TestNetworkIPV6ENISource):
2388 __test__ = True
2389
2390+ @classmethod
2391+ def test_ip_output(cls):
2392+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1701097",
2393+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2394+
2395
2396 class YakketyTestNetworkIPV6ENISource(relbase.yakkety,
2397 TestNetworkIPV6ENISource):
2398 __test__ = False
2399
2400+ @classmethod
2401+ def test_ip_output(cls):
2402+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1701097",
2403+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2404+
2405
2406 class ZestyTestNetworkIPV6ENISource(relbase.zesty, TestNetworkIPV6ENISource):
2407 __test__ = True
2408
2409+ @classmethod
2410+ def test_ip_output(cls):
2411+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1701097",
2412+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2413+
2414
2415 class ArtfulTestNetworkIPV6ENISource(relbase.artful, TestNetworkIPV6ENISource):
2416 __test__ = True
2417+
2418+ @classmethod
2419+ def test_ip_output(cls):
2420+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1701097",
2421+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2422
2423=== modified file 'tests/vmtests/test_network_ipv6_static.py'
2424--- tests/vmtests/test_network_ipv6_static.py 2017-07-26 14:35:50 +0000
2425+++ tests/vmtests/test_network_ipv6_static.py 2017-07-27 13:56:24 +0000
2426@@ -1,5 +1,7 @@
2427 from .releases import base_vm_classes as relbase
2428-from .test_network_static import TestNetworkStaticAbs
2429+from .releases import centos_base_vm_classes as centos_relbase
2430+from .test_network_static import (TestNetworkStaticAbs,
2431+ CentosTestNetworkStaticAbs)
2432
2433
2434 # reuse basic network tests but with different config (static, no dhcp)
2435@@ -7,6 +9,10 @@
2436 conf_file = "examples/tests/basic_network_static_ipv6.yaml"
2437
2438
2439+class CentosTestNetworkIPV6StaticAbs(CentosTestNetworkStaticAbs):
2440+ conf_file = "examples/tests/basic_network_static_ipv6.yaml"
2441+
2442+
2443 class PreciseHWETTestNetworkIPV6Static(relbase.precise_hwe_t,
2444 TestNetworkIPV6StaticAbs):
2445 __test__ = True
2446@@ -53,3 +59,13 @@
2447
2448 class ArtfulTestNetworkIPV6Static(relbase.artful, TestNetworkIPV6StaticAbs):
2449 __test__ = True
2450+
2451+
2452+class Centos66TestNetworkIPV6Static(centos_relbase.centos66fromxenial,
2453+ CentosTestNetworkIPV6StaticAbs):
2454+ __test__ = True
2455+
2456+
2457+class Centos70TestNetworkIPV6Static(centos_relbase.centos70fromxenial,
2458+ CentosTestNetworkIPV6StaticAbs):
2459+ __test__ = True
2460
2461=== modified file 'tests/vmtests/test_network_ipv6_vlan.py'
2462--- tests/vmtests/test_network_ipv6_vlan.py 2017-07-26 14:35:50 +0000
2463+++ tests/vmtests/test_network_ipv6_vlan.py 2017-07-27 13:56:24 +0000
2464@@ -1,11 +1,17 @@
2465 from .releases import base_vm_classes as relbase
2466-from .test_network_vlan import TestNetworkVlanAbs
2467+from .releases import centos_base_vm_classes as centos_relbase
2468+from .test_network_vlan import (TestNetworkVlanAbs,
2469+ CentosTestNetworkVlanAbs)
2470
2471
2472 class TestNetworkIPV6VlanAbs(TestNetworkVlanAbs):
2473 conf_file = "examples/tests/vlan_network_ipv6.yaml"
2474
2475
2476+class CentosTestNetworkIPV6VlanAbs(CentosTestNetworkVlanAbs):
2477+ conf_file = "examples/tests/vlan_network_ipv6.yaml"
2478+
2479+
2480 class PreciseTestNetworkIPV6Vlan(relbase.precise, TestNetworkIPV6VlanAbs):
2481 __test__ = True
2482
2483@@ -42,6 +48,22 @@
2484 class ZestyTestNetworkIPV6Vlan(relbase.zesty, TestNetworkIPV6VlanAbs):
2485 __test__ = True
2486
2487+ @classmethod
2488+ def setUpClass(cls):
2489+ cls.skip_by_date(cls.__name__, cls.release, bugnum="ci-003c6678e",
2490+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2491+ super().setUpClass()
2492+
2493
2494 class ArtfulTestNetworkIPV6Vlan(relbase.artful, TestNetworkIPV6VlanAbs):
2495 __test__ = True
2496+
2497+
2498+class Centos66TestNetworkIPV6Vlan(centos_relbase.centos66fromxenial,
2499+ CentosTestNetworkIPV6VlanAbs):
2500+ __test__ = True
2501+
2502+
2503+class Centos70TestNetworkIPV6Vlan(centos_relbase.centos70fromxenial,
2504+ CentosTestNetworkIPV6VlanAbs):
2505+ __test__ = True
2506
2507=== modified file 'tests/vmtests/test_network_mtu.py'
2508--- tests/vmtests/test_network_mtu.py 2017-07-26 14:35:50 +0000
2509+++ tests/vmtests/test_network_mtu.py 2017-07-27 13:56:24 +0000
2510@@ -1,4 +1,5 @@
2511 from .releases import base_vm_classes as relbase
2512+from .releases import centos_base_vm_classes as centos_relbase
2513 from .test_network_ipv6 import TestNetworkIPV6Abs
2514
2515 import textwrap
2516@@ -25,8 +26,8 @@
2517 cd OUTPUT_COLLECT_D
2518 proc_v6="/proc/sys/net/ipv6/conf"
2519 for f in `seq 0 7`; do
2520- cat /sys/class/net/interface${f}/mtu > interface${f}_dev_mtu;
2521- cat $proc_v6/interface${f}/mtu > interface${f}_ipv6_mtu;
2522+ cat /sys/class/net/interface${f}/mtu |tee interface${f}_dev_mtu;
2523+ cat $proc_v6/interface${f}/mtu |tee interface${f}_ipv6_mtu;
2524 done
2525 if [ -e /var/log/upstart ]; then
2526 cp -a /var/log/upstart ./var_log_upstart
2527@@ -114,6 +115,50 @@
2528 self._check_iface_subnets('interface7')
2529
2530
2531+class CentosTestNetworkMtuAbs(TestNetworkMtuAbs):
2532+ conf_file = "examples/tests/network_mtu.yaml"
2533+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2534+ collect_scripts = TestNetworkMtuAbs.collect_scripts + [
2535+ textwrap.dedent("""
2536+ cd OUTPUT_COLLECT_D
2537+ cp -a /etc/sysconfig/network-scripts .
2538+ cp -a /var/log/cloud-init* .
2539+ cp -a /var/lib/cloud ./var_lib_cloud
2540+ cp -a /run/cloud-init ./run_cloud-init
2541+ """)]
2542+
2543+ def test_etc_network_interfaces(self):
2544+ pass
2545+
2546+ def test_etc_resolvconf(self):
2547+ pass
2548+
2549+ @classmethod
2550+ def test_ip_output(cls):
2551+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1706973",
2552+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2553+
2554+ @classmethod
2555+ def test_ipv6_mtu_smaller_than_ipv4_v6_iface_first(cls):
2556+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1706973",
2557+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2558+
2559+ @classmethod
2560+ def test_ipv6_mtu_smaller_than_ipv4_non_default(cls):
2561+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1706973",
2562+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2563+
2564+ @classmethod
2565+ def test_ipv6_mtu_higher_than_default_no_ipv4_iface_up(cls):
2566+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1706973",
2567+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2568+
2569+ @classmethod
2570+ def test_ipv6_mtu_higher_than_default_no_ipv4_iface_v6_iface_first(cls):
2571+ cls.skip_by_date(cls.__name__, cls.release, bugnum="1706973",
2572+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2573+
2574+
2575 class PreciseHWETTestNetworkMtu(relbase.precise_hwe_t, TestNetworkMtuAbs):
2576 # FIXME: Precise mtu / ipv6 is buggy
2577 __test__ = False
2578@@ -162,3 +207,13 @@
2579
2580 class ArtfulTestNetworkMtu(relbase.artful, TestNetworkMtuAbs):
2581 __test__ = True
2582+
2583+
2584+class Centos66TestNetworkMtu(centos_relbase.centos66fromxenial,
2585+ CentosTestNetworkMtuAbs):
2586+ __test__ = True
2587+
2588+
2589+class Centos70TestNetworkMtu(centos_relbase.centos70fromxenial,
2590+ CentosTestNetworkMtuAbs):
2591+ __test__ = True
2592
2593=== modified file 'tests/vmtests/test_network_static.py'
2594--- tests/vmtests/test_network_static.py 2017-07-26 14:35:50 +0000
2595+++ tests/vmtests/test_network_static.py 2017-07-27 13:56:24 +0000
2596@@ -1,5 +1,7 @@
2597 from .releases import base_vm_classes as relbase
2598+from .releases import centos_base_vm_classes as centos_relbase
2599 from .test_network import TestNetworkBaseTestsAbs
2600+import textwrap
2601
2602
2603 class TestNetworkStaticAbs(TestNetworkBaseTestsAbs):
2604@@ -8,6 +10,24 @@
2605 conf_file = "examples/tests/basic_network_static.yaml"
2606
2607
2608+class CentosTestNetworkStaticAbs(TestNetworkStaticAbs):
2609+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2610+ collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
2611+ textwrap.dedent("""
2612+ cd OUTPUT_COLLECT_D
2613+ cp -a /etc/sysconfig/network-scripts .
2614+ cp -a /var/log/cloud-init* .
2615+ cp -a /var/lib/cloud ./var_lib_cloud
2616+ cp -a /run/cloud-init ./run_cloud-init
2617+ """)]
2618+
2619+ def test_etc_network_interfaces(self):
2620+ pass
2621+
2622+ def test_etc_resolvconf(self):
2623+ pass
2624+
2625+
2626 class PreciseHWETTestNetworkStatic(relbase.precise_hwe_t,
2627 TestNetworkStaticAbs):
2628 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2629@@ -55,3 +75,13 @@
2630
2631 class ArtfulTestNetworkStatic(relbase.artful, TestNetworkStaticAbs):
2632 __test__ = True
2633+
2634+
2635+class Centos66TestNetworkStatic(centos_relbase.centos66fromxenial,
2636+ CentosTestNetworkStaticAbs):
2637+ __test__ = True
2638+
2639+
2640+class Centos70TestNetworkStatic(centos_relbase.centos70fromxenial,
2641+ CentosTestNetworkStaticAbs):
2642+ __test__ = True
2643
2644=== modified file 'tests/vmtests/test_network_static_routes.py'
2645--- tests/vmtests/test_network_static_routes.py 2017-07-26 14:35:50 +0000
2646+++ tests/vmtests/test_network_static_routes.py 2017-07-27 13:56:24 +0000
2647@@ -1,5 +1,7 @@
2648 from .releases import base_vm_classes as relbase
2649-from .test_network import TestNetworkBaseTestsAbs
2650+from .releases import centos_base_vm_classes as centos_relbase
2651+from .test_network import (TestNetworkBaseTestsAbs,
2652+ CentosTestNetworkBasicAbs)
2653
2654
2655 class TestNetworkStaticRoutesAbs(TestNetworkBaseTestsAbs):
2656@@ -8,6 +10,12 @@
2657 conf_file = "examples/tests/network_static_routes.yaml"
2658
2659
2660+class CentosTestNetworkStaticRoutesAbs(CentosTestNetworkBasicAbs):
2661+ """ Static network routes testing with ipv4
2662+ """
2663+ conf_file = "examples/tests/network_static_routes.yaml"
2664+
2665+
2666 class PreciseHWETTestNetworkStaticRoutes(relbase.precise_hwe_t,
2667 TestNetworkStaticRoutesAbs):
2668 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2669@@ -57,3 +65,13 @@
2670 class ArtfulTestNetworkStaticRoutes(relbase.artful,
2671 TestNetworkStaticRoutesAbs):
2672 __test__ = True
2673+
2674+
2675+class Centos66TestNetworkStaticRoutes(centos_relbase.centos66fromxenial,
2676+ CentosTestNetworkStaticRoutesAbs):
2677+ __test__ = False
2678+
2679+
2680+class Centos70TestNetworkStaticRoutes(centos_relbase.centos70fromxenial,
2681+ CentosTestNetworkStaticRoutesAbs):
2682+ __test__ = False
2683
2684=== modified file 'tests/vmtests/test_network_vlan.py'
2685--- tests/vmtests/test_network_vlan.py 2017-07-26 14:35:50 +0000
2686+++ tests/vmtests/test_network_vlan.py 2017-07-27 13:56:24 +0000
2687@@ -1,5 +1,6 @@
2688 from . import logger
2689 from .releases import base_vm_classes as relbase
2690+from .releases import centos_base_vm_classes as centos_relbase
2691 from .test_network import TestNetworkBaseTestsAbs
2692
2693 import textwrap
2694@@ -12,10 +13,10 @@
2695 textwrap.dedent("""
2696 cd OUTPUT_COLLECT_D
2697 dpkg-query -W -f '${Status}' vlan > vlan_installed
2698- ip -d link show interface1.2667 > ip_link_show_interface1.2667
2699- ip -d link show interface1.2668 > ip_link_show_interface1.2668
2700- ip -d link show interface1.2669 > ip_link_show_interface1.2669
2701- ip -d link show interface1.2670 > ip_link_show_interface1.2670
2702+ ip -d link show interface1.2667 |tee ip_link_show_interface1.2667
2703+ ip -d link show interface1.2668 |tee ip_link_show_interface1.2668
2704+ ip -d link show interface1.2669 |tee ip_link_show_interface1.2669
2705+ ip -d link show interface1.2670 |tee ip_link_show_interface1.2670
2706 """)]
2707
2708 def get_vlans(self):
2709@@ -45,10 +46,32 @@
2710 # did they get configured?
2711 for vlan in self.get_vlans():
2712 link_file = "ip_link_show_" + vlan['name']
2713- vlan_msg = "vlan protocol 802.1Q id " + str(vlan['vlan_id'])
2714+ vlan_msg = "vlan.*id " + str(vlan['vlan_id'])
2715 self.check_file_regex(link_file, vlan_msg)
2716
2717
2718+class CentosTestNetworkVlanAbs(TestNetworkVlanAbs):
2719+ extra_kern_args = "BOOTIF=eth0-d4:be:d9:a8:49:13"
2720+ collect_scripts = TestNetworkVlanAbs.collect_scripts + [
2721+ textwrap.dedent("""
2722+ cd OUTPUT_COLLECT_D
2723+ cp -a /etc/sysconfig/network-scripts .
2724+ cp -a /var/log/cloud-init* .
2725+ cp -a /var/lib/cloud ./var_lib_cloud
2726+ cp -a /run/cloud-init ./run_cloud-init
2727+ """)]
2728+
2729+ def test_etc_network_interfaces(self):
2730+ pass
2731+
2732+ def test_etc_resolvconf(self):
2733+ pass
2734+
2735+ def test_vlan_installed(self):
2736+ """centos has vlan support built-in, no extra packages needed"""
2737+ pass
2738+
2739+
2740 class PreciseTestNetworkVlan(relbase.precise, TestNetworkVlanAbs):
2741 __test__ = True
2742
2743@@ -84,6 +107,22 @@
2744 class ZestyTestNetworkVlan(relbase.zesty, TestNetworkVlanAbs):
2745 __test__ = True
2746
2747+ @classmethod
2748+ def setUpClass(cls):
2749+ cls.skip_by_date(cls.__name__, cls.release, "ci-003c6678e",
2750+ fixby=(2017, 8, 16), removeby=(2017, 8, 31))
2751+ super().setUpClass()
2752+
2753
2754 class ArtfulTestNetworkVlan(relbase.artful, TestNetworkVlanAbs):
2755 __test__ = True
2756+
2757+
2758+class Centos66TestNetworkVlan(centos_relbase.centos66fromxenial,
2759+ CentosTestNetworkVlanAbs):
2760+ __test__ = True
2761+
2762+
2763+class Centos70TestNetworkVlan(centos_relbase.centos70fromxenial,
2764+ CentosTestNetworkVlanAbs):
2765+ __test__ = True
2766
2767=== modified file 'tools/build-deb'
2768--- tools/build-deb 2013-09-25 21:04:22 +0000
2769+++ tools/build-deb 2017-07-27 13:56:24 +0000
2770@@ -5,6 +5,7 @@
2771 sourcename="curtin"
2772 TEMP_D=""
2773 UNCOMMITTED=${UNCOMMITTED:-0}
2774+RELEASE=${RELEASE:-UNRELEASED}
2775
2776 fail() { echo "$@" 1>&2; exit 1; }
2777 cleanup() {
2778@@ -67,7 +68,8 @@
2779 mv "$f" "${f%.trunk}"
2780 done
2781
2782-sed -i "1s,${clogver_o},${clogver_new}," debian/changelog ||
2783+sed -i -e "1s,${clogver_o},${clogver_new}," \
2784+ -e "1s,UNRELEASED,${RELEASE}," debian/changelog ||
2785 fail "failed to write debian/changelog"
2786 debuild "$@" || fail "debuild failed"
2787

Subscribers

People subscribed via source and target branches