Merge lp:~raharper/curtin/centos-passthrough into lp:~curtin-dev/curtin/trunk
- centos-passthrough
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
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_
curtin config. MAAS will modify centos built-in curthooks to
use the centos_
- If the target centos image does not contain a new enough cloud-init
then the cloud-init-
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
Server Team CI bot (server-team-bot) wrote : | # |
- 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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:557
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
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:/
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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:564
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:565
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:566
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:567
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
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.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:571
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 572. By Ryan Harper
-
Drop https_proxy config
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:572
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:573
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
I approve.
thanks for addressing all the feedback.
I have just 2 other questions.
a.) CENTOS_
b.) you want me to pull this in?
I think we should see a complete run with jenkins
https:/
and then pull this in.
I didn't just run one because
a curtin-
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/
Joshua Powers (powersj) wrote : | # |
Joshua Powers (powersj) wrote : | # |
I ran sync images with ryan's branch, output: http://
Updated devel test to use:
export no_proxy=
export https_proxy=http://
Launched another run:
https:/
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:/
It is limited in that I just ran these tests:
tests/
that will get us a result a bit quicker but will push us through centos and ubuntu installs.
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:/
if that goes, then i'll merge this.
Scott Moser (smoser) wrote : | # |
i've got another run of my branch going at
https:/
i went ahead and submitted that for merge at
https:/
Preview Diff
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 |
FAILED: Continuous integration, rev:548 /jenkins. ubuntu. com/server/ job/curtin- ci/533/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-amd64/ 533/console /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-arm64/ 533/console /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-ppc64el/ 533/console /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-s390x/ 533/console /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= vm-i386/ 533/console
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/curtin- ci/533/ rebuild
https:/