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

Proposed by Ryan Harper
Status: Merged
Merged at revision: 511
Proposed branch: lp:~raharper/curtin/centos-passthrough
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 2781 lines (+1733/-176)
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 (+79/-9)
tests/vmtests/test_network_ipv6.py (+39/-0)
tests/vmtests/test_network_ipv6_enisource.py (+26/-0)
tests/vmtests/test_network_ipv6_static.py (+22/-1)
tests/vmtests/test_network_ipv6_vlan.py (+17/-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 (+38/-5)
tools/build-deb (+3/-1)
To merge this branch: bzr merge lp:~raharper/curtin/centos-passthrough
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Scott Moser (community) Needs Fixing
Review via email: mp+327536@code.launchpad.net

Description of the change

pass network configuration through to target for ubuntu and centos

curtin will now query the in-target cloud-init for the NETWORK_CONFIG_V2 feature flag. If present, curtin will copy in any custom networking config an defer rendering to cloud-init upon first boot. This function is also implemented for Centos/Enterprise Linux distros.

Centos network curthooks has the following capabilities
  - Only runs when 'centos_network_curthooks: true' is passed in via
    curtin config. MAAS will modify centos built-in curthooks to
    use the centos_network_curthooks as needed.
  - If the target centos image does not contain a new enough cloud-init
    then the cloud-init-el-stable repo is configured and curtin installs
    epel-release, cloud-init and bridge-utils if needed

Additional VMtests were added for Centos66 and Centos70 covering all of
the network tests. Note that network_mtu is disabled as mixed v4 and ipv6 mtu settings are not supported in sysconfig.

Other useful features in this branch:
- tools/build-deb now lets users set the RELEASE value like:
  RELEASE=xenial ./tools/build-deb
  and the RELEASE value will be set in the changelog for the deb

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
549. By Ryan Harper

Remove location debug message

550. By Ryan Harper

Use 50-curtin-networking.cfg everywhere for passthrough cloud-init net config

551. By Ryan Harper

Fix style-check which fails in tox

552. By Ryan Harper

Improve log message when writing network config into target

553. By Ryan Harper

Rework type checking in network_config_required_packages

554. By Ryan Harper

s/mock_/m_ in TestApplyNet

555. By Ryan Harper

Refactor TestApplyNet patching to reduce line-length

556. By Ryan Harper

Remove assert_not_called, replace with mock.call_count == 0

557. By Ryan Harper

Use load_collec_file when loading eni and subdirectorie configurations

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

some comments in line.
you need a good commit message.

one thing to call out is that you are moving system_upgrade to run earlier than it ran before.

Then, please make sure tha tyou fix the things i've raised in
https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/327648

review: Needs Fixing
Revision history for this message
Ryan Harper (raharper) wrote :

Thanks for the review, will update with fixes for ACKs.

558. By Ryan Harper

Clean up ipv6 mtu hook file unittests for clarity

559. By Ryan Harper

Use 'cloud-init features' to check in-target cloud-init feature availability for passthrough

560. By Ryan Harper

Clean up comment around when passthrough is used

561. By Ryan Harper

Clarify ipv6 privacy config removal message

562. By Ryan Harper

Reword centos_network_curthooks docstring

563. By Ryan Harper

Simplify removal of embedded network configuration

564. By Ryan Harper

Use return-code to check if we need to install bridge-utils, drop target in log message

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

Fix shadowing of function parameter with local value

566. By Ryan Harper

Remove exception logging when probing cloud-init features, non-fatal but fails vmtest log parsing

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

Bootstrap install of cloud-init-el-release package

- Update centos cloud-init repo configuration file to
  point to cloud-init-el-stable repository in a temporary
  repo configuration file
- Install cloud-init-el-release package to configure target
  with the correct cloud-init yum repository
- Remove the bootstrap repo configuration

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) :
568. By Ryan Harper

Move system_upgrade back to the end of curthooks

569. By Ryan Harper

Add feature flag for centos network curthook

570. By Ryan Harper

Drop curthook proxy export, will use existing http[s]_proxy support

571. By Ryan Harper

Only run centos_network_curthooks for vmtest

MAAS Centos images will call centos_network_curthooks from their built-in
curthooks, however, until new images are published, vmtest will need to
always run centos_network_curthooks; set this in the centos_default config
yaml used for all centos vmtests.

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

Drop https_proxy config

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

write a combined commit message.

I think i had some comments on the grub config... i'd like some more explanation of what is going on there (and why necessary in the default_config).

other than that... thanks for your work.

573. By Ryan Harper

Address MP feedback around debug and comments for clarity

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

I approve.
thanks for addressing all the feedback.

I have just 2 other questions.
a.) CENTOS_NETWORK_CURTHOOKS maybe NETWORK_CONFIG_SUPPORT_CENTOS ?
b.) you want me to pull this in?

I think we should see a complete run with jenkins

https://jenkins.ubuntu.com/server/view/Curtin/job/curtin-vmtest-devel-debug/build?delay=0sec
and then pull this in.
I didn't just run one because
a curtin-vmtest-devel-amd64 just started.

Revision history for this message
Ryan Harper (raharper) wrote :

A) Sure
B) Sure

Yes, let's do a vmtest run on jenkins, I've only run them on diglett with http_proxy set, so we can figure out if we need to embed the proxy values into the examples/tests/centos_default.yaml

Revision history for this message
Joshua Powers (powersj) wrote :
Revision history for this message
Joshua Powers (powersj) wrote :

I ran sync images with ryan's branch, output: http://paste.ubuntu.com/25164423/

Updated devel test to use:
export no_proxy=archive.ubuntu.com,security.ubuntu.com
export https_proxy=http://squid.internal:3128

Launched another run:
https://jenkins.ubuntu.com/server/job/curtin-vmtest-devel-debug/83/console

Revision history for this message
Scott Moser (smoser) wrote :

I merged this branch with trunk and pushed that to
 lp:~smoser/curtin/centos-passthrough

Then started a vmtest-devel-debug run of it at:
   https://jenkins.ubuntu.com/server/view/Curtin/job/curtin-vmtest-devel-debug/84/console

It is limited in that I just ran these tests:
  tests/vmtests/test_basic.py tests/vmtests/test_centos_basic.py tests/vmtests/test_network_ipv6.py

that will get us a result a bit quicker but will push us through centos and ubuntu installs.

Revision history for this message
Scott Moser (smoser) wrote :

the previous run failed, but only because we had made an unrelated change (attempted to collect tgt.d/ which had a socket file).

I've run a full run at
 https://jenkins.ubuntu.com/server/view/Curtin/job/curtin-vmtest-devel-debug/85/console

if that goes, then i'll merge this.

Revision history for this message
Scott Moser (smoser) wrote :

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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-21 22:51:41 +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-05-19 21:40:48 +0000
1711+++ tests/vmtests/test_network.py 2017-07-21 22:51:41 +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-04-26 16:14:04 +0000
2027+++ tests/vmtests/test_network_alias.py 2017-07-21 22:51:41 +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-04-26 16:14:04 +0000
2077+++ tests/vmtests/test_network_bonding.py 2017-07-21 22:51:41 +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-05-19 21:40:48 +0000
2134+++ tests/vmtests/test_network_bridging.py 2017-07-21 22:51:41 +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,100 @@
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__ = True
2281
2282+ @classmethod
2283+ def setUpClass(cls):
2284+ cls.skip_by_date(cls.__name__, cls.release, "<NoBugYet>",
2285+ (2017, 7, 10), (2017, 7, 31))
2286+ super().setUpClass()
2287+
2288
2289 class ZestyTestBridging(relbase.zesty, TestBridgeNetworkAbs):
2290 __test__ = True
2291
2292+ @classmethod
2293+ def setUpClass(cls):
2294+ cls.skip_by_date(cls.__name__, cls.release, "<NoBugYet>",
2295+ (2017, 7, 10), (2017, 7, 31))
2296+ super().setUpClass()
2297+
2298
2299 class ArtfulTestBridging(relbase.artful, TestBridgeNetworkAbs):
2300 __test__ = True
2301+
2302+ @classmethod
2303+ def setUpClass(cls):
2304+ cls.skip_by_date(cls.__name__, cls.release, "<NoBugYet>",
2305+ (2017, 7, 10), (2017, 7, 31))
2306+ super().setUpClass()
2307
2308=== modified file 'tests/vmtests/test_network_ipv6.py'
2309--- tests/vmtests/test_network_ipv6.py 2017-04-26 16:14:04 +0000
2310+++ tests/vmtests/test_network_ipv6.py 2017-07-21 22:51:41 +0000
2311@@ -1,4 +1,5 @@
2312 from .releases import base_vm_classes as relbase
2313+from .releases import centos_base_vm_classes as centos_relbase
2314 from .test_network import TestNetworkBaseTestsAbs
2315
2316 import textwrap
2317@@ -21,6 +22,24 @@
2318 """)]
2319
2320
2321+class CentosTestNetworkIPV6Abs(TestNetworkIPV6Abs):
2322+ extra_kern_args = "BOOTIF=eth0-bc:76:4e:06:96:b3"
2323+ collect_scripts = TestNetworkIPV6Abs.collect_scripts + [
2324+ textwrap.dedent("""
2325+ cd OUTPUT_COLLECT_D
2326+ cp -a /etc/sysconfig/network-scripts .
2327+ cp -a /var/log/cloud-init* .
2328+ cp -a /var/lib/cloud ./var_lib_cloud
2329+ cp -a /run/cloud-init ./run_cloud-init
2330+ """)]
2331+
2332+ def test_etc_network_interfaces(self):
2333+ pass
2334+
2335+ def test_etc_resolvconf(self):
2336+ pass
2337+
2338+
2339 class PreciseHWETTestNetwork(relbase.precise_hwe_t, TestNetworkIPV6Abs):
2340 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2341 __test__ = False
2342@@ -48,6 +67,11 @@
2343 class XenialTestNetworkIPV6(relbase.xenial, TestNetworkIPV6Abs):
2344 __test__ = True
2345
2346+ @classmethod
2347+ def test_ip_output(cls):
2348+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2349+ (2017, 7, 10), (2017, 7, 31))
2350+
2351
2352 class YakketyTestNetworkIPV6(relbase.yakkety, TestNetworkIPV6Abs):
2353 __test__ = True
2354@@ -59,3 +83,18 @@
2355
2356 class ArtfulTestNetworkIPV6(relbase.artful, TestNetworkIPV6Abs):
2357 __test__ = True
2358+
2359+ @classmethod
2360+ def test_ip_output(cls):
2361+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2362+ (2017, 7, 10), (2017, 7, 31))
2363+
2364+
2365+class Centos66TestNetworkIPV6(centos_relbase.centos66fromxenial,
2366+ CentosTestNetworkIPV6Abs):
2367+ __test__ = True
2368+
2369+
2370+class Centos70TestNetworkIPV6(centos_relbase.centos70fromxenial,
2371+ CentosTestNetworkIPV6Abs):
2372+ __test__ = True
2373
2374=== modified file 'tests/vmtests/test_network_ipv6_enisource.py'
2375--- tests/vmtests/test_network_ipv6_enisource.py 2017-04-26 16:14:04 +0000
2376+++ tests/vmtests/test_network_ipv6_enisource.py 2017-07-21 22:51:41 +0000
2377@@ -1,10 +1,16 @@
2378 from .releases import base_vm_classes as relbase
2379 from .test_network_enisource import TestNetworkENISource
2380
2381+import unittest
2382+
2383
2384 class TestNetworkIPV6ENISource(TestNetworkENISource):
2385 conf_file = "examples/tests/network_source_ipv6.yaml"
2386
2387+ @unittest.skip("FIXME: cloud-init.net needs update")
2388+ def test_etc_network_interfaces(self):
2389+ pass
2390+
2391
2392 class PreciseTestNetworkIPV6ENISource(relbase.precise,
2393 TestNetworkIPV6ENISource):
2394@@ -25,15 +31,35 @@
2395 class XenialTestNetworkIPV6ENISource(relbase.xenial, TestNetworkIPV6ENISource):
2396 __test__ = True
2397
2398+ @classmethod
2399+ def test_ip_output(cls):
2400+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2401+ (2017, 7, 10), (2017, 7, 31))
2402+
2403
2404 class YakketyTestNetworkIPV6ENISource(relbase.yakkety,
2405 TestNetworkIPV6ENISource):
2406 __test__ = True
2407
2408+ @classmethod
2409+ def test_ip_output(cls):
2410+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2411+ (2017, 7, 10), (2017, 7, 31))
2412+
2413
2414 class ZestyTestNetworkIPV6ENISource(relbase.zesty, TestNetworkIPV6ENISource):
2415 __test__ = True
2416
2417+ @classmethod
2418+ def test_ip_output(cls):
2419+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2420+ (2017, 7, 10), (2017, 7, 31))
2421+
2422
2423 class ArtfulTestNetworkIPV6ENISource(relbase.artful, TestNetworkIPV6ENISource):
2424 __test__ = True
2425+
2426+ @classmethod
2427+ def test_ip_output(cls):
2428+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2429+ (2017, 7, 10), (2017, 7, 31))
2430
2431=== modified file 'tests/vmtests/test_network_ipv6_static.py'
2432--- tests/vmtests/test_network_ipv6_static.py 2017-04-26 16:14:04 +0000
2433+++ tests/vmtests/test_network_ipv6_static.py 2017-07-21 22:51:41 +0000
2434@@ -1,5 +1,7 @@
2435 from .releases import base_vm_classes as relbase
2436-from .test_network_static import TestNetworkStaticAbs
2437+from .releases import centos_base_vm_classes as centos_relbase
2438+from .test_network_static import (TestNetworkStaticAbs,
2439+ CentosTestNetworkStaticAbs)
2440
2441
2442 # reuse basic network tests but with different config (static, no dhcp)
2443@@ -7,6 +9,10 @@
2444 conf_file = "examples/tests/basic_network_static_ipv6.yaml"
2445
2446
2447+class CentosTestNetworkIPV6StaticAbs(CentosTestNetworkStaticAbs):
2448+ conf_file = "examples/tests/basic_network_static_ipv6.yaml"
2449+
2450+
2451 class PreciseHWETTestNetworkIPV6Static(relbase.precise_hwe_t,
2452 TestNetworkIPV6StaticAbs):
2453 __test__ = True
2454@@ -53,3 +59,18 @@
2455
2456 class ArtfulTestNetworkIPV6Static(relbase.artful, TestNetworkIPV6StaticAbs):
2457 __test__ = True
2458+
2459+ @classmethod
2460+ def test_ip_output(cls):
2461+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2462+ (2017, 7, 10), (2017, 7, 31))
2463+
2464+
2465+class Centos66TestNetworkIPV6Static(centos_relbase.centos66fromxenial,
2466+ CentosTestNetworkIPV6StaticAbs):
2467+ __test__ = True
2468+
2469+
2470+class Centos70TestNetworkIPV6Static(centos_relbase.centos70fromxenial,
2471+ CentosTestNetworkIPV6StaticAbs):
2472+ __test__ = True
2473
2474=== modified file 'tests/vmtests/test_network_ipv6_vlan.py'
2475--- tests/vmtests/test_network_ipv6_vlan.py 2017-04-26 16:14:04 +0000
2476+++ tests/vmtests/test_network_ipv6_vlan.py 2017-07-21 22:51:41 +0000
2477@@ -1,11 +1,17 @@
2478 from .releases import base_vm_classes as relbase
2479-from .test_network_vlan import TestNetworkVlanAbs
2480+from .releases import centos_base_vm_classes as centos_relbase
2481+from .test_network_vlan import (TestNetworkVlanAbs,
2482+ CentosTestNetworkVlanAbs)
2483
2484
2485 class TestNetworkIPV6VlanAbs(TestNetworkVlanAbs):
2486 conf_file = "examples/tests/vlan_network_ipv6.yaml"
2487
2488
2489+class CentosTestNetworkIPV6VlanAbs(CentosTestNetworkVlanAbs):
2490+ conf_file = "examples/tests/vlan_network_ipv6.yaml"
2491+
2492+
2493 class PreciseTestNetworkIPV6Vlan(relbase.precise, TestNetworkIPV6VlanAbs):
2494 __test__ = True
2495
2496@@ -45,3 +51,13 @@
2497
2498 class ArtfulTestNetworkIPV6Vlan(relbase.artful, TestNetworkIPV6VlanAbs):
2499 __test__ = True
2500+
2501+
2502+class Centos66TestNetworkIPV6Vlan(centos_relbase.centos66fromxenial,
2503+ CentosTestNetworkIPV6VlanAbs):
2504+ __test__ = True
2505+
2506+
2507+class Centos70TestNetworkIPV6Vlan(centos_relbase.centos70fromxenial,
2508+ CentosTestNetworkIPV6VlanAbs):
2509+ __test__ = True
2510
2511=== modified file 'tests/vmtests/test_network_mtu.py'
2512--- tests/vmtests/test_network_mtu.py 2017-05-19 21:40:48 +0000
2513+++ tests/vmtests/test_network_mtu.py 2017-07-21 22:51:41 +0000
2514@@ -1,4 +1,5 @@
2515 from .releases import base_vm_classes as relbase
2516+from .releases import centos_base_vm_classes as centos_relbase
2517 from .test_network_ipv6 import TestNetworkIPV6Abs
2518
2519 import textwrap
2520@@ -25,8 +26,8 @@
2521 cd OUTPUT_COLLECT_D
2522 proc_v6="/proc/sys/net/ipv6/conf"
2523 for f in `seq 0 7`; do
2524- cat /sys/class/net/interface${f}/mtu > interface${f}_dev_mtu;
2525- cat $proc_v6/interface${f}/mtu > interface${f}_ipv6_mtu;
2526+ cat /sys/class/net/interface${f}/mtu |tee interface${f}_dev_mtu;
2527+ cat $proc_v6/interface${f}/mtu |tee interface${f}_ipv6_mtu;
2528 done
2529 if [ -e /var/log/upstart ]; then
2530 cp -a /var/log/upstart ./var_log_upstart
2531@@ -114,6 +115,50 @@
2532 self._check_iface_subnets('interface7')
2533
2534
2535+class CentosTestNetworkMtuAbs(TestNetworkMtuAbs):
2536+ conf_file = "examples/tests/network_mtu.yaml"
2537+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2538+ collect_scripts = TestNetworkMtuAbs.collect_scripts + [
2539+ textwrap.dedent("""
2540+ cd OUTPUT_COLLECT_D
2541+ cp -a /etc/sysconfig/network-scripts .
2542+ cp -a /var/log/cloud-init* .
2543+ cp -a /var/lib/cloud ./var_lib_cloud
2544+ cp -a /run/cloud-init ./run_cloud-init
2545+ """)]
2546+
2547+ def test_etc_network_interfaces(self):
2548+ pass
2549+
2550+ def test_etc_resolvconf(self):
2551+ pass
2552+
2553+ @classmethod
2554+ def test_ip_output(cls):
2555+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2556+ (2017, 7, 10), (2017, 7, 31))
2557+
2558+ @classmethod
2559+ def test_ipv6_mtu_smaller_than_ipv4_v6_iface_first(cls):
2560+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2561+ (2017, 7, 10), (2017, 7, 31))
2562+
2563+ @classmethod
2564+ def test_ipv6_mtu_smaller_than_ipv4_non_default(cls):
2565+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2566+ (2017, 7, 10), (2017, 7, 31))
2567+
2568+ @classmethod
2569+ def test_ipv6_mtu_higher_than_default_no_ipv4_iface_up(cls):
2570+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2571+ (2017, 7, 10), (2017, 7, 31))
2572+
2573+ @classmethod
2574+ def test_ipv6_mtu_higher_than_default_no_ipv4_iface_v6_iface_first(cls):
2575+ cls.skip_by_date(cls.__name__, cls.release, "1701097",
2576+ (2017, 7, 10), (2017, 7, 31))
2577+
2578+
2579 class PreciseHWETTestNetworkMtu(relbase.precise_hwe_t, TestNetworkMtuAbs):
2580 # FIXME: Precise mtu / ipv6 is buggy
2581 __test__ = False
2582@@ -162,3 +207,13 @@
2583
2584 class ArtfulTestNetworkMtu(relbase.artful, TestNetworkMtuAbs):
2585 __test__ = True
2586+
2587+
2588+class Centos66TestNetworkMtu(centos_relbase.centos66fromxenial,
2589+ CentosTestNetworkMtuAbs):
2590+ __test__ = True
2591+
2592+
2593+class Centos70TestNetworkMtu(centos_relbase.centos70fromxenial,
2594+ CentosTestNetworkMtuAbs):
2595+ __test__ = True
2596
2597=== modified file 'tests/vmtests/test_network_static.py'
2598--- tests/vmtests/test_network_static.py 2017-04-26 16:14:04 +0000
2599+++ tests/vmtests/test_network_static.py 2017-07-21 22:51:41 +0000
2600@@ -1,5 +1,7 @@
2601 from .releases import base_vm_classes as relbase
2602+from .releases import centos_base_vm_classes as centos_relbase
2603 from .test_network import TestNetworkBaseTestsAbs
2604+import textwrap
2605
2606
2607 class TestNetworkStaticAbs(TestNetworkBaseTestsAbs):
2608@@ -8,6 +10,24 @@
2609 conf_file = "examples/tests/basic_network_static.yaml"
2610
2611
2612+class CentosTestNetworkStaticAbs(TestNetworkStaticAbs):
2613+ extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00"
2614+ collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
2615+ textwrap.dedent("""
2616+ cd OUTPUT_COLLECT_D
2617+ cp -a /etc/sysconfig/network-scripts .
2618+ cp -a /var/log/cloud-init* .
2619+ cp -a /var/lib/cloud ./var_lib_cloud
2620+ cp -a /run/cloud-init ./run_cloud-init
2621+ """)]
2622+
2623+ def test_etc_network_interfaces(self):
2624+ pass
2625+
2626+ def test_etc_resolvconf(self):
2627+ pass
2628+
2629+
2630 class PreciseHWETTestNetworkStatic(relbase.precise_hwe_t,
2631 TestNetworkStaticAbs):
2632 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2633@@ -55,3 +75,13 @@
2634
2635 class ArtfulTestNetworkStatic(relbase.artful, TestNetworkStaticAbs):
2636 __test__ = True
2637+
2638+
2639+class Centos66TestNetworkStatic(centos_relbase.centos66fromxenial,
2640+ CentosTestNetworkStaticAbs):
2641+ __test__ = True
2642+
2643+
2644+class Centos70TestNetworkStatic(centos_relbase.centos70fromxenial,
2645+ CentosTestNetworkStaticAbs):
2646+ __test__ = True
2647
2648=== modified file 'tests/vmtests/test_network_static_routes.py'
2649--- tests/vmtests/test_network_static_routes.py 2017-04-26 16:14:04 +0000
2650+++ tests/vmtests/test_network_static_routes.py 2017-07-21 22:51:41 +0000
2651@@ -1,5 +1,7 @@
2652 from .releases import base_vm_classes as relbase
2653-from .test_network import TestNetworkBaseTestsAbs
2654+from .releases import centos_base_vm_classes as centos_relbase
2655+from .test_network import (TestNetworkBaseTestsAbs,
2656+ CentosTestNetworkBasicAbs)
2657
2658
2659 class TestNetworkStaticRoutesAbs(TestNetworkBaseTestsAbs):
2660@@ -8,6 +10,12 @@
2661 conf_file = "examples/tests/network_static_routes.yaml"
2662
2663
2664+class CentosTestNetworkStaticRoutesAbs(CentosTestNetworkBasicAbs):
2665+ """ Static network routes testing with ipv4
2666+ """
2667+ conf_file = "examples/tests/network_static_routes.yaml"
2668+
2669+
2670 class PreciseHWETTestNetworkStaticRoutes(relbase.precise_hwe_t,
2671 TestNetworkStaticRoutesAbs):
2672 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
2673@@ -57,3 +65,13 @@
2674 class ArtfulTestNetworkStaticRoutes(relbase.artful,
2675 TestNetworkStaticRoutesAbs):
2676 __test__ = True
2677+
2678+
2679+class Centos66TestNetworkStaticRoutes(centos_relbase.centos66fromxenial,
2680+ CentosTestNetworkStaticRoutesAbs):
2681+ __test__ = False
2682+
2683+
2684+class Centos70TestNetworkStaticRoutes(centos_relbase.centos70fromxenial,
2685+ CentosTestNetworkStaticRoutesAbs):
2686+ __test__ = False
2687
2688=== modified file 'tests/vmtests/test_network_vlan.py'
2689--- tests/vmtests/test_network_vlan.py 2017-04-26 16:14:04 +0000
2690+++ tests/vmtests/test_network_vlan.py 2017-07-21 22:51:41 +0000
2691@@ -1,5 +1,6 @@
2692 from . import logger
2693 from .releases import base_vm_classes as relbase
2694+from .releases import centos_base_vm_classes as centos_relbase
2695 from .test_network import TestNetworkBaseTestsAbs
2696
2697 import textwrap
2698@@ -12,10 +13,10 @@
2699 textwrap.dedent("""
2700 cd OUTPUT_COLLECT_D
2701 dpkg-query -W -f '${Status}' vlan > vlan_installed
2702- ip -d link show interface1.2667 > ip_link_show_interface1.2667
2703- ip -d link show interface1.2668 > ip_link_show_interface1.2668
2704- ip -d link show interface1.2669 > ip_link_show_interface1.2669
2705- ip -d link show interface1.2670 > ip_link_show_interface1.2670
2706+ ip -d link show interface1.2667 |tee ip_link_show_interface1.2667
2707+ ip -d link show interface1.2668 |tee ip_link_show_interface1.2668
2708+ ip -d link show interface1.2669 |tee ip_link_show_interface1.2669
2709+ ip -d link show interface1.2670 |tee ip_link_show_interface1.2670
2710 """)]
2711
2712 def get_vlans(self):
2713@@ -45,10 +46,32 @@
2714 # did they get configured?
2715 for vlan in self.get_vlans():
2716 link_file = "ip_link_show_" + vlan['name']
2717- vlan_msg = "vlan protocol 802.1Q id " + str(vlan['vlan_id'])
2718+ vlan_msg = "vlan.*id " + str(vlan['vlan_id'])
2719 self.check_file_regex(link_file, vlan_msg)
2720
2721
2722+class CentosTestNetworkVlanAbs(TestNetworkVlanAbs):
2723+ extra_kern_args = "BOOTIF=eth0-d4:be:d9:a8:49:13"
2724+ collect_scripts = TestNetworkVlanAbs.collect_scripts + [
2725+ textwrap.dedent("""
2726+ cd OUTPUT_COLLECT_D
2727+ cp -a /etc/sysconfig/network-scripts .
2728+ cp -a /var/log/cloud-init* .
2729+ cp -a /var/lib/cloud ./var_lib_cloud
2730+ cp -a /run/cloud-init ./run_cloud-init
2731+ """)]
2732+
2733+ def test_etc_network_interfaces(self):
2734+ pass
2735+
2736+ def test_etc_resolvconf(self):
2737+ pass
2738+
2739+ def test_vlan_installed(self):
2740+ """centos has vlan support built-in, no extra packages needed"""
2741+ pass
2742+
2743+
2744 class PreciseTestNetworkVlan(relbase.precise, TestNetworkVlanAbs):
2745 __test__ = True
2746
2747@@ -87,3 +110,13 @@
2748
2749 class ArtfulTestNetworkVlan(relbase.artful, TestNetworkVlanAbs):
2750 __test__ = True
2751+
2752+
2753+class Centos66TestNetworkVlan(centos_relbase.centos66fromxenial,
2754+ CentosTestNetworkVlanAbs):
2755+ __test__ = True
2756+
2757+
2758+class Centos70TestNetworkVlan(centos_relbase.centos70fromxenial,
2759+ CentosTestNetworkVlanAbs):
2760+ __test__ = True
2761
2762=== modified file 'tools/build-deb'
2763--- tools/build-deb 2013-09-25 21:04:22 +0000
2764+++ tools/build-deb 2017-07-21 22:51:41 +0000
2765@@ -5,6 +5,7 @@
2766 sourcename="curtin"
2767 TEMP_D=""
2768 UNCOMMITTED=${UNCOMMITTED:-0}
2769+RELEASE=${RELEASE:-UNRELEASED}
2770
2771 fail() { echo "$@" 1>&2; exit 1; }
2772 cleanup() {
2773@@ -67,7 +68,8 @@
2774 mv "$f" "${f%.trunk}"
2775 done
2776
2777-sed -i "1s,${clogver_o},${clogver_new}," debian/changelog ||
2778+sed -i -e "1s,${clogver_o},${clogver_new}," \
2779+ -e "1s,UNRELEASED,${RELEASE}," debian/changelog ||
2780 fail "failed to write debian/changelog"
2781 debuild "$@" || fail "debuild failed"
2782

Subscribers

People subscribed via source and target branches