Merge lp:~chad.smith/curtin/xenial-sru-1721808 into lp:~curtin-dev/curtin/xenial
- xenial-sru-1721808
- Merge into xenial
Proposed by
Chad Smith
Status: | Merged | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 71 | ||||||||||||||||
Proposed branch: | lp:~chad.smith/curtin/xenial-sru-1721808 | ||||||||||||||||
Merge into: | lp:~curtin-dev/curtin/xenial | ||||||||||||||||
Diff against target: |
7045 lines (+3340/-962) 91 files modified
curtin/__init__.py (+2/-0) curtin/block/__init__.py (+69/-20) curtin/block/iscsi.py (+44/-3) curtin/block/mdadm.py (+10/-6) curtin/commands/apply_net.py (+34/-8) curtin/commands/apt_config.py (+0/-9) curtin/commands/curthooks.py (+197/-94) curtin/commands/extract.py (+6/-0) curtin/commands/install.py (+44/-4) curtin/futil.py (+24/-1) curtin/net/__init__.py (+106/-0) curtin/reporter/handlers.py (+42/-0) curtin/util.py (+137/-13) debian/changelog (+33/-0) doc/index.rst (+1/-0) doc/topics/apt_source.rst (+9/-6) doc/topics/config.rst (+18/-0) doc/topics/curthooks.rst (+109/-0) doc/topics/integration-testing.rst (+6/-0) doc/topics/networking.rst (+2/-0) doc/topics/overview.rst (+45/-47) doc/topics/reporting.rst (+29/-0) doc/topics/storage.rst (+2/-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 (+91/-0) examples/tests/journald_reporter.yaml (+20/-0) examples/tests/network_alias.yaml (+29/-31) examples/tests/network_static_routes.yaml (+10/-15) examples/tests/network_v2_passthrough.yaml (+8/-0) setup.py (+16/-2) tests/unittests/helpers.py (+36/-0) tests/unittests/test_apt_custom_sources_list.py (+3/-6) tests/unittests/test_apt_source.py (+4/-7) tests/unittests/test_basic.py (+4/-4) tests/unittests/test_block.py (+20/-36) tests/unittests/test_block_iscsi.py (+187/-18) tests/unittests/test_block_lvm.py (+2/-2) tests/unittests/test_block_mdadm.py (+10/-22) tests/unittests/test_block_mkfs.py (+2/-2) tests/unittests/test_clear_holders.py (+5/-5) tests/unittests/test_commands_apply_net.py (+334/-0) tests/unittests/test_commands_block_meta.py (+6/-19) tests/unittests/test_commands_install.py (+22/-0) tests/unittests/test_config.py (+6/-6) tests/unittests/test_curthooks.py (+241/-57) tests/unittests/test_feature.py (+5/-2) tests/unittests/test_gpg.py (+4/-4) tests/unittests/test_make_dname.py (+4/-4) tests/unittests/test_net.py (+99/-24) tests/unittests/test_partitioning.py (+4/-3) tests/unittests/test_public.py (+54/-0) tests/unittests/test_reporter.py (+29/-38) tests/unittests/test_util.py (+201/-52) tests/unittests/test_version.py (+7/-19) tests/vmtests/__init__.py (+59/-7) tests/vmtests/releases.py (+0/-15) tests/vmtests/test_apt_config_cmd.py (+0/-4) tests/vmtests/test_basic.py (+0/-13) tests/vmtests/test_bcache_basic.py (+0/-4) tests/vmtests/test_centos_basic.py (+35/-0) tests/vmtests/test_iscsi.py (+0/-4) tests/vmtests/test_journald_reporter.py (+52/-0) tests/vmtests/test_lvm.py (+0/-9) tests/vmtests/test_lvm_iscsi.py (+4/-4) tests/vmtests/test_mdadm_bcache.py (+3/-59) tests/vmtests/test_mdadm_iscsi.py (+4/-4) tests/vmtests/test_multipath.py (+0/-4) tests/vmtests/test_network.py (+202/-39) tests/vmtests/test_network_alias.py (+33/-4) tests/vmtests/test_network_bonding.py (+47/-22) tests/vmtests/test_network_bridging.py (+77/-17) tests/vmtests/test_network_enisource.py (+2/-8) tests/vmtests/test_network_ipv6.py (+29/-4) tests/vmtests/test_network_ipv6_enisource.py (+8/-6) tests/vmtests/test_network_ipv6_static.py (+17/-5) tests/vmtests/test_network_ipv6_vlan.py (+17/-5) tests/vmtests/test_network_mtu.py (+61/-8) tests/vmtests/test_network_static.py (+30/-4) tests/vmtests/test_network_static_routes.py (+19/-6) tests/vmtests/test_network_vlan.py (+40/-15) tests/vmtests/test_nvme.py (+0/-9) tests/vmtests/test_raid5_bcache.py (+0/-9) tests/vmtests/test_simple.py (+0/-4) tests/vmtests/test_uefi_basic.py (+0/-19) tools/build-deb (+3/-1) tools/curtainer (+14/-8) tools/find-tgt (+54/-29) tools/jenkins-runner (+47/-10) tools/launch (+46/-7) |
||||||||||||||||
To merge this branch: | bzr merge lp:~chad.smith/curtin/xenial-sru-1721808 | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
curtin developers | Pending | ||
Review via email: mp+331961@code.launchpad.net |
Commit message
Description of the change
Upstream snapshot from Xenial for SRU.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'curtin/__init__.py' | |||
2 | --- curtin/__init__.py 2017-06-12 20:39:06 +0000 | |||
3 | +++ curtin/__init__.py 2017-10-06 16:35:22 +0000 | |||
4 | @@ -23,6 +23,8 @@ | |||
5 | 23 | # can determine which features are supported. Each entry should have | 23 | # can determine which features are supported. Each entry should have |
6 | 24 | # a consistent meaning. | 24 | # a consistent meaning. |
7 | 25 | FEATURES = [ | 25 | FEATURES = [ |
8 | 26 | # curtin can apply centos networking via centos_apply_network_config | ||
9 | 27 | 'CENTOS_APPLY_NETWORK_CONFIG', | ||
10 | 26 | # install supports the 'network' config version 1 | 28 | # install supports the 'network' config version 1 |
11 | 27 | 'NETWORK_CONFIG_V1', | 29 | 'NETWORK_CONFIG_V1', |
12 | 28 | # reporter supports 'webhook' type | 30 | # reporter supports 'webhook' type |
13 | 29 | 31 | ||
14 | === modified file 'curtin/block/__init__.py' | |||
15 | --- curtin/block/__init__.py 2017-06-12 20:39:06 +0000 | |||
16 | +++ curtin/block/__init__.py 2017-10-06 16:35:22 +0000 | |||
17 | @@ -19,7 +19,6 @@ | |||
18 | 19 | import errno | 19 | import errno |
19 | 20 | import itertools | 20 | import itertools |
20 | 21 | import os | 21 | import os |
21 | 22 | import shlex | ||
22 | 23 | import stat | 22 | import stat |
23 | 24 | import sys | 23 | import sys |
24 | 25 | import tempfile | 24 | import tempfile |
25 | @@ -204,30 +203,13 @@ | |||
26 | 204 | return [path_to_kname(device)] | 203 | return [path_to_kname(device)] |
27 | 205 | 204 | ||
28 | 206 | 205 | ||
29 | 207 | def _shlex_split(str_in): | ||
30 | 208 | # shlex.split takes a string | ||
31 | 209 | # but in python2 if input here is a unicode, encode it to a string. | ||
32 | 210 | # http://stackoverflow.com/questions/2365411/ | ||
33 | 211 | # python-convert-unicode-to-ascii-without-errors | ||
34 | 212 | if sys.version_info.major == 2: | ||
35 | 213 | try: | ||
36 | 214 | if isinstance(str_in, unicode): | ||
37 | 215 | str_in = str_in.encode('utf-8') | ||
38 | 216 | except NameError: | ||
39 | 217 | pass | ||
40 | 218 | |||
41 | 219 | return shlex.split(str_in) | ||
42 | 220 | else: | ||
43 | 221 | return shlex.split(str_in) | ||
44 | 222 | |||
45 | 223 | |||
46 | 224 | def _lsblock_pairs_to_dict(lines): | 206 | def _lsblock_pairs_to_dict(lines): |
47 | 225 | """ | 207 | """ |
48 | 226 | parse lsblock output and convert to dict | 208 | parse lsblock output and convert to dict |
49 | 227 | """ | 209 | """ |
50 | 228 | ret = {} | 210 | ret = {} |
51 | 229 | for line in lines.splitlines(): | 211 | for line in lines.splitlines(): |
53 | 230 | toks = _shlex_split(line) | 212 | toks = util.shlex_split(line) |
54 | 231 | cur = {} | 213 | cur = {} |
55 | 232 | for tok in toks: | 214 | for tok in toks: |
56 | 233 | k, v = tok.split("=", 1) | 215 | k, v = tok.split("=", 1) |
57 | @@ -468,7 +450,7 @@ | |||
58 | 468 | for line in out.splitlines(): | 450 | for line in out.splitlines(): |
59 | 469 | curdev, curdata = line.split(":", 1) | 451 | curdev, curdata = line.split(":", 1) |
60 | 470 | data[curdev] = dict(tok.split('=', 1) | 452 | data[curdev] = dict(tok.split('=', 1) |
62 | 471 | for tok in _shlex_split(curdata)) | 453 | for tok in util.shlex_split(curdata)) |
63 | 472 | return data | 454 | return data |
64 | 473 | 455 | ||
65 | 474 | 456 | ||
66 | @@ -978,4 +960,71 @@ | |||
67 | 978 | else: | 960 | else: |
68 | 979 | raise ValueError("wipe mode %s not supported" % mode) | 961 | raise ValueError("wipe mode %s not supported" % mode) |
69 | 980 | 962 | ||
70 | 963 | |||
71 | 964 | def storage_config_required_packages(storage_config, mapping): | ||
72 | 965 | """Read storage configuration dictionary and determine | ||
73 | 966 | which packages are required for the supplied configuration | ||
74 | 967 | to function. Return a list of packaged to install. | ||
75 | 968 | """ | ||
76 | 969 | |||
77 | 970 | if not storage_config or not isinstance(storage_config, dict): | ||
78 | 971 | raise ValueError('Invalid storage configuration. ' | ||
79 | 972 | 'Must be a dict:\n %s' % storage_config) | ||
80 | 973 | |||
81 | 974 | if not mapping or not isinstance(mapping, dict): | ||
82 | 975 | raise ValueError('Invalid storage mapping. Must be a dict') | ||
83 | 976 | |||
84 | 977 | if 'storage' in storage_config: | ||
85 | 978 | storage_config = storage_config.get('storage') | ||
86 | 979 | |||
87 | 980 | needed_packages = [] | ||
88 | 981 | |||
89 | 982 | # get reqs by device operation type | ||
90 | 983 | dev_configs = set(operation['type'] | ||
91 | 984 | for operation in storage_config['config']) | ||
92 | 985 | |||
93 | 986 | for dev_type in dev_configs: | ||
94 | 987 | if dev_type in mapping: | ||
95 | 988 | needed_packages.extend(mapping[dev_type]) | ||
96 | 989 | |||
97 | 990 | # for any format operations, check the fstype and | ||
98 | 991 | # determine if we need any mkfs tools as well. | ||
99 | 992 | format_configs = set([operation['fstype'] | ||
100 | 993 | for operation in storage_config['config'] | ||
101 | 994 | if operation['type'] == 'format']) | ||
102 | 995 | for format_type in format_configs: | ||
103 | 996 | if format_type in mapping: | ||
104 | 997 | needed_packages.extend(mapping[format_type]) | ||
105 | 998 | |||
106 | 999 | return needed_packages | ||
107 | 1000 | |||
108 | 1001 | |||
109 | 1002 | def detect_required_packages_mapping(): | ||
110 | 1003 | """Return a dictionary providing a versioned configuration which maps | ||
111 | 1004 | storage configuration elements to the packages which are required | ||
112 | 1005 | for functionality. | ||
113 | 1006 | |||
114 | 1007 | The mapping key is either a config type value, or an fstype value. | ||
115 | 1008 | |||
116 | 1009 | """ | ||
117 | 1010 | version = 1 | ||
118 | 1011 | mapping = { | ||
119 | 1012 | version: { | ||
120 | 1013 | 'handler': storage_config_required_packages, | ||
121 | 1014 | 'mapping': { | ||
122 | 1015 | 'bcache': ['bcache-tools'], | ||
123 | 1016 | 'btrfs': ['btrfs-tools'], | ||
124 | 1017 | 'ext2': ['e2fsprogs'], | ||
125 | 1018 | 'ext3': ['e2fsprogs'], | ||
126 | 1019 | 'ext4': ['e2fsprogs'], | ||
127 | 1020 | 'lvm_partition': ['lvm2'], | ||
128 | 1021 | 'lvm_volgroup': ['lvm2'], | ||
129 | 1022 | 'raid': ['mdadm'], | ||
130 | 1023 | 'xfs': ['xfsprogs'] | ||
131 | 1024 | }, | ||
132 | 1025 | }, | ||
133 | 1026 | } | ||
134 | 1027 | return mapping | ||
135 | 1028 | |||
136 | 1029 | |||
137 | 981 | # vi: ts=4 expandtab syntax=python | 1030 | # vi: ts=4 expandtab syntax=python |
138 | 982 | 1031 | ||
139 | === modified file 'curtin/block/iscsi.py' | |||
140 | --- curtin/block/iscsi.py 2017-06-12 20:39:06 +0000 | |||
141 | +++ curtin/block/iscsi.py 2017-10-06 16:35:22 +0000 | |||
142 | @@ -195,6 +195,15 @@ | |||
143 | 195 | return target_nodes_location | 195 | return target_nodes_location |
144 | 196 | 196 | ||
145 | 197 | 197 | ||
146 | 198 | def restart_iscsi_service(): | ||
147 | 199 | LOG.info('restarting iscsi service') | ||
148 | 200 | if util.uses_systemd(): | ||
149 | 201 | cmd = ['systemctl', 'reload-or-restart', 'open-iscsi'] | ||
150 | 202 | else: | ||
151 | 203 | cmd = ['service', 'open-iscsi', 'restart'] | ||
152 | 204 | util.subp(cmd, capture=True) | ||
153 | 205 | |||
154 | 206 | |||
155 | 198 | def save_iscsi_config(iscsi_disk): | 207 | def save_iscsi_config(iscsi_disk): |
156 | 199 | state = util.load_command_environment() | 208 | state = util.load_command_environment() |
157 | 200 | # A nodes directory will be created in the same directory as the | 209 | # A nodes directory will be created in the same directory as the |
158 | @@ -238,11 +247,35 @@ | |||
159 | 238 | return _ISCSI_DISKS | 247 | return _ISCSI_DISKS |
160 | 239 | 248 | ||
161 | 240 | 249 | ||
162 | 250 | def get_iscsi_disks_from_config(cfg): | ||
163 | 251 | """Parse a curtin storage config and return a list | ||
164 | 252 | of iscsi disk objects for each configuration present | ||
165 | 253 | """ | ||
166 | 254 | if not cfg: | ||
167 | 255 | cfg = {} | ||
168 | 256 | |||
169 | 257 | sconfig = cfg.get('storage', {}).get('config', {}) | ||
170 | 258 | if not sconfig: | ||
171 | 259 | LOG.warning('Configuration dictionary did not contain' | ||
172 | 260 | ' a storage configuration') | ||
173 | 261 | return [] | ||
174 | 262 | |||
175 | 263 | # Construct IscsiDisk objects for each iscsi volume present | ||
176 | 264 | iscsi_disks = [IscsiDisk(disk['path']) for disk in sconfig | ||
177 | 265 | if disk['type'] == 'disk' and | ||
178 | 266 | disk.get('path', "").startswith('iscsi:')] | ||
179 | 267 | LOG.debug('Found %s iscsi disks in storage config', len(iscsi_disks)) | ||
180 | 268 | return iscsi_disks | ||
181 | 269 | |||
182 | 270 | |||
183 | 241 | def disconnect_target_disks(target_root_path=None): | 271 | def disconnect_target_disks(target_root_path=None): |
184 | 242 | target_nodes_path = util.target_path(target_root_path, '/etc/iscsi/nodes') | 272 | target_nodes_path = util.target_path(target_root_path, '/etc/iscsi/nodes') |
185 | 243 | fails = [] | 273 | fails = [] |
186 | 244 | if os.path.isdir(target_nodes_path): | 274 | if os.path.isdir(target_nodes_path): |
187 | 245 | for target in os.listdir(target_nodes_path): | 275 | for target in os.listdir(target_nodes_path): |
188 | 276 | if target not in iscsiadm_sessions(): | ||
189 | 277 | LOG.debug('iscsi target %s not active, skipping', target) | ||
190 | 278 | continue | ||
191 | 246 | # conn is "host,port,lun" | 279 | # conn is "host,port,lun" |
192 | 247 | for conn in os.listdir( | 280 | for conn in os.listdir( |
193 | 248 | os.path.sep.join([target_nodes_path, target])): | 281 | os.path.sep.join([target_nodes_path, target])): |
194 | @@ -254,7 +287,9 @@ | |||
195 | 254 | fails.append(target) | 287 | fails.append(target) |
196 | 255 | LOG.warn("Unable to logout of iSCSI target %s: %s", | 288 | LOG.warn("Unable to logout of iSCSI target %s: %s", |
197 | 256 | target, e) | 289 | target, e) |
199 | 257 | 290 | else: | |
200 | 291 | LOG.warning('Skipping disconnect: failed to find iscsi nodes path: %s', | ||
201 | 292 | target_nodes_path) | ||
202 | 258 | if fails: | 293 | if fails: |
203 | 259 | raise RuntimeError( | 294 | raise RuntimeError( |
204 | 260 | "Unable to logout of iSCSI targets: %s" % ', '.join(fails)) | 295 | "Unable to logout of iSCSI targets: %s" % ', '.join(fails)) |
205 | @@ -414,9 +449,15 @@ | |||
206 | 414 | 449 | ||
207 | 415 | def disconnect(self): | 450 | def disconnect(self): |
208 | 416 | if self.target not in iscsiadm_sessions(): | 451 | if self.target not in iscsiadm_sessions(): |
209 | 452 | LOG.warning('Iscsi target %s not in active iscsi sessions', | ||
210 | 453 | self.target) | ||
211 | 417 | return | 454 | return |
212 | 418 | 455 | ||
215 | 419 | util.subp(['sync']) | 456 | try: |
216 | 420 | iscsiadm_logout(self.target, self.portal) | 457 | util.subp(['sync']) |
217 | 458 | iscsiadm_logout(self.target, self.portal) | ||
218 | 459 | except util.ProcessExecutionError as e: | ||
219 | 460 | LOG.warn("Unable to logout of iSCSI target %s from portal %s: %s", | ||
220 | 461 | self.target, self.portal, e) | ||
221 | 421 | 462 | ||
222 | 422 | # vi: ts=4 expandtab syntax=python | 463 | # vi: ts=4 expandtab syntax=python |
223 | 423 | 464 | ||
224 | === modified file 'curtin/block/mdadm.py' | |||
225 | --- curtin/block/mdadm.py 2017-06-12 20:39:06 +0000 | |||
226 | +++ curtin/block/mdadm.py 2017-10-06 16:35:22 +0000 | |||
227 | @@ -273,7 +273,11 @@ | |||
228 | 273 | LOG.debug('%s/sync_max = %s', sync_action, val) | 273 | LOG.debug('%s/sync_max = %s', sync_action, val) |
229 | 274 | if val != "idle": | 274 | if val != "idle": |
230 | 275 | LOG.debug("mdadm: setting array sync_action=idle") | 275 | LOG.debug("mdadm: setting array sync_action=idle") |
232 | 276 | util.write_file(sync_action, content="idle") | 276 | try: |
233 | 277 | util.write_file(sync_action, content="idle") | ||
234 | 278 | except (IOError, OSError) as e: | ||
235 | 279 | LOG.debug("mdadm: (non-fatal) write to %s failed %s", | ||
236 | 280 | sync_action, e) | ||
237 | 277 | 281 | ||
238 | 278 | # Setting the sync_{max,min} may can help prevent the array from | 282 | # Setting the sync_{max,min} may can help prevent the array from |
239 | 279 | # changing back to 'resync' which may prevent the array from being | 283 | # changing back to 'resync' which may prevent the array from being |
240 | @@ -283,11 +287,11 @@ | |||
241 | 283 | if val != "0": | 287 | if val != "0": |
242 | 284 | LOG.debug("mdadm: setting array sync_{min,max}=0") | 288 | LOG.debug("mdadm: setting array sync_{min,max}=0") |
243 | 285 | try: | 289 | try: |
249 | 286 | util.write_file(sync_max, content="0") | 290 | for sync_file in [sync_max, sync_min]: |
250 | 287 | util.write_file(sync_min, content="0") | 291 | util.write_file(sync_file, content="0") |
251 | 288 | except IOError: | 292 | except (IOError, OSError) as e: |
252 | 289 | LOG.warning('mdadm: failed to set sync_{max,min} values') | 293 | LOG.debug('mdadm: (non-fatal) write to %s failed %s', |
253 | 290 | pass | 294 | sync_file, e) |
254 | 291 | 295 | ||
255 | 292 | # one wonders why this command doesn't do any of the above itself? | 296 | # one wonders why this command doesn't do any of the above itself? |
256 | 293 | out, err = util.subp(["mdadm", "--manage", "--stop", devpath], | 297 | out, err = util.subp(["mdadm", "--manage", "--stop", devpath], |
257 | 294 | 298 | ||
258 | === modified file 'curtin/commands/apply_net.py' | |||
259 | --- curtin/commands/apply_net.py 2017-02-08 22:22:44 +0000 | |||
260 | +++ curtin/commands/apply_net.py 2017-10-06 16:35:22 +0000 | |||
261 | @@ -21,6 +21,7 @@ | |||
262 | 21 | from .. import log | 21 | from .. import log |
263 | 22 | import curtin.net as net | 22 | import curtin.net as net |
264 | 23 | import curtin.util as util | 23 | import curtin.util as util |
265 | 24 | from curtin import config | ||
266 | 24 | from . import populate_one_subcmd | 25 | from . import populate_one_subcmd |
267 | 25 | 26 | ||
268 | 26 | 27 | ||
269 | @@ -89,15 +90,38 @@ | |||
270 | 89 | sys.stderr.write(msg + "\n") | 90 | sys.stderr.write(msg + "\n") |
271 | 90 | raise Exception(msg) | 91 | raise Exception(msg) |
272 | 91 | 92 | ||
273 | 93 | passthrough = False | ||
274 | 92 | if network_state: | 94 | if network_state: |
275 | 95 | # NB: we cannot support passthrough until curtin can convert from | ||
276 | 96 | # network_state to network-config yaml | ||
277 | 93 | ns = net.network_state.from_state_file(network_state) | 97 | ns = net.network_state.from_state_file(network_state) |
278 | 98 | raise ValueError('Not Supported; curtin lacks a network_state to ' | ||
279 | 99 | 'network_config converter.') | ||
280 | 94 | elif network_config: | 100 | elif network_config: |
284 | 95 | ns = net.parse_net_config(network_config) | 101 | netcfg = config.load_config(network_config) |
285 | 96 | 102 | ||
286 | 97 | net.render_network_state(target=target, network_state=ns) | 103 | # curtin will pass-through the netconfig into the target |
287 | 104 | # for rendering at runtime unless the target OS does not | ||
288 | 105 | # support NETWORK_CONFIG_V2 feature. | ||
289 | 106 | LOG.info('Checking cloud-init in target [%s] for network ' | ||
290 | 107 | 'configuration passthrough support.', target) | ||
291 | 108 | try: | ||
292 | 109 | passthrough = net.netconfig_passthrough_available(target) | ||
293 | 110 | except util.ProcessExecutionError: | ||
294 | 111 | LOG.warning('Failed to determine if passthrough is available') | ||
295 | 112 | |||
296 | 113 | if passthrough: | ||
297 | 114 | LOG.info('Passing network configuration through to target: %s', | ||
298 | 115 | target) | ||
299 | 116 | net.render_netconfig_passthrough(target, netconfig=netcfg) | ||
300 | 117 | else: | ||
301 | 118 | ns = net.parse_net_config_data(netcfg.get('network', {})) | ||
302 | 119 | |||
303 | 120 | if not passthrough: | ||
304 | 121 | LOG.info('Rendering network configuration in target') | ||
305 | 122 | net.render_network_state(target=target, network_state=ns) | ||
306 | 98 | 123 | ||
307 | 99 | _maybe_remove_legacy_eth0(target) | 124 | _maybe_remove_legacy_eth0(target) |
308 | 100 | LOG.info('Attempting to remove ipv6 privacy extensions') | ||
309 | 101 | _disable_ipv6_privacy_extensions(target) | 125 | _disable_ipv6_privacy_extensions(target) |
310 | 102 | _patch_ifupdown_ipv6_mtu_hook(target) | 126 | _patch_ifupdown_ipv6_mtu_hook(target) |
311 | 103 | 127 | ||
312 | @@ -130,6 +154,7 @@ | |||
313 | 130 | by default; this races with the cloud-image desire to disable them. | 154 | by default; this races with the cloud-image desire to disable them. |
314 | 131 | Resolve this by allowing the cloud-image setting to win. """ | 155 | Resolve this by allowing the cloud-image setting to win. """ |
315 | 132 | 156 | ||
316 | 157 | LOG.debug('Attempting to remove ipv6 privacy extensions') | ||
317 | 133 | cfg = util.target_path(target, path=path) | 158 | cfg = util.target_path(target, path=path) |
318 | 134 | if not os.path.exists(cfg): | 159 | if not os.path.exists(cfg): |
319 | 135 | LOG.warn('Failed to find ipv6 privacy conf file %s', cfg) | 160 | LOG.warn('Failed to find ipv6 privacy conf file %s', cfg) |
320 | @@ -143,7 +168,7 @@ | |||
321 | 143 | lines = [f.strip() for f in contents.splitlines() | 168 | lines = [f.strip() for f in contents.splitlines() |
322 | 144 | if not f.startswith("#")] | 169 | if not f.startswith("#")] |
323 | 145 | if lines == known_contents: | 170 | if lines == known_contents: |
325 | 146 | LOG.info('deleting file: %s', cfg) | 171 | LOG.info('Removing ipv6 privacy extension config file: %s', cfg) |
326 | 147 | util.del_file(cfg) | 172 | util.del_file(cfg) |
327 | 148 | msg = "removed %s with known contents" % cfg | 173 | msg = "removed %s with known contents" % cfg |
328 | 149 | curtin_contents = '\n'.join( | 174 | curtin_contents = '\n'.join( |
329 | @@ -153,9 +178,10 @@ | |||
330 | 153 | "# net.ipv6.conf.default.use_tempaddr = 2"]) | 178 | "# net.ipv6.conf.default.use_tempaddr = 2"]) |
331 | 154 | util.write_file(cfg, curtin_contents) | 179 | util.write_file(cfg, curtin_contents) |
332 | 155 | else: | 180 | else: |
336 | 156 | LOG.info('skipping, content didnt match') | 181 | LOG.debug('skipping removal of %s, expected content not found', |
337 | 157 | LOG.debug("found content:\n%s", lines) | 182 | cfg) |
338 | 158 | LOG.debug("expected contents:\n%s", known_contents) | 183 | LOG.debug("Found content in file %s:\n%s", cfg, lines) |
339 | 184 | LOG.debug("Expected contents in file %s:\n%s", cfg, known_contents) | ||
340 | 159 | msg = (bmsg + " '%s' exists with user configured content." % cfg) | 185 | msg = (bmsg + " '%s' exists with user configured content." % cfg) |
341 | 160 | except Exception as e: | 186 | except Exception as e: |
342 | 161 | msg = bmsg + " %s exists, but could not be read. %s" % (cfg, e) | 187 | msg = bmsg + " %s exists, but could not be read. %s" % (cfg, e) |
343 | 162 | 188 | ||
344 | === modified file 'curtin/commands/apt_config.py' | |||
345 | --- curtin/commands/apt_config.py 2017-03-01 16:13:56 +0000 | |||
346 | +++ curtin/commands/apt_config.py 2017-10-06 16:35:22 +0000 | |||
347 | @@ -24,7 +24,6 @@ | |||
348 | 24 | import os | 24 | import os |
349 | 25 | import re | 25 | import re |
350 | 26 | import sys | 26 | import sys |
351 | 27 | import time | ||
352 | 28 | import yaml | 27 | import yaml |
353 | 29 | 28 | ||
354 | 30 | from curtin.log import LOG | 29 | from curtin.log import LOG |
355 | @@ -406,20 +405,12 @@ | |||
356 | 406 | if aa_repo_match(source): | 405 | if aa_repo_match(source): |
357 | 407 | with util.ChrootableTarget( | 406 | with util.ChrootableTarget( |
358 | 408 | target, sys_resolvconf=True) as in_chroot: | 407 | target, sys_resolvconf=True) as in_chroot: |
359 | 409 | time_entered = time.time() | ||
360 | 410 | try: | 408 | try: |
361 | 411 | in_chroot.subp(["add-apt-repository", source], | 409 | in_chroot.subp(["add-apt-repository", source], |
362 | 412 | retries=(1, 2, 5, 10)) | 410 | retries=(1, 2, 5, 10)) |
363 | 413 | except util.ProcessExecutionError: | 411 | except util.ProcessExecutionError: |
364 | 414 | LOG.exception("add-apt-repository failed.") | 412 | LOG.exception("add-apt-repository failed.") |
365 | 415 | raise | 413 | raise |
366 | 416 | finally: | ||
367 | 417 | # workaround to gnupg >=2.x spawning daemons (LP: #1645680) | ||
368 | 418 | seconds_since = time.time() - time_entered + 1 | ||
369 | 419 | in_chroot.subp(['killall', '--wait', '--quiet', | ||
370 | 420 | '--younger-than', '%ds' % seconds_since, | ||
371 | 421 | '--regexp', '(dirmngr|gpg-agent)'], | ||
372 | 422 | rcs=[0, 1]) | ||
373 | 423 | continue | 414 | continue |
374 | 424 | 415 | ||
375 | 425 | sourcefn = util.target_path(target, ent['filename']) | 416 | sourcefn = util.target_path(target, ent['filename']) |
376 | 426 | 417 | ||
377 | === modified file 'curtin/commands/curthooks.py' | |||
378 | --- curtin/commands/curthooks.py 2017-06-12 20:39:06 +0000 | |||
379 | +++ curtin/commands/curthooks.py 2017-10-06 16:35:22 +0000 | |||
380 | @@ -16,6 +16,7 @@ | |||
381 | 16 | # along with Curtin. If not, see <http://www.gnu.org/licenses/>. | 16 | # along with Curtin. If not, see <http://www.gnu.org/licenses/>. |
382 | 17 | 17 | ||
383 | 18 | import copy | 18 | import copy |
384 | 19 | import glob | ||
385 | 19 | import os | 20 | import os |
386 | 20 | import platform | 21 | import platform |
387 | 21 | import re | 22 | import re |
388 | @@ -25,6 +26,7 @@ | |||
389 | 25 | 26 | ||
390 | 26 | from curtin import config | 27 | from curtin import config |
391 | 27 | from curtin import block | 28 | from curtin import block |
392 | 29 | from curtin import net | ||
393 | 28 | from curtin import futil | 30 | from curtin import futil |
394 | 29 | from curtin.log import LOG | 31 | from curtin.log import LOG |
395 | 30 | from curtin import swap | 32 | from curtin import swap |
396 | @@ -65,28 +67,18 @@ | |||
397 | 65 | } | 67 | } |
398 | 66 | } | 68 | } |
399 | 67 | 69 | ||
422 | 68 | 70 | CLOUD_INIT_YUM_REPO_TEMPLATE = """ | |
423 | 69 | def write_files(cfg, target): | 71 | [group_cloud-init-el-stable] |
424 | 70 | # this takes 'write_files' entry in config and writes files in the target | 72 | name=Copr repo for el-stable owned by @cloud-init |
425 | 71 | # config entry example: | 73 | baseurl=https://copr-be.cloud.fedoraproject.org/results/@cloud-init/el-stable/epel-%s-$basearch/ |
426 | 72 | # f1: | 74 | type=rpm-md |
427 | 73 | # path: /file1 | 75 | skip_if_unavailable=True |
428 | 74 | # content: !!binary | | 76 | gpgcheck=1 |
429 | 75 | # f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAwARAAAAAAABAAAAAAAAAAJAVAAAAAAA | 77 | gpgkey=https://copr-be.cloud.fedoraproject.org/results/@cloud-init/el-stable/pubkey.gpg |
430 | 76 | # f2: {path: /file2, content: "foobar", permissions: '0666'} | 78 | repo_gpgcheck=0 |
431 | 77 | if 'write_files' not in cfg: | 79 | enabled=1 |
432 | 78 | return | 80 | enabled_metadata=1 |
433 | 79 | 81 | """ | |
412 | 80 | for (key, info) in cfg.get('write_files').items(): | ||
413 | 81 | if not info.get('path'): | ||
414 | 82 | LOG.warn("Warning, write_files[%s] had no 'path' entry", key) | ||
415 | 83 | continue | ||
416 | 84 | |||
417 | 85 | futil.write_finfo(path=target + os.path.sep + info['path'], | ||
418 | 86 | content=info.get('content', ''), | ||
419 | 87 | owner=info.get('owner', "-1:-1"), | ||
420 | 88 | perms=info.get('permissions', | ||
421 | 89 | info.get('perms', "0644"))) | ||
434 | 90 | 82 | ||
435 | 91 | 83 | ||
436 | 92 | def do_apt_config(cfg, target): | 84 | def do_apt_config(cfg, target): |
437 | @@ -142,15 +134,9 @@ | |||
438 | 142 | parameters = root=%s | 134 | parameters = root=%s |
439 | 143 | 135 | ||
440 | 144 | """ % root_arg | 136 | """ % root_arg |
450 | 145 | zipl_cfg = { | 137 | futil.write_files( |
451 | 146 | "write_files": { | 138 | files={"zipl_conf": {"path": "/etc/zipl.conf", "content": zipl_conf}}, |
452 | 147 | "zipl_cfg": { | 139 | base_dir=target) |
444 | 148 | "path": "/etc/zipl.conf", | ||
445 | 149 | "content": zipl_conf, | ||
446 | 150 | } | ||
447 | 151 | } | ||
448 | 152 | } | ||
449 | 153 | write_files(zipl_cfg, target) | ||
453 | 154 | 140 | ||
454 | 155 | 141 | ||
455 | 156 | def run_zipl(cfg, target): | 142 | def run_zipl(cfg, target): |
456 | @@ -648,6 +634,40 @@ | |||
457 | 648 | update_initramfs(target, all_kernels=True) | 634 | update_initramfs(target, all_kernels=True) |
458 | 649 | 635 | ||
459 | 650 | 636 | ||
460 | 637 | def detect_required_packages(cfg): | ||
461 | 638 | """ | ||
462 | 639 | detect packages that will be required in-target by custom config items | ||
463 | 640 | """ | ||
464 | 641 | |||
465 | 642 | mapping = { | ||
466 | 643 | 'storage': block.detect_required_packages_mapping(), | ||
467 | 644 | 'network': net.detect_required_packages_mapping(), | ||
468 | 645 | } | ||
469 | 646 | |||
470 | 647 | needed_packages = [] | ||
471 | 648 | for cfg_type, cfg_map in mapping.items(): | ||
472 | 649 | |||
473 | 650 | # skip missing or invalid config items, configs may | ||
474 | 651 | # only have network or storage, not always both | ||
475 | 652 | if not isinstance(cfg.get(cfg_type), dict): | ||
476 | 653 | continue | ||
477 | 654 | |||
478 | 655 | cfg_version = cfg[cfg_type].get('version') | ||
479 | 656 | if not isinstance(cfg_version, int) or cfg_version not in cfg_map: | ||
480 | 657 | msg = ('Supplied configuration version "%s", for config type' | ||
481 | 658 | '"%s" is not present in the known mapping.' % (cfg_version, | ||
482 | 659 | cfg_type)) | ||
483 | 660 | raise ValueError(msg) | ||
484 | 661 | |||
485 | 662 | mapped_config = cfg_map[cfg_version] | ||
486 | 663 | found_reqs = mapped_config['handler'](cfg, mapped_config['mapping']) | ||
487 | 664 | needed_packages.extend(found_reqs) | ||
488 | 665 | |||
489 | 666 | LOG.debug('Curtin config dependencies requires additional packages: %s', | ||
490 | 667 | needed_packages) | ||
491 | 668 | return needed_packages | ||
492 | 669 | |||
493 | 670 | |||
494 | 651 | def install_missing_packages(cfg, target): | 671 | def install_missing_packages(cfg, target): |
495 | 652 | ''' describe which operation types will require specific packages | 672 | ''' describe which operation types will require specific packages |
496 | 653 | 673 | ||
497 | @@ -655,46 +675,10 @@ | |||
498 | 655 | 'pkg1': ['op_name_1', 'op_name_2', ...] | 675 | 'pkg1': ['op_name_1', 'op_name_2', ...] |
499 | 656 | } | 676 | } |
500 | 657 | ''' | 677 | ''' |
519 | 658 | custom_configs = { | 678 | |
502 | 659 | 'storage': { | ||
503 | 660 | 'lvm2': ['lvm_volgroup', 'lvm_partition'], | ||
504 | 661 | 'mdadm': ['raid'], | ||
505 | 662 | 'bcache-tools': ['bcache']}, | ||
506 | 663 | 'network': { | ||
507 | 664 | 'vlan': ['vlan'], | ||
508 | 665 | 'ifenslave': ['bond'], | ||
509 | 666 | 'bridge-utils': ['bridge']}, | ||
510 | 667 | } | ||
511 | 668 | |||
512 | 669 | format_configs = { | ||
513 | 670 | 'xfsprogs': ['xfs'], | ||
514 | 671 | 'e2fsprogs': ['ext2', 'ext3', 'ext4'], | ||
515 | 672 | 'btrfs-tools': ['btrfs'], | ||
516 | 673 | } | ||
517 | 674 | |||
518 | 675 | needed_packages = [] | ||
520 | 676 | installed_packages = util.get_installed_packages(target) | 679 | installed_packages = util.get_installed_packages(target) |
542 | 677 | for cust_cfg, pkg_reqs in custom_configs.items(): | 680 | needed_packages = set([pkg for pkg in detect_required_packages(cfg) |
543 | 678 | if cust_cfg not in cfg: | 681 | if pkg not in installed_packages]) |
523 | 679 | continue | ||
524 | 680 | |||
525 | 681 | all_types = set( | ||
526 | 682 | operation['type'] | ||
527 | 683 | for operation in cfg[cust_cfg]['config'] | ||
528 | 684 | ) | ||
529 | 685 | for pkg, types in pkg_reqs.items(): | ||
530 | 686 | if set(types).intersection(all_types) and \ | ||
531 | 687 | pkg not in installed_packages: | ||
532 | 688 | needed_packages.append(pkg) | ||
533 | 689 | |||
534 | 690 | format_types = set( | ||
535 | 691 | [operation['fstype'] | ||
536 | 692 | for operation in cfg[cust_cfg]['config'] | ||
537 | 693 | if operation['type'] == 'format']) | ||
538 | 694 | for pkg, fstypes in format_configs.items(): | ||
539 | 695 | if set(fstypes).intersection(format_types) and \ | ||
540 | 696 | pkg not in installed_packages: | ||
541 | 697 | needed_packages.append(pkg) | ||
544 | 698 | 682 | ||
545 | 699 | arch_packages = { | 683 | arch_packages = { |
546 | 700 | 's390x': [('s390-tools', 'zipl')], | 684 | 's390x': [('s390-tools', 'zipl')], |
547 | @@ -703,16 +687,28 @@ | |||
548 | 703 | for pkg, cmd in arch_packages.get(platform.machine(), []): | 687 | for pkg, cmd in arch_packages.get(platform.machine(), []): |
549 | 704 | if not util.which(cmd, target=target): | 688 | if not util.which(cmd, target=target): |
550 | 705 | if pkg not in needed_packages: | 689 | if pkg not in needed_packages: |
552 | 706 | needed_packages.append(pkg) | 690 | needed_packages.add(pkg) |
553 | 691 | |||
554 | 692 | # FIXME: This needs cleaning up. | ||
555 | 693 | # do not install certain packages on artful as they are no longer needed. | ||
556 | 694 | # ifenslave specifically causes issuse due to dependency on ifupdown. | ||
557 | 695 | codename = util.lsb_release(target=target).get('codename') | ||
558 | 696 | if codename == 'artful': | ||
559 | 697 | drops = set(['bridge-utils', 'ifenslave', 'vlan']) | ||
560 | 698 | if needed_packages.union(drops): | ||
561 | 699 | LOG.debug("Skipping install of %s. Not needed on artful.", | ||
562 | 700 | needed_packages.union(drops)) | ||
563 | 701 | needed_packages = needed_packages.difference(drops) | ||
564 | 707 | 702 | ||
565 | 708 | if needed_packages: | 703 | if needed_packages: |
566 | 704 | to_add = list(sorted(needed_packages)) | ||
567 | 709 | state = util.load_command_environment() | 705 | state = util.load_command_environment() |
568 | 710 | with events.ReportEventStack( | 706 | with events.ReportEventStack( |
569 | 711 | name=state.get('report_stack_prefix'), | 707 | name=state.get('report_stack_prefix'), |
570 | 712 | reporting_enabled=True, level="INFO", | 708 | reporting_enabled=True, level="INFO", |
571 | 713 | description="Installing packages on target system: " + | 709 | description="Installing packages on target system: " + |
574 | 714 | str(needed_packages)): | 710 | str(to_add)): |
575 | 715 | util.install_packages(needed_packages, target=target) | 711 | util.install_packages(to_add, target=target) |
576 | 716 | 712 | ||
577 | 717 | 713 | ||
578 | 718 | def system_upgrade(cfg, target): | 714 | def system_upgrade(cfg, target): |
579 | @@ -737,8 +733,8 @@ | |||
580 | 737 | util.system_upgrade(target=target) | 733 | util.system_upgrade(target=target) |
581 | 738 | 734 | ||
582 | 739 | 735 | ||
585 | 740 | def handle_cloudconfig(cfg, target=None): | 736 | def handle_cloudconfig(cfg, base_dir=None): |
586 | 741 | """write cloud-init configuration files into target | 737 | """write cloud-init configuration files into base_dir. |
587 | 742 | 738 | ||
588 | 743 | cloudconfig format is a dictionary of keys and values of content | 739 | cloudconfig format is a dictionary of keys and values of content |
589 | 744 | 740 | ||
590 | @@ -773,9 +769,9 @@ | |||
591 | 773 | cfgvalue['path'] = cfgpath | 769 | cfgvalue['path'] = cfgpath |
592 | 774 | 770 | ||
593 | 775 | # re-use write_files format and adjust target to prepend | 771 | # re-use write_files format and adjust target to prepend |
595 | 776 | LOG.debug('Calling write_files with cloudconfig @ %s', target) | 772 | LOG.debug('Calling write_files with cloudconfig @ %s', base_dir) |
596 | 777 | LOG.debug('Injecting cloud-config:\n%s', cfg) | 773 | LOG.debug('Injecting cloud-config:\n%s', cfg) |
598 | 778 | write_files({'write_files': cfg}, target) | 774 | futil.write_files(cfg, base_dir) |
599 | 779 | 775 | ||
600 | 780 | 776 | ||
601 | 781 | def ubuntu_core_curthooks(cfg, target=None): | 777 | def ubuntu_core_curthooks(cfg, target=None): |
602 | @@ -795,17 +791,98 @@ | |||
603 | 795 | if os.path.exists(cloudinit_disable): | 791 | if os.path.exists(cloudinit_disable): |
604 | 796 | util.del_file(cloudinit_disable) | 792 | util.del_file(cloudinit_disable) |
605 | 797 | 793 | ||
607 | 798 | handle_cloudconfig(cloudconfig, target=cc_target) | 794 | handle_cloudconfig(cloudconfig, base_dir=cc_target) |
608 | 799 | 795 | ||
609 | 800 | netconfig = cfg.get('network', None) | 796 | netconfig = cfg.get('network', None) |
610 | 801 | if netconfig: | 797 | if netconfig: |
611 | 802 | LOG.info('Writing network configuration') | 798 | LOG.info('Writing network configuration') |
612 | 803 | ubuntu_core_netconfig = os.path.join(cc_target, | 799 | ubuntu_core_netconfig = os.path.join(cc_target, |
614 | 804 | "50-network-config.cfg") | 800 | "50-curtin-networking.cfg") |
615 | 805 | util.write_file(ubuntu_core_netconfig, | 801 | util.write_file(ubuntu_core_netconfig, |
616 | 806 | content=config.dump_config({'network': netconfig})) | 802 | content=config.dump_config({'network': netconfig})) |
617 | 807 | 803 | ||
618 | 808 | 804 | ||
619 | 805 | def rpm_get_dist_id(target): | ||
620 | 806 | """Use rpm command to extract the '%rhel' distro macro which returns | ||
621 | 807 | the major os version id (6, 7, 8). This works for centos or rhel | ||
622 | 808 | """ | ||
623 | 809 | with util.ChrootableTarget(target) as in_chroot: | ||
624 | 810 | dist, _ = in_chroot.subp(['rpm', '-E', '%rhel'], capture=True) | ||
625 | 811 | return dist.rstrip() | ||
626 | 812 | |||
627 | 813 | |||
628 | 814 | def centos_apply_network_config(netcfg, target=None): | ||
629 | 815 | """ CentOS images execute built-in curthooks which only supports | ||
630 | 816 | simple networking configuration. This hook enables advanced | ||
631 | 817 | network configuration via config passthrough to the target. | ||
632 | 818 | """ | ||
633 | 819 | |||
634 | 820 | def cloud_init_repo(version): | ||
635 | 821 | if not version: | ||
636 | 822 | raise ValueError('Missing required version parameter') | ||
637 | 823 | |||
638 | 824 | return CLOUD_INIT_YUM_REPO_TEMPLATE % version | ||
639 | 825 | |||
640 | 826 | if netcfg: | ||
641 | 827 | LOG.info('Removing embedded network configuration (if present)') | ||
642 | 828 | ifcfgs = glob.glob(util.target_path(target, | ||
643 | 829 | 'etc/sysconfig/network-scripts') + | ||
644 | 830 | '/ifcfg-*') | ||
645 | 831 | # remove ifcfg-* (except ifcfg-lo) | ||
646 | 832 | for ifcfg in ifcfgs: | ||
647 | 833 | if os.path.basename(ifcfg) != "ifcfg-lo": | ||
648 | 834 | util.del_file(ifcfg) | ||
649 | 835 | |||
650 | 836 | LOG.info('Checking cloud-init in target [%s] for network ' | ||
651 | 837 | 'configuration passthrough support.', target) | ||
652 | 838 | passthrough = net.netconfig_passthrough_available(target) | ||
653 | 839 | LOG.debug('passthrough available via in-target: %s', passthrough) | ||
654 | 840 | |||
655 | 841 | # if in-target cloud-init is not updated, upgrade via cloud-init repo | ||
656 | 842 | if not passthrough: | ||
657 | 843 | cloud_init_yum_repo = ( | ||
658 | 844 | util.target_path(target, | ||
659 | 845 | 'etc/yum.repos.d/curtin-cloud-init.repo')) | ||
660 | 846 | # Inject cloud-init daily yum repo | ||
661 | 847 | util.write_file(cloud_init_yum_repo, | ||
662 | 848 | content=cloud_init_repo(rpm_get_dist_id(target))) | ||
663 | 849 | |||
664 | 850 | # we separate the installation of repository packages (epel, | ||
665 | 851 | # cloud-init-el-release) as we need a new invocation of yum | ||
666 | 852 | # to read the newly installed repo files. | ||
667 | 853 | YUM_CMD = ['yum', '-y', '--noplugins', 'install'] | ||
668 | 854 | retries = [1] * 30 | ||
669 | 855 | with util.ChrootableTarget(target) as in_chroot: | ||
670 | 856 | # ensure up-to-date ca-certificates to handle https mirror | ||
671 | 857 | # connections | ||
672 | 858 | in_chroot.subp(YUM_CMD + ['ca-certificates'], capture=True, | ||
673 | 859 | log_captured=True, retries=retries) | ||
674 | 860 | in_chroot.subp(YUM_CMD + ['epel-release'], capture=True, | ||
675 | 861 | log_captured=True, retries=retries) | ||
676 | 862 | in_chroot.subp(YUM_CMD + ['cloud-init-el-release'], | ||
677 | 863 | log_captured=True, capture=True, | ||
678 | 864 | retries=retries) | ||
679 | 865 | in_chroot.subp(YUM_CMD + ['cloud-init'], capture=True, | ||
680 | 866 | log_captured=True, retries=retries) | ||
681 | 867 | |||
682 | 868 | # remove cloud-init el-stable bootstrap repo config as the | ||
683 | 869 | # cloud-init-el-release package points to the correct repo | ||
684 | 870 | util.del_file(cloud_init_yum_repo) | ||
685 | 871 | |||
686 | 872 | # install bridge-utils if needed | ||
687 | 873 | with util.ChrootableTarget(target) as in_chroot: | ||
688 | 874 | try: | ||
689 | 875 | in_chroot.subp(['rpm', '-q', 'bridge-utils'], | ||
690 | 876 | capture=False, rcs=[0]) | ||
691 | 877 | except util.ProcessExecutionError: | ||
692 | 878 | LOG.debug('Image missing bridge-utils package, installing') | ||
693 | 879 | in_chroot.subp(YUM_CMD + ['bridge-utils'], capture=True, | ||
694 | 880 | log_captured=True, retries=retries) | ||
695 | 881 | |||
696 | 882 | LOG.info('Passing network configuration through to target') | ||
697 | 883 | net.render_netconfig_passthrough(target, netconfig={'network': netcfg}) | ||
698 | 884 | |||
699 | 885 | |||
700 | 809 | def target_is_ubuntu_core(target): | 886 | def target_is_ubuntu_core(target): |
701 | 810 | """Check if Ubuntu-Core specific directory is present at target""" | 887 | """Check if Ubuntu-Core specific directory is present at target""" |
702 | 811 | if target: | 888 | if target: |
703 | @@ -814,6 +891,22 @@ | |||
704 | 814 | return False | 891 | return False |
705 | 815 | 892 | ||
706 | 816 | 893 | ||
707 | 894 | def target_is_centos(target): | ||
708 | 895 | """Check if CentOS specific file is present at target""" | ||
709 | 896 | if target: | ||
710 | 897 | return os.path.exists(util.target_path(target, 'etc/centos-release')) | ||
711 | 898 | |||
712 | 899 | return False | ||
713 | 900 | |||
714 | 901 | |||
715 | 902 | def target_is_rhel(target): | ||
716 | 903 | """Check if RHEL specific file is present at target""" | ||
717 | 904 | if target: | ||
718 | 905 | return os.path.exists(util.target_path(target, 'etc/redhat-release')) | ||
719 | 906 | |||
720 | 907 | return False | ||
721 | 908 | |||
722 | 909 | |||
723 | 817 | def curthooks(args): | 910 | def curthooks(args): |
724 | 818 | state = util.load_command_environment() | 911 | state = util.load_command_environment() |
725 | 819 | 912 | ||
726 | @@ -827,14 +920,28 @@ | |||
727 | 827 | "Use --target or set TARGET_MOUNT_POINT\n") | 920 | "Use --target or set TARGET_MOUNT_POINT\n") |
728 | 828 | sys.exit(2) | 921 | sys.exit(2) |
729 | 829 | 922 | ||
730 | 830 | # if network-config hook exists in target, | ||
731 | 831 | # we do not run the builtin | ||
732 | 832 | if util.run_hook_if_exists(target, 'curtin-hooks'): | ||
733 | 833 | sys.exit(0) | ||
734 | 834 | |||
735 | 835 | cfg = config.load_command_config(args, state) | 923 | cfg = config.load_command_config(args, state) |
736 | 836 | stack_prefix = state.get('report_stack_prefix', '') | 924 | stack_prefix = state.get('report_stack_prefix', '') |
737 | 837 | 925 | ||
738 | 926 | # if curtin-hooks hook exists in target we can defer to the in-target hooks | ||
739 | 927 | if util.run_hook_if_exists(target, 'curtin-hooks'): | ||
740 | 928 | # For vmtests to force execute centos_apply_network_config, uncomment | ||
741 | 929 | # the value in examples/tests/centos_defaults.yaml | ||
742 | 930 | if cfg.get('_ammend_centos_curthooks'): | ||
743 | 931 | if cfg.get('cloudconfig'): | ||
744 | 932 | handle_cloudconfig( | ||
745 | 933 | cfg['cloudconfig'], | ||
746 | 934 | base_dir=util.target_path(target, 'etc/cloud/cloud.cfg.d')) | ||
747 | 935 | |||
748 | 936 | if target_is_centos(target) or target_is_rhel(target): | ||
749 | 937 | LOG.info('Detected RHEL/CentOS image, running extra hooks') | ||
750 | 938 | with events.ReportEventStack( | ||
751 | 939 | name=stack_prefix, reporting_enabled=True, | ||
752 | 940 | level="INFO", | ||
753 | 941 | description="Configuring CentOS for first boot"): | ||
754 | 942 | centos_apply_network_config(cfg.get('network', {}), target) | ||
755 | 943 | sys.exit(0) | ||
756 | 944 | |||
757 | 838 | if target_is_ubuntu_core(target): | 945 | if target_is_ubuntu_core(target): |
758 | 839 | LOG.info('Detected Ubuntu-Core image, running hooks') | 946 | LOG.info('Detected Ubuntu-Core image, running hooks') |
759 | 840 | with events.ReportEventStack( | 947 | with events.ReportEventStack( |
760 | @@ -846,13 +953,16 @@ | |||
761 | 846 | with events.ReportEventStack( | 953 | with events.ReportEventStack( |
762 | 847 | name=stack_prefix + '/writing-config', | 954 | name=stack_prefix + '/writing-config', |
763 | 848 | reporting_enabled=True, level="INFO", | 955 | reporting_enabled=True, level="INFO", |
766 | 849 | description="writing config files and configuring apt"): | 956 | description="configuring apt configuring apt"): |
765 | 850 | write_files(cfg, target) | ||
767 | 851 | do_apt_config(cfg, target) | 957 | do_apt_config(cfg, target) |
768 | 852 | disable_overlayroot(cfg, target) | 958 | disable_overlayroot(cfg, target) |
769 | 853 | 959 | ||
770 | 854 | # packages may be needed prior to installing kernel | 960 | # packages may be needed prior to installing kernel |
772 | 855 | install_missing_packages(cfg, target) | 961 | with events.ReportEventStack( |
773 | 962 | name=stack_prefix + '/installing-missing-packages', | ||
774 | 963 | reporting_enabled=True, level="INFO", | ||
775 | 964 | description="installing missing packages"): | ||
776 | 965 | install_missing_packages(cfg, target) | ||
777 | 856 | 966 | ||
778 | 857 | # If a /etc/iscsi/nodes/... file was created by block_meta then it | 967 | # If a /etc/iscsi/nodes/... file was created by block_meta then it |
779 | 858 | # needs to be copied onto the target system | 968 | # needs to be copied onto the target system |
780 | @@ -880,7 +990,6 @@ | |||
781 | 880 | setup_zipl(cfg, target) | 990 | setup_zipl(cfg, target) |
782 | 881 | install_kernel(cfg, target) | 991 | install_kernel(cfg, target) |
783 | 882 | run_zipl(cfg, target) | 992 | run_zipl(cfg, target) |
784 | 883 | |||
785 | 884 | restore_dist_interfaces(cfg, target) | 993 | restore_dist_interfaces(cfg, target) |
786 | 885 | 994 | ||
787 | 886 | with events.ReportEventStack( | 995 | with events.ReportEventStack( |
788 | @@ -908,12 +1017,6 @@ | |||
789 | 908 | detect_and_handle_multipath(cfg, target) | 1017 | detect_and_handle_multipath(cfg, target) |
790 | 909 | 1018 | ||
791 | 910 | with events.ReportEventStack( | 1019 | with events.ReportEventStack( |
792 | 911 | name=stack_prefix + '/installing-missing-packages', | ||
793 | 912 | reporting_enabled=True, level="INFO", | ||
794 | 913 | description="installing missing packages"): | ||
795 | 914 | install_missing_packages(cfg, target) | ||
796 | 915 | |||
797 | 916 | with events.ReportEventStack( | ||
798 | 917 | name=stack_prefix + '/system-upgrade', | 1020 | name=stack_prefix + '/system-upgrade', |
799 | 918 | reporting_enabled=True, level="INFO", | 1021 | reporting_enabled=True, level="INFO", |
800 | 919 | description="updating packages on target system"): | 1022 | description="updating packages on target system"): |
801 | 920 | 1023 | ||
802 | === modified file 'curtin/commands/extract.py' | |||
803 | --- curtin/commands/extract.py 2016-05-10 16:13:29 +0000 | |||
804 | +++ curtin/commands/extract.py 2017-10-06 16:35:22 +0000 | |||
805 | @@ -21,6 +21,7 @@ | |||
806 | 21 | import curtin.config | 21 | import curtin.config |
807 | 22 | from curtin.log import LOG | 22 | from curtin.log import LOG |
808 | 23 | import curtin.util | 23 | import curtin.util |
809 | 24 | from curtin.futil import write_files | ||
810 | 24 | from curtin.reporter import events | 25 | from curtin.reporter import events |
811 | 25 | 26 | ||
812 | 26 | from . import populate_one_subcmd | 27 | from . import populate_one_subcmd |
813 | @@ -122,6 +123,11 @@ | |||
814 | 122 | "do not know how to extract '%s'" % | 123 | "do not know how to extract '%s'" % |
815 | 123 | source['uri']) | 124 | source['uri']) |
816 | 124 | 125 | ||
817 | 126 | if cfg.get('write_files'): | ||
818 | 127 | LOG.info("Applying write_files from config.") | ||
819 | 128 | write_files(cfg['write_files'], target) | ||
820 | 129 | else: | ||
821 | 130 | LOG.info("No write_files in config.") | ||
822 | 125 | sys.exit(0) | 131 | sys.exit(0) |
823 | 126 | 132 | ||
824 | 127 | 133 | ||
825 | 128 | 134 | ||
826 | === modified file 'curtin/commands/install.py' | |||
827 | --- curtin/commands/install.py 2017-06-12 20:39:06 +0000 | |||
828 | +++ curtin/commands/install.py 2017-10-06 16:35:22 +0000 | |||
829 | @@ -366,6 +366,27 @@ | |||
830 | 366 | return True | 366 | return True |
831 | 367 | 367 | ||
832 | 368 | 368 | ||
833 | 369 | def migrate_proxy_settings(cfg): | ||
834 | 370 | """Move the legacy proxy setting 'http_proxy' into cfg['proxy'].""" | ||
835 | 371 | proxy = cfg.get('proxy', {}) | ||
836 | 372 | if not isinstance(proxy, dict): | ||
837 | 373 | raise ValueError("'proxy' in config is not a dictionary: %s" % proxy) | ||
838 | 374 | |||
839 | 375 | if 'http_proxy' in cfg: | ||
840 | 376 | hp = cfg['http_proxy'] | ||
841 | 377 | if hp: | ||
842 | 378 | if proxy.get('http_proxy', hp) != hp: | ||
843 | 379 | LOG.warn("legacy http_proxy setting (%s) differs from " | ||
844 | 380 | "proxy/http_proxy (%s), using %s", | ||
845 | 381 | hp, proxy['http_proxy'], proxy['http_proxy']) | ||
846 | 382 | else: | ||
847 | 383 | LOG.debug("legacy 'http_proxy' migrated to proxy/http_proxy") | ||
848 | 384 | proxy['http_proxy'] = hp | ||
849 | 385 | del cfg['http_proxy'] | ||
850 | 386 | |||
851 | 387 | cfg['proxy'] = proxy | ||
852 | 388 | |||
853 | 389 | |||
854 | 369 | def cmd_install(args): | 390 | def cmd_install(args): |
855 | 370 | cfg = CONFIG_BUILTIN.copy() | 391 | cfg = CONFIG_BUILTIN.copy() |
856 | 371 | config.merge_config(cfg, args.config) | 392 | config.merge_config(cfg, args.config) |
857 | @@ -384,8 +405,10 @@ | |||
858 | 384 | # we default to tgz for old style sources config | 405 | # we default to tgz for old style sources config |
859 | 385 | cfg['sources'][i] = util.sanitize_source(cfg['sources'][i]) | 406 | cfg['sources'][i] = util.sanitize_source(cfg['sources'][i]) |
860 | 386 | 407 | ||
863 | 387 | if cfg.get('http_proxy'): | 408 | migrate_proxy_settings(cfg) |
864 | 388 | os.environ['http_proxy'] = cfg['http_proxy'] | 409 | for k in ('http_proxy', 'https_proxy', 'no_proxy'): |
865 | 410 | if k in cfg['proxy']: | ||
866 | 411 | os.environ[k] = cfg['proxy'][k] | ||
867 | 389 | 412 | ||
868 | 390 | instcfg = cfg.get('install', {}) | 413 | instcfg = cfg.get('install', {}) |
869 | 391 | logfile = instcfg.get('log_file') | 414 | logfile = instcfg.get('log_file') |
870 | @@ -454,9 +477,26 @@ | |||
871 | 454 | '/root/curtin-install.log') | 477 | '/root/curtin-install.log') |
872 | 455 | if log_target_path: | 478 | if log_target_path: |
873 | 456 | copy_install_log(logfile, workingd.target, log_target_path) | 479 | copy_install_log(logfile, workingd.target, log_target_path) |
874 | 480 | # unmount everything (including iscsi disks) | ||
875 | 457 | util.do_umount(workingd.target, recursive=True) | 481 | util.do_umount(workingd.target, recursive=True) |
878 | 458 | # need to do some processing on iscsi disks to disconnect? | 482 | |
879 | 459 | iscsi.disconnect_target_disks(workingd.target) | 483 | # The open-iscsi service in the ephemeral environment handles |
880 | 484 | # disconnecting active sessions. On Artful release the systemd | ||
881 | 485 | # unit file has conditionals that are not met at boot time and | ||
882 | 486 | # results in open-iscsi service not being started; This breaks | ||
883 | 487 | # shutdown on Artful releases. | ||
884 | 488 | # Additionally, in release < Artful, if the storage configuration | ||
885 | 489 | # is layered, like RAID over iscsi volumes, then disconnecting iscsi | ||
886 | 490 | # sessions before stopping the raid device hangs. | ||
887 | 491 | # As it turns out, letting the open-iscsi service take down the | ||
888 | 492 | # session last is the cleanest way to handle all releases regardless | ||
889 | 493 | # of what may be layered on top of the iscsi disks. | ||
890 | 494 | # | ||
891 | 495 | # Check if storage configuration has iscsi volumes and if so ensure | ||
892 | 496 | # iscsi service is active before exiting install | ||
893 | 497 | if iscsi.get_iscsi_disks_from_config(cfg): | ||
894 | 498 | iscsi.restart_iscsi_service() | ||
895 | 499 | |||
896 | 460 | shutil.rmtree(workingd.top) | 500 | shutil.rmtree(workingd.top) |
897 | 461 | 501 | ||
898 | 462 | apply_power_state(cfg.get('power_state')) | 502 | apply_power_state(cfg.get('power_state')) |
899 | 463 | 503 | ||
900 | === modified file 'curtin/futil.py' | |||
901 | --- curtin/futil.py 2014-03-26 17:34:57 +0000 | |||
902 | +++ curtin/futil.py 2017-10-06 16:35:22 +0000 | |||
903 | @@ -19,7 +19,8 @@ | |||
904 | 19 | import pwd | 19 | import pwd |
905 | 20 | import os | 20 | import os |
906 | 21 | 21 | ||
908 | 22 | from .util import write_file | 22 | from .util import write_file, target_path |
909 | 23 | from .log import LOG | ||
910 | 23 | 24 | ||
911 | 24 | 25 | ||
912 | 25 | def chownbyid(fname, uid=None, gid=None): | 26 | def chownbyid(fname, uid=None, gid=None): |
913 | @@ -78,3 +79,25 @@ | |||
914 | 78 | omode = "wb" | 79 | omode = "wb" |
915 | 79 | write_file(path, content, mode=decode_perms(perms), omode=omode) | 80 | write_file(path, content, mode=decode_perms(perms), omode=omode) |
916 | 80 | chownbyname(path, u, g) | 81 | chownbyname(path, u, g) |
917 | 82 | |||
918 | 83 | |||
919 | 84 | def write_files(files, base_dir=None): | ||
920 | 85 | """Write files described in the dictionary 'files' | ||
921 | 86 | |||
922 | 87 | paths are assumed under 'base_dir', which will default to '/'. | ||
923 | 88 | A trailing '/' will be applied if not present. | ||
924 | 89 | |||
925 | 90 | files is a dictionary where each entry has: | ||
926 | 91 | path: /file1 | ||
927 | 92 | content: (bytes or string) | ||
928 | 93 | permissions: (optional, default=0644) | ||
929 | 94 | owner: (optional, default -1:-1): string of 'uid:gid'.""" | ||
930 | 95 | for (key, info) in files.items(): | ||
931 | 96 | if not info.get('path'): | ||
932 | 97 | LOG.warn("Warning, write_files[%s] had no 'path' entry", key) | ||
933 | 98 | continue | ||
934 | 99 | |||
935 | 100 | write_finfo(path=target_path(base_dir, info['path']), | ||
936 | 101 | content=info.get('content', ''), | ||
937 | 102 | owner=info.get('owner', "-1:-1"), | ||
938 | 103 | perms=info.get('permissions', info.get('perms', "0644"))) | ||
939 | 81 | 104 | ||
940 | === modified file 'curtin/net/__init__.py' | |||
941 | --- curtin/net/__init__.py 2017-03-01 16:13:56 +0000 | |||
942 | +++ curtin/net/__init__.py 2017-10-06 16:35:22 +0000 | |||
943 | @@ -520,7 +520,52 @@ | |||
944 | 520 | return content | 520 | return content |
945 | 521 | 521 | ||
946 | 522 | 522 | ||
947 | 523 | def netconfig_passthrough_available(target, feature='NETWORK_CONFIG_V2'): | ||
948 | 524 | """ | ||
949 | 525 | Determine if curtin can pass v2 network config to in target cloud-init | ||
950 | 526 | """ | ||
951 | 527 | LOG.debug('Checking in-target cloud-init for feature: %s', feature) | ||
952 | 528 | with util.ChrootableTarget(target) as in_chroot: | ||
953 | 529 | |||
954 | 530 | cloudinit = util.which('cloud-init', target=target) | ||
955 | 531 | if not cloudinit: | ||
956 | 532 | LOG.warning('Target does not have cloud-init installed') | ||
957 | 533 | return False | ||
958 | 534 | |||
959 | 535 | available = False | ||
960 | 536 | try: | ||
961 | 537 | out, _ = in_chroot.subp([cloudinit, 'features'], capture=True) | ||
962 | 538 | available = feature in out.splitlines() | ||
963 | 539 | except util.ProcessExecutionError: | ||
964 | 540 | # we explicitly don't dump the exception as this triggers | ||
965 | 541 | # vmtest failures when parsing the installation log file | ||
966 | 542 | LOG.warning("Failed to probe cloudinit features") | ||
967 | 543 | return False | ||
968 | 544 | |||
969 | 545 | LOG.debug('cloud-init feature %s available? %s', feature, available) | ||
970 | 546 | return available | ||
971 | 547 | |||
972 | 548 | |||
973 | 549 | def render_netconfig_passthrough(target, netconfig=None): | ||
974 | 550 | """ | ||
975 | 551 | Extract original network config and pass it | ||
976 | 552 | through to cloud-init in target | ||
977 | 553 | """ | ||
978 | 554 | cc = 'etc/cloud/cloud.cfg.d/50-curtin-networking.cfg' | ||
979 | 555 | if not isinstance(netconfig, dict): | ||
980 | 556 | raise ValueError('Network config must be a dictionary') | ||
981 | 557 | |||
982 | 558 | if 'network' not in netconfig: | ||
983 | 559 | raise ValueError("Network config must contain the key 'network'") | ||
984 | 560 | |||
985 | 561 | content = config.dump_config(netconfig) | ||
986 | 562 | cc_passthrough = os.path.sep.join((target, cc,)) | ||
987 | 563 | LOG.info('Writing network config to %s: %s', cc, cc_passthrough) | ||
988 | 564 | util.write_file(cc_passthrough, content=content) | ||
989 | 565 | |||
990 | 566 | |||
991 | 523 | def render_network_state(target, network_state): | 567 | def render_network_state(target, network_state): |
992 | 568 | LOG.debug("rendering eni from netconfig") | ||
993 | 524 | eni = 'etc/network/interfaces' | 569 | eni = 'etc/network/interfaces' |
994 | 525 | netrules = 'etc/udev/rules.d/70-persistent-net.rules' | 570 | netrules = 'etc/udev/rules.d/70-persistent-net.rules' |
995 | 526 | cc = 'etc/cloud/cloud.cfg.d/curtin-disable-cloudinit-networking.cfg' | 571 | cc = 'etc/cloud/cloud.cfg.d/curtin-disable-cloudinit-networking.cfg' |
996 | @@ -542,4 +587,65 @@ | |||
997 | 542 | """Returns the string value of an interface's MAC Address""" | 587 | """Returns the string value of an interface's MAC Address""" |
998 | 543 | return read_sys_net(ifname, "address", enoent=False) | 588 | return read_sys_net(ifname, "address", enoent=False) |
999 | 544 | 589 | ||
1000 | 590 | |||
1001 | 591 | def network_config_required_packages(network_config, mapping=None): | ||
1002 | 592 | |||
1003 | 593 | if network_config is None: | ||
1004 | 594 | network_config = {} | ||
1005 | 595 | |||
1006 | 596 | if not isinstance(network_config, dict): | ||
1007 | 597 | raise ValueError('Invalid network configuration. Must be a dict') | ||
1008 | 598 | |||
1009 | 599 | if mapping is None: | ||
1010 | 600 | mapping = {} | ||
1011 | 601 | |||
1012 | 602 | if not isinstance(mapping, dict): | ||
1013 | 603 | raise ValueError('Invalid network mapping. Must be a dict') | ||
1014 | 604 | |||
1015 | 605 | # allow top-level 'network' key | ||
1016 | 606 | if 'network' in network_config: | ||
1017 | 607 | network_config = network_config.get('network') | ||
1018 | 608 | |||
1019 | 609 | # v1 has 'config' key and uses type: devtype elements | ||
1020 | 610 | if 'config' in network_config: | ||
1021 | 611 | dev_configs = set(device['type'] | ||
1022 | 612 | for device in network_config['config']) | ||
1023 | 613 | else: | ||
1024 | 614 | # v2 has no config key | ||
1025 | 615 | dev_configs = set(cfgtype for (cfgtype, cfg) in | ||
1026 | 616 | network_config.items() if cfgtype not in ['version']) | ||
1027 | 617 | |||
1028 | 618 | needed_packages = [] | ||
1029 | 619 | for dev_type in dev_configs: | ||
1030 | 620 | if dev_type in mapping: | ||
1031 | 621 | needed_packages.extend(mapping[dev_type]) | ||
1032 | 622 | |||
1033 | 623 | return needed_packages | ||
1034 | 624 | |||
1035 | 625 | |||
1036 | 626 | def detect_required_packages_mapping(): | ||
1037 | 627 | """Return a dictionary providing a versioned configuration which maps | ||
1038 | 628 | network configuration elements to the packages which are required | ||
1039 | 629 | for functionality. | ||
1040 | 630 | """ | ||
1041 | 631 | mapping = { | ||
1042 | 632 | 1: { | ||
1043 | 633 | 'handler': network_config_required_packages, | ||
1044 | 634 | 'mapping': { | ||
1045 | 635 | 'bond': ['ifenslave'], | ||
1046 | 636 | 'bridge': ['bridge-utils'], | ||
1047 | 637 | 'vlan': ['vlan']}, | ||
1048 | 638 | }, | ||
1049 | 639 | 2: { | ||
1050 | 640 | 'handler': network_config_required_packages, | ||
1051 | 641 | 'mapping': { | ||
1052 | 642 | 'bonds': ['ifenslave'], | ||
1053 | 643 | 'bridges': ['bridge-utils'], | ||
1054 | 644 | 'vlans': ['vlan']} | ||
1055 | 645 | }, | ||
1056 | 646 | } | ||
1057 | 647 | |||
1058 | 648 | return mapping | ||
1059 | 649 | |||
1060 | 650 | |||
1061 | 545 | # vi: ts=4 expandtab syntax=python | 651 | # vi: ts=4 expandtab syntax=python |
1062 | 546 | 652 | ||
1063 | === modified file 'curtin/reporter/handlers.py' | |||
1064 | --- curtin/reporter/handlers.py 2017-02-08 22:22:44 +0000 | |||
1065 | +++ curtin/reporter/handlers.py 2017-10-06 16:35:22 +0000 | |||
1066 | @@ -80,7 +80,49 @@ | |||
1067 | 80 | LOG.warn("failed posting event: %s [%s]" % (event.as_string(), e)) | 80 | LOG.warn("failed posting event: %s [%s]" % (event.as_string(), e)) |
1068 | 81 | 81 | ||
1069 | 82 | 82 | ||
1070 | 83 | class JournaldHandler(ReportingHandler): | ||
1071 | 84 | |||
1072 | 85 | def __init__(self, level="DEBUG", identifier="curtin_event"): | ||
1073 | 86 | super(JournaldHandler, self).__init__() | ||
1074 | 87 | if isinstance(level, int): | ||
1075 | 88 | pass | ||
1076 | 89 | else: | ||
1077 | 90 | input_level = level | ||
1078 | 91 | try: | ||
1079 | 92 | level = getattr(logging, level.upper()) | ||
1080 | 93 | except Exception: | ||
1081 | 94 | LOG.warn("invalid level '%s', using WARN", input_level) | ||
1082 | 95 | level = logging.WARN | ||
1083 | 96 | self.level = level | ||
1084 | 97 | self.identifier = identifier | ||
1085 | 98 | |||
1086 | 99 | def publish_event(self, event): | ||
1087 | 100 | # Ubuntu older than precise will not have python-systemd installed. | ||
1088 | 101 | try: | ||
1089 | 102 | from systemd import journal | ||
1090 | 103 | except ImportError: | ||
1091 | 104 | raise | ||
1092 | 105 | level = str(getattr(journal, "LOG_" + event.level, journal.LOG_DEBUG)) | ||
1093 | 106 | extra = {} | ||
1094 | 107 | if hasattr(event, 'result'): | ||
1095 | 108 | extra['CURTIN_RESULT'] = event.result | ||
1096 | 109 | journal.send( | ||
1097 | 110 | event.as_string(), | ||
1098 | 111 | PRIORITY=level, | ||
1099 | 112 | SYSLOG_IDENTIFIER=self.identifier, | ||
1100 | 113 | CURTIN_EVENT_TYPE=event.event_type, | ||
1101 | 114 | CURTIN_MESSAGE=event.description, | ||
1102 | 115 | CURTIN_NAME=event.name, | ||
1103 | 116 | **extra | ||
1104 | 117 | ) | ||
1105 | 118 | |||
1106 | 119 | |||
1107 | 83 | available_handlers = DictRegistry() | 120 | available_handlers = DictRegistry() |
1108 | 84 | available_handlers.register_item('log', LogHandler) | 121 | available_handlers.register_item('log', LogHandler) |
1109 | 85 | available_handlers.register_item('print', PrintHandler) | 122 | available_handlers.register_item('print', PrintHandler) |
1110 | 86 | available_handlers.register_item('webhook', WebHookHandler) | 123 | available_handlers.register_item('webhook', WebHookHandler) |
1111 | 124 | # only add journald handler on systemd systems | ||
1112 | 125 | try: | ||
1113 | 126 | available_handlers.register_item('journald', JournaldHandler) | ||
1114 | 127 | except ImportError: | ||
1115 | 128 | print('journald report handler not supported; no systemd module') | ||
1116 | 87 | 129 | ||
1117 | === modified file 'curtin/util.py' | |||
1118 | --- curtin/util.py 2017-06-12 20:39:06 +0000 | |||
1119 | +++ curtin/util.py 2017-10-06 16:35:22 +0000 | |||
1120 | @@ -23,6 +23,7 @@ | |||
1121 | 23 | import os | 23 | import os |
1122 | 24 | import platform | 24 | import platform |
1123 | 25 | import re | 25 | import re |
1124 | 26 | import shlex | ||
1125 | 26 | import shutil | 27 | import shutil |
1126 | 27 | import socket | 28 | import socket |
1127 | 28 | import subprocess | 29 | import subprocess |
1128 | @@ -57,6 +58,8 @@ | |||
1129 | 57 | _INSTALLED_MAIN = '/usr/bin/curtin' | 58 | _INSTALLED_MAIN = '/usr/bin/curtin' |
1130 | 58 | 59 | ||
1131 | 59 | _LSB_RELEASE = {} | 60 | _LSB_RELEASE = {} |
1132 | 61 | _USES_SYSTEMD = None | ||
1133 | 62 | _HAS_UNSHARE_PID = None | ||
1134 | 60 | 63 | ||
1135 | 61 | _DNS_REDIRECT_IP = None | 64 | _DNS_REDIRECT_IP = None |
1136 | 62 | 65 | ||
1137 | @@ -66,21 +69,31 @@ | |||
1138 | 66 | 69 | ||
1139 | 67 | def _subp(args, data=None, rcs=None, env=None, capture=False, | 70 | def _subp(args, data=None, rcs=None, env=None, capture=False, |
1140 | 68 | shell=False, logstring=False, decode="replace", | 71 | shell=False, logstring=False, decode="replace", |
1142 | 69 | target=None, cwd=None, log_captured=False): | 72 | target=None, cwd=None, log_captured=False, unshare_pid=None): |
1143 | 70 | if rcs is None: | 73 | if rcs is None: |
1144 | 71 | rcs = [0] | 74 | rcs = [0] |
1145 | 72 | |||
1146 | 73 | devnull_fp = None | 75 | devnull_fp = None |
1157 | 74 | try: | 76 | |
1158 | 75 | if target_path(target) != "/": | 77 | tpath = target_path(target) |
1159 | 76 | args = ['chroot', target] + list(args) | 78 | chroot_args = [] if tpath == "/" else ['chroot', target] |
1160 | 77 | 79 | sh_args = ['sh', '-c'] if shell else [] | |
1161 | 78 | if not logstring: | 80 | if isinstance(args, string_types): |
1162 | 79 | LOG.debug(("Running command %s with allowed return codes %s" | 81 | args = [args] |
1163 | 80 | " (shell=%s, capture=%s)"), args, rcs, shell, capture) | 82 | |
1164 | 81 | else: | 83 | try: |
1165 | 82 | LOG.debug(("Running hidden command to protect sensitive " | 84 | unshare_args = _get_unshare_pid_args(unshare_pid, tpath) |
1166 | 83 | "input/output logstring: %s"), logstring) | 85 | except RuntimeError as e: |
1167 | 86 | raise RuntimeError("Unable to unshare pid (cmd=%s): %s" % (args, e)) | ||
1168 | 87 | |||
1169 | 88 | args = unshare_args + chroot_args + sh_args + list(args) | ||
1170 | 89 | |||
1171 | 90 | if not logstring: | ||
1172 | 91 | LOG.debug(("Running command %s with allowed return codes %s" | ||
1173 | 92 | " (capture=%s)"), args, rcs, capture) | ||
1174 | 93 | else: | ||
1175 | 94 | LOG.debug(("Running hidden command to protect sensitive " | ||
1176 | 95 | "input/output logstring: %s"), logstring) | ||
1177 | 96 | try: | ||
1178 | 84 | stdin = None | 97 | stdin = None |
1179 | 85 | stdout = None | 98 | stdout = None |
1180 | 86 | stderr = None | 99 | stderr = None |
1181 | @@ -94,7 +107,7 @@ | |||
1182 | 94 | stdin = subprocess.PIPE | 107 | stdin = subprocess.PIPE |
1183 | 95 | sp = subprocess.Popen(args, stdout=stdout, | 108 | sp = subprocess.Popen(args, stdout=stdout, |
1184 | 96 | stderr=stderr, stdin=stdin, | 109 | stderr=stderr, stdin=stdin, |
1186 | 97 | env=env, shell=shell, cwd=cwd) | 110 | env=env, shell=False, cwd=cwd) |
1187 | 98 | # communicate in python2 returns str, python3 returns bytes | 111 | # communicate in python2 returns str, python3 returns bytes |
1188 | 99 | (out, err) = sp.communicate(data) | 112 | (out, err) = sp.communicate(data) |
1189 | 100 | 113 | ||
1190 | @@ -128,6 +141,63 @@ | |||
1191 | 128 | return (out, err) | 141 | return (out, err) |
1192 | 129 | 142 | ||
1193 | 130 | 143 | ||
1194 | 144 | def _has_unshare_pid(): | ||
1195 | 145 | global _HAS_UNSHARE_PID | ||
1196 | 146 | if _HAS_UNSHARE_PID is not None: | ||
1197 | 147 | return _HAS_UNSHARE_PID | ||
1198 | 148 | |||
1199 | 149 | if not which('unshare'): | ||
1200 | 150 | _HAS_UNSHARE_PID = False | ||
1201 | 151 | return False | ||
1202 | 152 | out, err = subp(["unshare", "--help"], capture=True, decode=False, | ||
1203 | 153 | unshare_pid=False) | ||
1204 | 154 | joined = b'\n'.join([out, err]) | ||
1205 | 155 | _HAS_UNSHARE_PID = b'--fork' in joined and b'--pid' in joined | ||
1206 | 156 | return _HAS_UNSHARE_PID | ||
1207 | 157 | |||
1208 | 158 | |||
1209 | 159 | def _get_unshare_pid_args(unshare_pid=None, target=None, euid=None): | ||
1210 | 160 | """Get args for calling unshare for a pid. | ||
1211 | 161 | |||
1212 | 162 | If unshare_pid is False, return empty list. | ||
1213 | 163 | If unshare_pid is True, check if it is usable. If not, raise exception. | ||
1214 | 164 | if unshare_pid is None, then unshare if | ||
1215 | 165 | * euid is 0 | ||
1216 | 166 | * 'unshare' with '--fork' and '--pid' is available. | ||
1217 | 167 | * target != / | ||
1218 | 168 | """ | ||
1219 | 169 | if unshare_pid is not None and not unshare_pid: | ||
1220 | 170 | # given a false-ish other than None means no. | ||
1221 | 171 | return [] | ||
1222 | 172 | |||
1223 | 173 | if euid is None: | ||
1224 | 174 | euid = os.geteuid() | ||
1225 | 175 | |||
1226 | 176 | tpath = target_path(target) | ||
1227 | 177 | |||
1228 | 178 | unshare_pid_in = unshare_pid | ||
1229 | 179 | if unshare_pid is None: | ||
1230 | 180 | unshare_pid = False | ||
1231 | 181 | if tpath != "/" and euid == 0: | ||
1232 | 182 | if _has_unshare_pid(): | ||
1233 | 183 | unshare_pid = True | ||
1234 | 184 | |||
1235 | 185 | if not unshare_pid: | ||
1236 | 186 | return [] | ||
1237 | 187 | |||
1238 | 188 | # either unshare was passed in as True, or None and turned to True. | ||
1239 | 189 | if euid != 0: | ||
1240 | 190 | raise RuntimeError( | ||
1241 | 191 | "given unshare_pid=%s but euid (%s) != 0." % | ||
1242 | 192 | (unshare_pid_in, euid)) | ||
1243 | 193 | |||
1244 | 194 | if not _has_unshare_pid(): | ||
1245 | 195 | raise RuntimeError( | ||
1246 | 196 | "given unshare_pid=%s but no unshare command." % unshare_pid_in) | ||
1247 | 197 | |||
1248 | 198 | return ['unshare', '--fork', '--pid', '--'] | ||
1249 | 199 | |||
1250 | 200 | |||
1251 | 131 | def subp(*args, **kwargs): | 201 | def subp(*args, **kwargs): |
1252 | 132 | """Run a subprocess. | 202 | """Run a subprocess. |
1253 | 133 | 203 | ||
1254 | @@ -160,6 +230,10 @@ | |||
1255 | 160 | means to run, sleep 1, run, sleep 3, run and then return exit code. | 230 | means to run, sleep 1, run, sleep 3, run and then return exit code. |
1256 | 161 | :param target: | 231 | :param target: |
1257 | 162 | run the command as 'chroot target <args>' | 232 | run the command as 'chroot target <args>' |
1258 | 233 | :param unshare_pid: | ||
1259 | 234 | unshare the pid namespace. | ||
1260 | 235 | default value (None) is to unshare pid namespace if possible | ||
1261 | 236 | and target != / | ||
1262 | 163 | 237 | ||
1263 | 164 | :return | 238 | :return |
1264 | 165 | if not capturing, return is (None, None) | 239 | if not capturing, return is (None, None) |
1265 | @@ -1275,6 +1349,9 @@ | |||
1266 | 1275 | if not path: | 1349 | if not path: |
1267 | 1276 | return target | 1350 | return target |
1268 | 1277 | 1351 | ||
1269 | 1352 | if not isinstance(path, string_types): | ||
1270 | 1353 | raise ValueError("Unexpected input for path: %s" % path) | ||
1271 | 1354 | |||
1272 | 1278 | # os.path.join("/etc", "/foo") returns "/foo". Chomp all leading /. | 1355 | # os.path.join("/etc", "/foo") returns "/foo". Chomp all leading /. |
1273 | 1279 | while len(path) and path[0] == "/": | 1356 | while len(path) and path[0] == "/": |
1274 | 1280 | path = path[1:] | 1357 | path = path[1:] |
1275 | @@ -1290,4 +1367,51 @@ | |||
1276 | 1290 | __call__ = ChrootableTarget.subp | 1367 | __call__ = ChrootableTarget.subp |
1277 | 1291 | 1368 | ||
1278 | 1292 | 1369 | ||
1279 | 1370 | def shlex_split(str_in): | ||
1280 | 1371 | # shlex.split takes a string | ||
1281 | 1372 | # but in python2 if input here is a unicode, encode it to a string. | ||
1282 | 1373 | # http://stackoverflow.com/questions/2365411/ | ||
1283 | 1374 | # python-convert-unicode-to-ascii-without-errors | ||
1284 | 1375 | if sys.version_info.major == 2: | ||
1285 | 1376 | try: | ||
1286 | 1377 | if isinstance(str_in, unicode): | ||
1287 | 1378 | str_in = str_in.encode('utf-8') | ||
1288 | 1379 | except NameError: | ||
1289 | 1380 | pass | ||
1290 | 1381 | |||
1291 | 1382 | return shlex.split(str_in) | ||
1292 | 1383 | else: | ||
1293 | 1384 | return shlex.split(str_in) | ||
1294 | 1385 | |||
1295 | 1386 | |||
1296 | 1387 | def load_shell_content(content, add_empty=False, empty_val=None): | ||
1297 | 1388 | """Given shell like syntax (key=value\nkey2=value2\n) in content | ||
1298 | 1389 | return the data in dictionary form. If 'add_empty' is True | ||
1299 | 1390 | then add entries in to the returned dictionary for 'VAR=' | ||
1300 | 1391 | variables. Set their value to empty_val.""" | ||
1301 | 1392 | |||
1302 | 1393 | data = {} | ||
1303 | 1394 | for line in shlex_split(content): | ||
1304 | 1395 | key, value = line.split("=", 1) | ||
1305 | 1396 | if not value: | ||
1306 | 1397 | value = empty_val | ||
1307 | 1398 | if add_empty or value: | ||
1308 | 1399 | data[key] = value | ||
1309 | 1400 | |||
1310 | 1401 | return data | ||
1311 | 1402 | |||
1312 | 1403 | |||
1313 | 1404 | def uses_systemd(): | ||
1314 | 1405 | """ Check if current enviroment uses systemd by testing if | ||
1315 | 1406 | /run/systemd/system is a directory; only present if | ||
1316 | 1407 | systemd is available on running system. | ||
1317 | 1408 | """ | ||
1318 | 1409 | |||
1319 | 1410 | global _USES_SYSTEMD | ||
1320 | 1411 | if _USES_SYSTEMD is None: | ||
1321 | 1412 | _USES_SYSTEMD = os.path.isdir('/run/systemd/system') | ||
1322 | 1413 | |||
1323 | 1414 | return _USES_SYSTEMD | ||
1324 | 1415 | |||
1325 | 1416 | |||
1326 | 1293 | # vi: ts=4 expandtab syntax=python | 1417 | # vi: ts=4 expandtab syntax=python |
1327 | 1294 | 1418 | ||
1328 | === modified file 'debian/changelog' | |||
1329 | --- debian/changelog 2017-06-12 20:52:38 +0000 | |||
1330 | +++ debian/changelog 2017-10-06 16:35:22 +0000 | |||
1331 | @@ -1,3 +1,36 @@ | |||
1332 | 1 | curtin (0.1.0~bzr532-0ubuntu1~16.04.1) xenial; urgency=medium | ||
1333 | 2 | |||
1334 | 3 | * New upstream snapshot. (LP: #1721808) | ||
1335 | 4 | - vmtest: fix artful networking | ||
1336 | 5 | - docs: Trivial doc fix for enabling proposed. | ||
1337 | 6 | - setup.py: fix to allow installation into a virtualenv | ||
1338 | 7 | - doc: update documentation on curtin-hooks and non-ubuntu installation. | ||
1339 | 8 | - reporter: Add journald reporter to send events to journald | ||
1340 | 9 | - vmtests: add option to tar disk images after test run | ||
1341 | 10 | - install: ensure iscsi service is running to handle shutdown properly | ||
1342 | 11 | - mdadm: handle write failures to sysfs entries when stopping mdadm | ||
1343 | 12 | - vmtest: catch exceptions in curtin-log-print | ||
1344 | 13 | - iscsi: use curtin storage config to disconnect iscsi targets | ||
1345 | 14 | - vmtests: bump skip_by_date values out to give cloud-init SRU more time | ||
1346 | 15 | - vmtest: get info about collected symlinks and then delete them. | ||
1347 | 16 | - Update network cloud-init related skiptest dates, SRU still pending | ||
1348 | 17 | - tests: Add CiTestCase common parent for all curtin tests. | ||
1349 | 18 | - vmtests: Remove force flag for centos curthooks | ||
1350 | 19 | - tools/jenkins-runner: improve tgtd cleanup logic | ||
1351 | 20 | - tests: Drop EOL Wily Vivid and Yakkety tests. | ||
1352 | 21 | - Disable yum plugins when installing packages, update ca-certs for https | ||
1353 | 22 | - Rename centos_network_curthooks -> centos_apply_network_config. | ||
1354 | 23 | - tests: in centos_defaults use write_files for grub serial. | ||
1355 | 24 | - write_files: write files after extract, change write_files signature. | ||
1356 | 25 | - pass network configuration through to target for ubuntu and centos | ||
1357 | 26 | - tests: disable yakkety tests. | ||
1358 | 27 | - tools/launch: automatically pass on proxy settings to curtin | ||
1359 | 28 | - Add top level 'proxy' to config, deprecate top level http_proxy. | ||
1360 | 29 | - tools/curtainer: fix to enable deb-src for -proposed. | ||
1361 | 30 | - Use unshare to put chroot commands in own pid namespace. | ||
1362 | 31 | |||
1363 | 32 | -- Chad Smith <chad.smith@canonical.com> Fri, 06 Oct 2017 10:07:36 -0600 | ||
1364 | 33 | |||
1365 | 1 | curtin (0.1.0~bzr505-0ubuntu1~16.04.1) xenial-proposed; urgency=medium | 34 | curtin (0.1.0~bzr505-0ubuntu1~16.04.1) xenial-proposed; urgency=medium |
1366 | 2 | 35 | ||
1367 | 3 | * debian/new-upstream-snapshot: create tarball in .. otherwise it | 36 | * debian/new-upstream-snapshot: create tarball in .. otherwise it |
1368 | 4 | 37 | ||
1369 | === modified file 'doc/index.rst' | |||
1370 | --- doc/index.rst 2016-10-03 18:42:29 +0000 | |||
1371 | +++ doc/index.rst 2017-10-06 16:35:22 +0000 | |||
1372 | @@ -17,6 +17,7 @@ | |||
1373 | 17 | topics/apt_source | 17 | topics/apt_source |
1374 | 18 | topics/networking | 18 | topics/networking |
1375 | 19 | topics/storage | 19 | topics/storage |
1376 | 20 | topics/curthooks | ||
1377 | 20 | topics/reporting | 21 | topics/reporting |
1378 | 21 | topics/development | 22 | topics/development |
1379 | 22 | topics/integration-testing | 23 | topics/integration-testing |
1380 | 23 | 24 | ||
1381 | === modified file 'doc/topics/apt_source.rst' | |||
1382 | --- doc/topics/apt_source.rst 2016-10-03 18:42:29 +0000 | |||
1383 | +++ doc/topics/apt_source.rst 2017-10-06 16:35:22 +0000 | |||
1384 | @@ -135,7 +135,9 @@ | |||
1385 | 135 | 135 | ||
1386 | 136 | apt: | 136 | apt: |
1387 | 137 | sources: | 137 | sources: |
1389 | 138 | proposed.list: deb $MIRROR $RELEASE-proposed main restricted universe multiverse | 138 | proposed.list: |
1390 | 139 | source: | | ||
1391 | 140 | deb $MIRROR $RELEASE-proposed main restricted universe multiverse | ||
1392 | 139 | 141 | ||
1393 | 140 | * Make debug symbols available | 142 | * Make debug symbols available |
1394 | 141 | 143 | ||
1395 | @@ -143,11 +145,12 @@ | |||
1396 | 143 | 145 | ||
1397 | 144 | apt: | 146 | apt: |
1398 | 145 | sources: | 147 | sources: |
1404 | 146 | ddebs.list: | | 148 | ddebs.list: |
1405 | 147 | deb http://ddebs.ubuntu.com $RELEASE main restricted universe multiverse | 149 | source: | |
1406 | 148 | Â deb http://ddebs.ubuntu.com $RELEASE-updates main restricted universe multiverse | 150 | deb http://ddebs.ubuntu.com $RELEASE main restricted universe multiverse |
1407 | 149 | Â deb http://ddebs.ubuntu.com $RELEASE-security main restricted universe multiverse | 151 | Â deb http://ddebs.ubuntu.com $RELEASE-updates main restricted universe multiverse |
1408 | 150 | deb http://ddebs.ubuntu.com $RELEASE-proposed main restricted universe multiverse | 152 | Â deb http://ddebs.ubuntu.com $RELEASE-security main restricted universe multiverse |
1409 | 153 | deb http://ddebs.ubuntu.com $RELEASE-proposed main restricted universe multiverse | ||
1410 | 151 | 154 | ||
1411 | 152 | Timing | 155 | Timing |
1412 | 153 | ~~~~~~ | 156 | ~~~~~~ |
1413 | 154 | 157 | ||
1414 | === modified file 'doc/topics/config.rst' | |||
1415 | --- doc/topics/config.rst 2016-10-03 18:42:29 +0000 | |||
1416 | +++ doc/topics/config.rst 2017-10-06 16:35:22 +0000 | |||
1417 | @@ -24,6 +24,7 @@ | |||
1418 | 24 | - multipath (``multipath``) | 24 | - multipath (``multipath``) |
1419 | 25 | - network (``network``) | 25 | - network (``network``) |
1420 | 26 | - power_state (``power_state``) | 26 | - power_state (``power_state``) |
1421 | 27 | - proxy (``proxy``) | ||
1422 | 27 | - reporting (``reporting``) | 28 | - reporting (``reporting``) |
1423 | 28 | - restore_dist_interfaces: (``restore_dist_interfaces``) | 29 | - restore_dist_interfaces: (``restore_dist_interfaces``) |
1424 | 29 | - sources (``sources``) | 30 | - sources (``sources``) |
1425 | @@ -177,6 +178,7 @@ | |||
1426 | 177 | http_proxy | 178 | http_proxy |
1427 | 178 | ~~~~~~~~~~ | 179 | ~~~~~~~~~~ |
1428 | 179 | Curtin will export ``http_proxy`` value into the installer environment. | 180 | Curtin will export ``http_proxy`` value into the installer environment. |
1429 | 181 | **Deprecated**: This setting is deprecated in favor of ``proxy`` below. | ||
1430 | 180 | 182 | ||
1431 | 181 | **http_proxy**: *<HTTP Proxy URL>* | 183 | **http_proxy**: *<HTTP Proxy URL>* |
1432 | 182 | 184 | ||
1433 | @@ -348,6 +350,22 @@ | |||
1434 | 348 | message: Bye Bye | 350 | message: Bye Bye |
1435 | 349 | 351 | ||
1436 | 350 | 352 | ||
1437 | 353 | proxy | ||
1438 | 354 | ~~~~~ | ||
1439 | 355 | Curtin will put ``http_proxy``, ``https_proxy`` and ``no_proxy`` | ||
1440 | 356 | into its install environment. This is in affect for curtin's process | ||
1441 | 357 | and subprocesses. | ||
1442 | 358 | |||
1443 | 359 | **proxy**: A dictionary containing http_proxy, https_proxy, and no_proxy. | ||
1444 | 360 | |||
1445 | 361 | **Example**:: | ||
1446 | 362 | |||
1447 | 363 | proxy: | ||
1448 | 364 | http_proxy: http://squid.proxy:3728/ | ||
1449 | 365 | https_proxy: http://squid.proxy:3728/ | ||
1450 | 366 | no_proxy: localhost,127.0.0.1,10.0.2.1 | ||
1451 | 367 | |||
1452 | 368 | |||
1453 | 351 | reporting | 369 | reporting |
1454 | 352 | ~~~~~~~~~ | 370 | ~~~~~~~~~ |
1455 | 353 | Configure installation reporting (see Reporting section for details). | 371 | Configure installation reporting (see Reporting section for details). |
1456 | 354 | 372 | ||
1457 | === added file 'doc/topics/curthooks.rst' | |||
1458 | --- doc/topics/curthooks.rst 1970-01-01 00:00:00 +0000 | |||
1459 | +++ doc/topics/curthooks.rst 2017-10-06 16:35:22 +0000 | |||
1460 | @@ -0,0 +1,109 @@ | |||
1461 | 1 | ======================================== | ||
1462 | 2 | Curthooks / New OS Support | ||
1463 | 3 | ======================================== | ||
1464 | 4 | Curtin has built-in support for installation of Ubuntu. | ||
1465 | 5 | Other operating systems are supported through a mechanism called | ||
1466 | 6 | 'curthooks' or 'curtin-hooks'. | ||
1467 | 7 | |||
1468 | 8 | A curtin install runs through different stages. See the | ||
1469 | 9 | :ref:`Stages <stages>` | ||
1470 | 10 | documentation for function of each stage. | ||
1471 | 11 | The stages communicate with each other via data in a working directory and | ||
1472 | 12 | environment variables as described in | ||
1473 | 13 | :ref:`Command Environment`. | ||
1474 | 14 | |||
1475 | 15 | Curtin handles partitioning, filesystem creation and target filesystem | ||
1476 | 16 | population for all operating systems. Curthooks are the mechanism provided | ||
1477 | 17 | so that the operating system can customize itself before reboot. This | ||
1478 | 18 | customization typically would need to include: | ||
1479 | 19 | |||
1480 | 20 | - ensuring that appropriate device drivers are loaded on first boot | ||
1481 | 21 | - consuming the network interfaces file and applying its declarations. | ||
1482 | 22 | - ensuring that necessary packages are installed to utilize storage | ||
1483 | 23 | configuration or networking configuration. | ||
1484 | 24 | - making the system boot (running grub-install or equivalent). | ||
1485 | 25 | |||
1486 | 26 | Image provided curtin-hooks | ||
1487 | 27 | --------------------------- | ||
1488 | 28 | An image provides curtin hooks support by containing a file | ||
1489 | 29 | ``/curtin/curtin-hooks``. | ||
1490 | 30 | |||
1491 | 31 | If an Ubuntu image image contains this path it will override the builtin | ||
1492 | 32 | curtin support. | ||
1493 | 33 | |||
1494 | 34 | The ``curtin-hooks`` program should be executable in the filesystem and | ||
1495 | 35 | will be executed without any arguments. It will be executed in the install | ||
1496 | 36 | environment, *not* the target environment. A change of root to the | ||
1497 | 37 | target environment can be done with ``curtin in-target``. | ||
1498 | 38 | |||
1499 | 39 | The hook is provided with some environment variables that can be used | ||
1500 | 40 | to find more information. See the :ref:`Command Environment` doc for | ||
1501 | 41 | details. Specifically interesting to this stage are: | ||
1502 | 42 | |||
1503 | 43 | - ``OUTPUT_NETWORK_CONFIG``: This is a path to the file created during | ||
1504 | 44 | network discovery stage. | ||
1505 | 45 | - ``OUTPUT_FSTAB``: This is a path to the file created during partitioning | ||
1506 | 46 | stage. | ||
1507 | 47 | - ``CONFIG``: This is a path to the curtin config file. It is provided so | ||
1508 | 48 | that additional configuration could be provided through to the OS | ||
1509 | 49 | customization. | ||
1510 | 50 | |||
1511 | 51 | .. **TODO**: We should add 'PYTHON' or 'CURTIN_PYTHON' to this environment | ||
1512 | 52 | so that the hook can easily run a python program with the same python | ||
1513 | 53 | that curtin ran with (ie, python2 or python3). | ||
1514 | 54 | |||
1515 | 55 | |||
1516 | 56 | Networking configuration | ||
1517 | 57 | ------------------------ | ||
1518 | 58 | Access to the network configuration that is desired is inside the config | ||
1519 | 59 | and is in the format described in :ref:`networking`. | ||
1520 | 60 | |||
1521 | 61 | .. TODO: We should guarantee that the presence | ||
1522 | 62 | of network config v1 in the file OUTPUT_NETWORK_CONFIG. | ||
1523 | 63 | |||
1524 | 64 | The curtin-hooks program must read the configuration from the | ||
1525 | 65 | path contained in ``OUTPUT_NETWORK_CONFIG`` and then set up | ||
1526 | 66 | the installed system to use it. | ||
1527 | 67 | |||
1528 | 68 | If the installed system has cloud-init at version 17.1 or higher, it may | ||
1529 | 69 | be possible to simply copy this section into the target in | ||
1530 | 70 | ``/etc/cloud/cloud.cfg.d/`` and let cloud-init render the correct | ||
1531 | 71 | networking on first boot. | ||
1532 | 72 | |||
1533 | 73 | Storage configuration | ||
1534 | 74 | --------------------- | ||
1535 | 75 | Access to the storage configuration that was set up is inside the config | ||
1536 | 76 | and is in the format described in :ref:`storage`. | ||
1537 | 77 | |||
1538 | 78 | .. TODO: We should guarantee that the presence | ||
1539 | 79 | of storage config v1 in the file OUTPUT_STORAGE_CONFIG. | ||
1540 | 80 | This would mean the user would not have to pull it out | ||
1541 | 81 | of CONFIG. We should guarantee its presence and format | ||
1542 | 82 | even in the 'simple' path. | ||
1543 | 83 | |||
1544 | 84 | To apply this storage configuration, the curthooks may need to: | ||
1545 | 85 | |||
1546 | 86 | * update /etc/fstab to add the expected mounts entries. The environment | ||
1547 | 87 | variable ``OUTPUT_FSTAB`` contains a path to a file that may be suitable | ||
1548 | 88 | for use. | ||
1549 | 89 | |||
1550 | 90 | * install any packages that are not already installed that are required | ||
1551 | 91 | to boot with the provided storage config. For example, if the storage | ||
1552 | 92 | layout includes raid you may need to install the mdadm package. | ||
1553 | 93 | |||
1554 | 94 | * update or create an initramfs. | ||
1555 | 95 | |||
1556 | 96 | |||
1557 | 97 | System boot | ||
1558 | 98 | ----------- | ||
1559 | 99 | In Ubuntu, curtin will run 'grub-setup' and to install grub. This covers | ||
1560 | 100 | putting the bootloader onto the disk(s) that are marked as | ||
1561 | 101 | ``grub_device``. The provided hook will need to do the equivalent | ||
1562 | 102 | operation. | ||
1563 | 103 | |||
1564 | 104 | finalize hook | ||
1565 | 105 | ------------- | ||
1566 | 106 | There is one other hook that curtin will invoke in an install, called | ||
1567 | 107 | ``finalize``. This program is invoked in the same environment as | ||
1568 | 108 | ``curtin-hooks`` above. It is intended to give the OS a final opportunity | ||
1569 | 109 | make updates before reboot. It is called before ``late_commands``. | ||
1570 | 0 | 110 | ||
1571 | === modified file 'doc/topics/integration-testing.rst' | |||
1572 | --- doc/topics/integration-testing.rst 2017-06-12 20:39:06 +0000 | |||
1573 | +++ doc/topics/integration-testing.rst 2017-10-06 16:35:22 +0000 | |||
1574 | @@ -161,6 +161,12 @@ | |||
1575 | 161 | - ``logs``: install and boot logs | 161 | - ``logs``: install and boot logs |
1576 | 162 | - ``collect``: data collected by the boot phase | 162 | - ``collect``: data collected by the boot phase |
1577 | 163 | 163 | ||
1578 | 164 | - ``CURTIN_VMTEST_TAR_DISKS``: default 0 | ||
1579 | 165 | |||
1580 | 166 | Vmtest writes out disk image files sparsely into a disks directory | ||
1581 | 167 | If this flag is set to a non-zero number, vmtest will tar all disks in | ||
1582 | 168 | the directory into a single disks.tar and remove the sparse disk files. | ||
1583 | 169 | |||
1584 | 164 | - ``CURTIN_VMTEST_TOPDIR``: default $TMPDIR/vmtest-<timestamp> | 170 | - ``CURTIN_VMTEST_TOPDIR``: default $TMPDIR/vmtest-<timestamp> |
1585 | 165 | 171 | ||
1586 | 166 | Vmtest puts all test data under this value. By default, it creates | 172 | Vmtest puts all test data under this value. By default, it creates |
1587 | 167 | 173 | ||
1588 | === modified file 'doc/topics/networking.rst' | |||
1589 | --- doc/topics/networking.rst 2016-10-03 18:42:29 +0000 | |||
1590 | +++ doc/topics/networking.rst 2017-10-06 16:35:22 +0000 | |||
1591 | @@ -1,3 +1,5 @@ | |||
1592 | 1 | .. _networking: | ||
1593 | 2 | |||
1594 | 1 | ========== | 3 | ========== |
1595 | 2 | Networking | 4 | Networking |
1596 | 3 | ========== | 5 | ========== |
1597 | 4 | 6 | ||
1598 | === modified file 'doc/topics/overview.rst' | |||
1599 | --- doc/topics/overview.rst 2016-10-03 18:42:29 +0000 | |||
1600 | +++ doc/topics/overview.rst 2017-10-06 16:35:22 +0000 | |||
1601 | @@ -4,6 +4,8 @@ | |||
1602 | 4 | 4 | ||
1603 | 5 | Curtin is intended to be a bare bones "installer". Its goal is to take data from a source, and get it onto disk as quick as possible and then boot it. The key difference from traditional package based installers is that curtin assumes the thing its installing is intelligent and will do the right thing. | 5 | Curtin is intended to be a bare bones "installer". Its goal is to take data from a source, and get it onto disk as quick as possible and then boot it. The key difference from traditional package based installers is that curtin assumes the thing its installing is intelligent and will do the right thing. |
1604 | 6 | 6 | ||
1605 | 7 | .. _Stages: | ||
1606 | 8 | |||
1607 | 7 | Stages | 9 | Stages |
1608 | 8 | ------ | 10 | ------ |
1609 | 9 | A usage of curtin will go through the following stages: | 11 | A usage of curtin will go through the following stages: |
1610 | @@ -22,6 +24,32 @@ | |||
1611 | 22 | 24 | ||
1612 | 23 | Curtin's assumption is that a fairly rich Linux (Ubuntu) environment is booted. | 25 | Curtin's assumption is that a fairly rich Linux (Ubuntu) environment is booted. |
1613 | 24 | 26 | ||
1614 | 27 | .. _Command Environment: | ||
1615 | 28 | |||
1616 | 29 | Command Environment | ||
1617 | 30 | ~~~~~~~~~~~~~~~~~~~ | ||
1618 | 31 | Stages and commands invoked by curtin always have the following environment | ||
1619 | 32 | variables defined. | ||
1620 | 33 | |||
1621 | 34 | - ``WORKING_DIR``: This is for inter-command state. It will be the same | ||
1622 | 35 | directory for each command run and will only be deleted at the end of the | ||
1623 | 36 | install. Files referenced in other environment variables will be in | ||
1624 | 37 | this directory. | ||
1625 | 38 | |||
1626 | 39 | - ``TARGET_MOUNT_POINT``: The path in the filesystem where the target | ||
1627 | 40 | filesystem will be mounted. | ||
1628 | 41 | |||
1629 | 42 | - ``OUTPUT_NETWORK_CONFIG``: After the network discovery stage, this file | ||
1630 | 43 | should contain networking config information that should then be written | ||
1631 | 44 | to the target. | ||
1632 | 45 | |||
1633 | 46 | - ``OUTPUT_FSTAB``: After partitioning and filesystem creation, this file | ||
1634 | 47 | will contain fstab(5) style content representing mounts. | ||
1635 | 48 | |||
1636 | 49 | - ``CONFIG``: This variable contains a path to a yaml formatted file with | ||
1637 | 50 | the fully rendered config. | ||
1638 | 51 | |||
1639 | 52 | |||
1640 | 25 | Early Commands | 53 | Early Commands |
1641 | 26 | ~~~~~~~~~~~~~~ | 54 | ~~~~~~~~~~~~~~ |
1642 | 27 | Early commands are executed on the system, and non-zero exit status will terminate the installation process. These commands are intended to be used for things like | 55 | Early commands are executed on the system, and non-zero exit status will terminate the installation process. These commands are intended to be used for things like |
1643 | @@ -48,32 +76,23 @@ | |||
1644 | 48 | 10_wipe_filesystems: curtin wipe --quick --all-unused-disks | 76 | 10_wipe_filesystems: curtin wipe --quick --all-unused-disks |
1645 | 49 | 50_setup_raid: curtin disk-setup --all-disks raid0 / | 77 | 50_setup_raid: curtin disk-setup --all-disks raid0 / |
1646 | 50 | 78 | ||
1673 | 51 | **Command environment** | 79 | |
1674 | 52 | 80 | Network Discovery | |
1675 | 53 | Partitioning commands have the following environment variables available to them: | 81 | ~~~~~~~~~~~~~~~~~ |
1676 | 54 | 82 | Networking configuration is *discovered* in the 'network' stage. | |
1677 | 55 | - ``WORKING_DIR``: This is simply for some sort of inter-command state. It will be the same directory for each command run and will only be deleted at the end of all partitioning_commands. | 83 | The default command run at this stage is ``curtin net-meta auto``. After |
1678 | 56 | - ``OUTPUT_FSTAB``: This is the target path for a fstab file. After all partitioning commands have been run, a file should exist, formatted per fstab(5) that describes how the filesystems should be mounted. | 84 | execution, it will write the discovered networking to the file specified |
1679 | 57 | - ``TARGET_MOUNT_POINT``: | 85 | in the environment variable ``OUTPUT_NETWORK_CONFIG``. The format of this |
1680 | 58 | 86 | file is as described in :ref:`networking`. | |
1681 | 59 | 87 | ||
1682 | 60 | Network Discovery and Setup | 88 | If curtin's config has a network section, the net-meta will simply parrot the |
1683 | 61 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 89 | data to the output file. If there is no network section, then its default |
1684 | 62 | Networking is done in a similar fashion to partitioning. A series of commands, specified in the config are run. At the end of these commands, a interfaces(5) style file is expected to be written to ``OUTPUT_INTERFACES``. | 90 | behavior is to copy existing config from the running environment. |
1685 | 63 | 91 | ||
1686 | 64 | Note, that as with fstab, this file is not copied verbatim to the target filesystem, but rather made available to the OS customization stage. That stage may just copy the file verbatim, but may also parse it, and use that as input. | 92 | Note, that as with fstab, this file is not copied verbatim to the target |
1687 | 65 | 93 | filesystem, but rather made available to the OS customization stage. That | |
1688 | 66 | **Config Example**:: | 94 | stage may just copy the file verbatim, but may also parse it, and apply the |
1689 | 67 | 95 | settings. | |
1664 | 68 | network_commands: | ||
1665 | 69 | 10_netconf: curtin network copy-existing | ||
1666 | 70 | |||
1667 | 71 | **Command environment** | ||
1668 | 72 | |||
1669 | 73 | Networking commands have the following environment variables available to them: | ||
1670 | 74 | |||
1671 | 75 | - ``WORKING_DIR``: This is simply for some sort of inter-command state. It will be the same directory for each command run and will only be deleted at the end of all network_commands. | ||
1672 | 76 | - ``OUTPUT_INTERFACES``: This is the target path for an interfaces style file. After all commands have been run, a file should exist, formatted per interfaces(5) that describes the systems network setup. | ||
1690 | 77 | 96 | ||
1691 | 78 | Extraction of sources | 97 | Extraction of sources |
1692 | 79 | ~~~~~~~~~~~~~~~~~~~~~ | 98 | ~~~~~~~~~~~~~~~~~~~~~ |
1693 | @@ -88,27 +107,6 @@ | |||
1694 | 88 | 107 | ||
1695 | 89 | wget $URL | tar -Sxvzf | 108 | wget $URL | tar -Sxvzf |
1696 | 90 | 109 | ||
1697 | 91 | Hook for installed OS to customize itself | ||
1698 | 92 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
1699 | 93 | After extraction of sources, the source that was extracted is then given a chance to customize itself for the system. This customization may include: | ||
1700 | 94 | - ensuring that appropriate device drivers are loaded on first boot | ||
1701 | 95 | - consuming the network interfaces file and applying its declarations. | ||
1702 | 96 | - ensuring that necessary packages | ||
1703 | 97 | |||
1704 | 98 | **Config Example**:: | ||
1705 | 99 | |||
1706 | 100 | config_hook: {{TARGET_MP}}/opt/curtin/config-hook | ||
1707 | 101 | |||
1708 | 102 | **Command environment** | ||
1709 | 103 | - ``INTERFACES``: This is a path to the file created during networking stage | ||
1710 | 104 | - ``FSTAB``: This is a path to the file created during partitioning stage | ||
1711 | 105 | - ``CONFIG``: This is a path to the curtin config file. It is provided so that additional configuration could be provided through to the OS customization. | ||
1712 | 106 | |||
1713 | 107 | **Helpers** | ||
1714 | 108 | |||
1715 | 109 | Curtin provides some helpers to make the OS customization easier. | ||
1716 | 110 | - `curtin in-target`: run the command while chrooted into the target. | ||
1717 | 111 | |||
1718 | 112 | Final Commands | 110 | Final Commands |
1719 | 113 | ~~~~~~~~~~~~~~ | 111 | ~~~~~~~~~~~~~~ |
1720 | 114 | 112 | ||
1721 | 115 | 113 | ||
1722 | === modified file 'doc/topics/reporting.rst' | |||
1723 | --- doc/topics/reporting.rst 2016-10-03 18:42:29 +0000 | |||
1724 | +++ doc/topics/reporting.rst 2017-10-06 16:35:22 +0000 | |||
1725 | @@ -10,6 +10,7 @@ | |||
1726 | 10 | Reporting consists of notification of a series of 'events. Each event has: | 10 | Reporting consists of notification of a series of 'events. Each event has: |
1727 | 11 | - **event_type**: 'start' or 'finish' | 11 | - **event_type**: 'start' or 'finish' |
1728 | 12 | - **description**: human readable text | 12 | - **description**: human readable text |
1729 | 13 | - **level**: the log level of the event, DEBUG/INFO/WARN etc. | ||
1730 | 13 | - **name**: and id for this event | 14 | - **name**: and id for this event |
1731 | 14 | - **result**: only present when event_type is 'finish', its value is one of "SUCCESS", "WARN", or "FAIL". A result of WARN indicates something is likely wrong, but a non-fatal error. A result of "FAIL" is fatal. | 15 | - **result**: only present when event_type is 'finish', its value is one of "SUCCESS", "WARN", or "FAIL". A result of WARN indicates something is likely wrong, but a non-fatal error. A result of "FAIL" is fatal. |
1732 | 15 | - **origin**: literal value 'curtin' | 16 | - **origin**: literal value 'curtin' |
1733 | @@ -75,6 +76,34 @@ | |||
1734 | 75 | is specified then all messages with a lower priority than specified will be | 76 | is specified then all messages with a lower priority than specified will be |
1735 | 76 | ignored. Default is INFO. | 77 | ignored. Default is INFO. |
1736 | 77 | 78 | ||
1737 | 79 | Journald Reporter | ||
1738 | 80 | ----------------- | ||
1739 | 81 | |||
1740 | 82 | The journald reporter sends the events to systemd's `journald`_. To enable, | ||
1741 | 83 | provide curtin with config like:: | ||
1742 | 84 | |||
1743 | 85 | reporting: | ||
1744 | 86 | mylistener: | ||
1745 | 87 | type: journald | ||
1746 | 88 | identifier: "my_identifier" | ||
1747 | 89 | level: DEBUG | ||
1748 | 90 | |||
1749 | 91 | The event's fields are mapped to fields of the resulting journal entry | ||
1750 | 92 | as follows: | ||
1751 | 93 | |||
1752 | 94 | - **description** maps to **CURTIN_MESSAGE** | ||
1753 | 95 | - **level** maps to **PRIORITY** | ||
1754 | 96 | - **name** maps to **CURTIN_NAME** | ||
1755 | 97 | - **event_type** maps to **CURTIN_EVENT_TYPE** | ||
1756 | 98 | - **result**, if present, maps to **CURTIN_RESULT** | ||
1757 | 99 | |||
1758 | 100 | The configured `identifier`, which defaults to "curtin_event", becomes | ||
1759 | 101 | the entry's **SYSLOG_IDENTIFIER**. | ||
1760 | 102 | |||
1761 | 103 | The python-systemd package must be installed to use this handler. | ||
1762 | 104 | |||
1763 | 105 | .. _`journald`: https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html | ||
1764 | 106 | |||
1765 | 78 | Example Events | 107 | Example Events |
1766 | 79 | ~~~~~~~~~~~~~~ | 108 | ~~~~~~~~~~~~~~ |
1767 | 80 | The following is an example event that would be posted:: | 109 | The following is an example event that would be posted:: |
1768 | 81 | 110 | ||
1769 | === modified file 'doc/topics/storage.rst' | |||
1770 | --- doc/topics/storage.rst 2017-06-12 20:39:06 +0000 | |||
1771 | +++ doc/topics/storage.rst 2017-10-06 16:35:22 +0000 | |||
1772 | @@ -1,3 +1,5 @@ | |||
1773 | 1 | .. _storage: | ||
1774 | 2 | |||
1775 | 1 | ======= | 3 | ======= |
1776 | 2 | Storage | 4 | Storage |
1777 | 3 | ======= | 5 | ======= |
1778 | 4 | 6 | ||
1779 | === modified file 'examples/network-ipv6-bond-vlan.yaml' | |||
1780 | --- examples/network-ipv6-bond-vlan.yaml 2016-10-03 18:42:29 +0000 | |||
1781 | +++ examples/network-ipv6-bond-vlan.yaml 2017-10-06 16:35:22 +0000 | |||
1782 | @@ -3,10 +3,10 @@ | |||
1783 | 3 | config: | 3 | config: |
1784 | 4 | - name: interface0 | 4 | - name: interface0 |
1785 | 5 | type: physical | 5 | type: physical |
1787 | 6 | mac_address: BC:76:4E:06:96:B3 | 6 | mac_address: bc:76:4e:06:96:b3 |
1788 | 7 | - name: interface1 | 7 | - name: interface1 |
1789 | 8 | type: physical | 8 | type: physical |
1791 | 9 | mac_address: BC:76:4E:04:88:41 | 9 | mac_address: bc:76:4e:04:88:41 |
1792 | 10 | - type: bond | 10 | - type: bond |
1793 | 11 | bond_interfaces: | 11 | bond_interfaces: |
1794 | 12 | - interface0 | 12 | - interface0 |
1795 | 13 | 13 | ||
1796 | === modified file 'examples/tests/bonding_network.yaml' | |||
1797 | --- examples/tests/bonding_network.yaml 2016-10-03 18:00:41 +0000 | |||
1798 | +++ examples/tests/bonding_network.yaml 2017-10-06 16:35:22 +0000 | |||
1799 | @@ -16,8 +16,7 @@ | |||
1800 | 16 | mac_address: "52:54:00:12:34:04" | 16 | mac_address: "52:54:00:12:34:04" |
1801 | 17 | # Bond. | 17 | # Bond. |
1802 | 18 | - type: bond | 18 | - type: bond |
1805 | 19 | name: bond0 | 19 | name: bond1 |
1804 | 20 | mac_address: "52:54:00:12:34:06" | ||
1806 | 21 | bond_interfaces: | 20 | bond_interfaces: |
1807 | 22 | - interface1 | 21 | - interface1 |
1808 | 23 | - interface2 | 22 | - interface2 |
1809 | @@ -26,8 +25,6 @@ | |||
1810 | 26 | subnets: | 25 | subnets: |
1811 | 27 | - type: static | 26 | - type: static |
1812 | 28 | address: 10.23.23.2/24 | 27 | address: 10.23.23.2/24 |
1813 | 29 | - type: static | ||
1814 | 30 | address: 10.23.24.2/24 | ||
1815 | 31 | 28 | ||
1816 | 32 | curthooks_commands: | 29 | curthooks_commands: |
1817 | 33 | # use curtin to disable open-iscsi ifupdown hooks for precise; they're | 30 | # use curtin to disable open-iscsi ifupdown hooks for precise; they're |
1818 | 34 | 31 | ||
1819 | === modified file 'examples/tests/centos_basic.yaml' | |||
1820 | --- examples/tests/centos_basic.yaml 2017-01-18 16:01:35 +0000 | |||
1821 | +++ examples/tests/centos_basic.yaml 2017-10-06 16:35:22 +0000 | |||
1822 | @@ -9,5 +9,6 @@ | |||
1823 | 9 | mac_address: "52:54:00:12:34:00" | 9 | mac_address: "52:54:00:12:34:00" |
1824 | 10 | subnets: | 10 | subnets: |
1825 | 11 | - type: static | 11 | - type: static |
1827 | 12 | address: 10.0.2.15/24 | 12 | address: 10.0.2.15 |
1828 | 13 | netmask: 255.255.255.0 | ||
1829 | 13 | gateway: 10.0.2.2 | 14 | gateway: 10.0.2.2 |
1830 | 14 | 15 | ||
1831 | === added file 'examples/tests/centos_defaults.yaml' | |||
1832 | --- examples/tests/centos_defaults.yaml 1970-01-01 00:00:00 +0000 | |||
1833 | +++ examples/tests/centos_defaults.yaml 2017-10-06 16:35:22 +0000 | |||
1834 | @@ -0,0 +1,91 @@ | |||
1835 | 1 | hook_commands: | ||
1836 | 2 | builtin: null | ||
1837 | 3 | |||
1838 | 4 | # To force curtin to run centos_apply_network_config vmtest, uncomment | ||
1839 | 5 | # _ammend_centos_curthooks: True | ||
1840 | 6 | |||
1841 | 7 | write_files: | ||
1842 | 8 | grub_serial_console: | ||
1843 | 9 | path: '/root/curtin-send-console-to-serial' | ||
1844 | 10 | permissions: '0755' | ||
1845 | 11 | owner: 'root:root' | ||
1846 | 12 | content: | | ||
1847 | 13 | # update grub1 and grub2 configs to write to serial console. | ||
1848 | 14 | CONPARM="console=ttyS0,115200" | ||
1849 | 15 | grub1conf="/boot/grub/grub.conf" | ||
1850 | 16 | grub2conf="/boot/grub2/grub.cfg" | ||
1851 | 17 | grub2def="/etc/default/grub" | ||
1852 | 18 | |||
1853 | 19 | rerror() { perror "$?" "$@"; return $r; } | ||
1854 | 20 | perror() { local r="$1"; shift; error "$@"; return $r; } | ||
1855 | 21 | error() { echo "GRUB_SERIAL:" "ERROR:" "$@" 1>&2; } | ||
1856 | 22 | info() { echo "GRUB_SERIAL:" "$@" 1>&2; } | ||
1857 | 23 | fail() { error "$@"; exit 1; } | ||
1858 | 24 | bk() { | ||
1859 | 25 | local ofile="$1" bk="$1.dist.curtin" | ||
1860 | 26 | shift | ||
1861 | 27 | [ -e "$ofile" ] || return 0 | ||
1862 | 28 | cp "$ofile" "$bk" || rerror "failed backup ($ofile -> $bk):" "$@"; | ||
1863 | 29 | } | ||
1864 | 30 | |||
1865 | 31 | update_grub1() { | ||
1866 | 32 | local cfg="$1" r="" | ||
1867 | 33 | [ -e "$cfg" ] || | ||
1868 | 34 | { info "no grub1 cfg '$cfg'"; return 0; } | ||
1869 | 35 | bk "$cfg" "grub1 config" || return | ||
1870 | 36 | if ! grep "^serial" "$cfg"; then | ||
1871 | 37 | cat >> "$cfg" <<EOF | ||
1872 | 38 | #curtin added | ||
1873 | 39 | serial --unit=0 --speed=115200 | ||
1874 | 40 | terminal --timeout=2 serial console | ||
1875 | 41 | EOF | ||
1876 | 42 | r=$? | ||
1877 | 43 | [ $r -eq 0 ] || | ||
1878 | 44 | { perror $r "failed to append to grub1 cfg '$cfg'"; return; } | ||
1879 | 45 | fi | ||
1880 | 46 | sed -i -e '/linux16/n' -e '/console=/n' \ | ||
1881 | 47 | -e "s/root=\([^ ]*\)/root=\1 ${CONPARM}/" "$cfg" || | ||
1882 | 48 | { rerror "failed to update grub1 cfg '$cfg'."; return; } | ||
1883 | 49 | info "updated grub1 cfg '$cfg'." | ||
1884 | 50 | } | ||
1885 | 51 | |||
1886 | 52 | update_grub2() { | ||
1887 | 53 | local cfg="$1" defgrub="$2" | ||
1888 | 54 | [ -e "$cfg" ] || { info "no grub2 config '$cfg'"; return 0; } | ||
1889 | 55 | bk "$cfg" "grub2 config" || return | ||
1890 | 56 | sed -i -e '/kernel/n' -e '/console=/n' \ | ||
1891 | 57 | -e "s/root=\([^ ]*\)/root=\1 ${CONPARM}/" "$cfg" || | ||
1892 | 58 | { rerror "failed to update grub2 '$cfg'"; return; } | ||
1893 | 59 | |||
1894 | 60 | # update /etc/default/grub. any GRUB_CMDLINE_LINUX remove | ||
1895 | 61 | # any console= and add conparm at the beginning. | ||
1896 | 62 | local var="GRUB_CMDLINE_LINUX" msg="updated grub2 '$cfg'." | ||
1897 | 63 | if [ ! -e "$defgrub" ]; then | ||
1898 | 64 | msg="$msg. no defaults file '$defgrub'." | ||
1899 | 65 | else | ||
1900 | 66 | bk "$defgrub" "grub2 defaults file" || return | ||
1901 | 67 | msg="$msg. updated defaults file '$defgrub'." | ||
1902 | 68 | sed -i \ | ||
1903 | 69 | -e "/$var=/!n" \ | ||
1904 | 70 | -e 's/console=[^ "]*//g' \ | ||
1905 | 71 | -e "s/$var=\"/$var=\"${CONPARM}/" "$defgrub" || | ||
1906 | 72 | { rerror "grub2 default update failed on $defgrub"; return; } | ||
1907 | 73 | fi | ||
1908 | 74 | info "$msg" | ||
1909 | 75 | } | ||
1910 | 76 | |||
1911 | 77 | update_grub1 "$grub1conf" || fail "failed update grub1" | ||
1912 | 78 | update_grub2 "$grub2conf" "$grub2def" || fail "failed update grub2" | ||
1913 | 79 | |||
1914 | 80 | late_commands: | ||
1915 | 81 | # centos66 images include grub 0.97 which will detect vmtests' ephemeral disk | ||
1916 | 82 | # and the install disk which leaves grub configured with two disks. When | ||
1917 | 83 | # vmtest reboots into installed disk, there is only one disk and the grub | ||
1918 | 84 | # map is no longer valid. Here in 00_grub, we switch hd1 to hd0. MAAS | ||
1919 | 85 | # is not affected as their ephemeral image (iscsi or http) is not discovered | ||
1920 | 86 | # by grub and therefor the device.map doesn't contain a second device. Cent7 | ||
1921 | 87 | # has grub2 which uses root by UUID | ||
1922 | 88 | 00_grub1_boot: [curtin, in-target, --, sed, -i.curtin, -e, | ||
1923 | 89 | 's|(hd1,0)|(hd0,0)|g', /boot/grub/grub.conf] | ||
1924 | 90 | # vmtest wants output to go to serial console so we update grub inside. | ||
1925 | 91 | 00_grub_serial: [curtin, in-target, --, '/root/curtin-send-console-to-serial'] | ||
1926 | 0 | 92 | ||
1927 | === added file 'examples/tests/journald_reporter.yaml' | |||
1928 | --- examples/tests/journald_reporter.yaml 1970-01-01 00:00:00 +0000 | |||
1929 | +++ examples/tests/journald_reporter.yaml 2017-10-06 16:35:22 +0000 | |||
1930 | @@ -0,0 +1,20 @@ | |||
1931 | 1 | reporting: | ||
1932 | 2 | journald: | ||
1933 | 3 | type: journald | ||
1934 | 4 | level: DEBUG | ||
1935 | 5 | |||
1936 | 6 | journal_cmds: | ||
1937 | 7 | - ©_journal_log | | ||
1938 | 8 | journalctl -b -o short-precise --no-pager -t curtin_event \ | ||
1939 | 9 | > ${TARGET_MOUNT_POINT}/root/journalctl.curtin_events.log | ||
1940 | 10 | |||
1941 | 11 | # use sed to make the json file loadable (listify the json) | ||
1942 | 12 | - ©_journal_json | | ||
1943 | 13 | journalctl -b -o json-pretty --no-pager -t curtin_event \ | ||
1944 | 14 | | sed -e '1i [' -e 's|^}|},|g' -e '$s|^},|}|' -e '$a]' \ | ||
1945 | 15 | > ${TARGET_MOUNT_POINT}/root/journalctl.curtin_events.json | ||
1946 | 16 | |||
1947 | 17 | # extract the journald entries for curtin | ||
1948 | 18 | late_commands: | ||
1949 | 19 | 00_copy_journal__log: [sh, -c, *copy_journal_log] | ||
1950 | 20 | 01_copy_journal_json: [sh, -c, *copy_journal_json] | ||
1951 | 0 | 21 | ||
1952 | === modified file 'examples/tests/network_alias.yaml' | |||
1953 | --- examples/tests/network_alias.yaml 2016-10-03 18:42:29 +0000 | |||
1954 | +++ examples/tests/network_alias.yaml 2017-10-06 16:35:22 +0000 | |||
1955 | @@ -8,29 +8,27 @@ | |||
1956 | 8 | mac_address: "52:54:00:12:34:00" | 8 | mac_address: "52:54:00:12:34:00" |
1957 | 9 | subnets: | 9 | subnets: |
1958 | 10 | - type: static | 10 | - type: static |
1961 | 11 | address: 192.168.1.2/24 | 11 | address: 10.47.98.1/24 |
1960 | 12 | mtu: 1501 | ||
1962 | 13 | - type: static | 12 | - type: static |
1963 | 14 | address: 2001:4800:78ff:1b:be76:4eff:fe06:ffac | 13 | address: 2001:4800:78ff:1b:be76:4eff:fe06:ffac |
1964 | 15 | netmask: 'ffff:ffff:ffff:ffff::' | 14 | netmask: 'ffff:ffff:ffff:ffff::' |
1965 | 16 | mtu: 1480 | ||
1966 | 17 | # multi_v4_alias: multiple v4 addrs on same interface | 15 | # multi_v4_alias: multiple v4 addrs on same interface |
1967 | 18 | - type: physical | 16 | - type: physical |
1968 | 19 | name: interface1 | 17 | name: interface1 |
1969 | 20 | mac_address: "52:54:00:12:34:02" | 18 | mac_address: "52:54:00:12:34:02" |
1970 | 21 | subnets: | 19 | subnets: |
1971 | 22 | - type: static | 20 | - type: static |
1973 | 23 | address: 192.168.2.2/22 | 21 | address: 192.168.20.2/24 |
1974 | 24 | routes: | 22 | routes: |
1978 | 25 | - network: 192.168.0.0 | 23 | - gateway: 192.168.20.1 |
1979 | 26 | netmask: 255.255.252.0 | 24 | netmask: 255.255.255.0 |
1980 | 27 | gateway: 192.168.2.1 | 25 | network: 10.242.47.0 |
1981 | 28 | - type: static | 26 | - type: static |
1983 | 29 | address: 10.23.23.7/23 | 27 | address: 10.23.22.7/23 |
1984 | 30 | routes: | 28 | routes: |
1988 | 31 | - gateway: 10.23.23.1 | 29 | - gateway: 10.23.22.2 |
1989 | 32 | netmask: 255.255.254.0 | 30 | netmask: 255.255.255.0 |
1990 | 33 | network: 10.23.22.0 | 31 | network: 10.49.253.0 |
1991 | 34 | # multi_v6_alias: multiple v6 addrs on same interface | 32 | # multi_v6_alias: multiple v6 addrs on same interface |
1992 | 35 | - type: physical | 33 | - type: physical |
1993 | 36 | name: interface2 | 34 | name: interface2 |
1994 | @@ -51,17 +49,17 @@ | |||
1995 | 51 | mac_address: "52:54:00:12:34:06" | 49 | mac_address: "52:54:00:12:34:06" |
1996 | 52 | subnets: | 50 | subnets: |
1997 | 53 | - type: static | 51 | - type: static |
1999 | 54 | address: 192.168.7.7/22 | 52 | address: 192.168.80.8/24 |
2000 | 55 | routes: | 53 | routes: |
2004 | 56 | - network: 192.168.0.0 | 54 | - gateway: 192.168.80.1 |
2005 | 57 | netmask: 255.255.252.0 | 55 | netmask: 255.255.255.0 |
2006 | 58 | gateway: 192.168.7.1 | 56 | network: 10.189.34.0 |
2007 | 59 | - type: static | 57 | - type: static |
2009 | 60 | address: 10.99.99.23/23 | 58 | address: 10.99.10.23/23 |
2010 | 61 | routes: | 59 | routes: |
2014 | 62 | - gateway: 10.99.99.1 | 60 | - gateway: 10.99.10.1 |
2015 | 63 | netmask: 255.255.254.0 | 61 | netmask: 255.255.255.0 |
2016 | 64 | network: 10.99.98.0 | 62 | network: 10.77.154.0 |
2017 | 65 | - type: static | 63 | - type: static |
2018 | 66 | address: 2001:4800:78ff:1b:be76:4eff:beef:4000 | 64 | address: 2001:4800:78ff:1b:be76:4eff:beef:4000 |
2019 | 67 | netmask: 'ffff:ffff:ffff:ffff::' | 65 | netmask: 'ffff:ffff:ffff:ffff::' |
2020 | @@ -86,17 +84,17 @@ | |||
2021 | 86 | address: 2001:4800:78ff:1b:be76:4eff:debe:9000 | 84 | address: 2001:4800:78ff:1b:be76:4eff:debe:9000 |
2022 | 87 | netmask: 'ffff:ffff:ffff:ffff::' | 85 | netmask: 'ffff:ffff:ffff:ffff::' |
2023 | 88 | - type: static | 86 | - type: static |
2025 | 89 | address: 192.168.100.100/22 | 87 | address: 192.168.100.100/24 |
2026 | 90 | routes: | 88 | routes: |
2030 | 91 | - network: 192.168.0.0 | 89 | - gateway: 192.168.100.1 |
2031 | 92 | netmask: 255.255.252.0 | 90 | netmask: 255.255.255.0 |
2032 | 93 | gateway: 192.168.100.1 | 91 | network: 10.28.219.0 |
2033 | 94 | - type: static | 92 | - type: static |
2034 | 95 | address: 10.17.142.2/23 | 93 | address: 10.17.142.2/23 |
2035 | 96 | routes: | 94 | routes: |
2036 | 97 | - gateway: 10.17.142.1 | 95 | - gateway: 10.17.142.1 |
2039 | 98 | netmask: 255.255.254.0 | 96 | netmask: 255.255.255.0 |
2040 | 99 | network: 10.17.142.0 | 97 | network: 10.82.49.0 |
2041 | 100 | # multi_v6_and_v4_mix_order: multiple v4 and v6 addr, mixed order | 98 | # multi_v6_and_v4_mix_order: multiple v4 and v6 addr, mixed order |
2042 | 101 | - type: physical | 99 | - type: physical |
2043 | 102 | name: interface5 | 100 | name: interface5 |
2044 | @@ -109,17 +107,17 @@ | |||
2045 | 109 | address: 2001:4800:78ff:1b:be76:4eff:baaf:c000 | 107 | address: 2001:4800:78ff:1b:be76:4eff:baaf:c000 |
2046 | 110 | netmask: 'ffff:ffff:ffff:ffff::' | 108 | netmask: 'ffff:ffff:ffff:ffff::' |
2047 | 111 | - type: static | 109 | - type: static |
2049 | 112 | address: 192.168.200.200/22 | 110 | address: 192.168.200.200/24 |
2050 | 113 | routes: | 111 | routes: |
2054 | 114 | - network: 192.168.0.0 | 112 | - gateway: 192.168.200.1 |
2055 | 115 | netmask: 255.255.252.0 | 113 | netmask: 255.255.255.0 |
2056 | 116 | gateway: 192.168.200.1 | 114 | network: 10.71.23.0 |
2057 | 117 | - type: static | 115 | - type: static |
2058 | 118 | address: 10.252.2.2/23 | 116 | address: 10.252.2.2/23 |
2059 | 119 | routes: | 117 | routes: |
2060 | 120 | - gateway: 10.252.2.1 | 118 | - gateway: 10.252.2.1 |
2063 | 121 | netmask: 255.255.254.0 | 119 | netmask: 255.255.255.0 |
2064 | 122 | network: 10.252.2.0 | 120 | network: 10.3.7.0 |
2065 | 123 | - type: static | 121 | - type: static |
2066 | 124 | address: 2001:4800:78ff:1b:be76:4eff:baaf:b000 | 122 | address: 2001:4800:78ff:1b:be76:4eff:baaf:b000 |
2067 | 125 | netmask: 'ffff:ffff:ffff:ffff::' | 123 | netmask: 'ffff:ffff:ffff:ffff::' |
2068 | 126 | 124 | ||
2069 | === modified file 'examples/tests/network_static_routes.yaml' | |||
2070 | --- examples/tests/network_static_routes.yaml 2017-02-08 22:22:44 +0000 | |||
2071 | +++ examples/tests/network_static_routes.yaml 2017-10-06 16:35:22 +0000 | |||
2072 | @@ -10,18 +10,13 @@ | |||
2073 | 10 | - address: 172.23.31.42/26 | 10 | - address: 172.23.31.42/26 |
2074 | 11 | gateway: 172.23.31.2 | 11 | gateway: 172.23.31.2 |
2075 | 12 | type: static | 12 | type: static |
2091 | 13 | - type: route | 13 | routes: |
2092 | 14 | id: 4 | 14 | - gateway: 172.23.31.1 |
2093 | 15 | metric: 0 | 15 | network: 10.0.0.0/12 |
2094 | 16 | destination: 10.0.0.0/12 | 16 | metric: 0 |
2095 | 17 | gateway: 172.23.31.1 | 17 | - gateway: 172.23.31.1 |
2096 | 18 | - type: route | 18 | network: 192.168.0.0/16 |
2097 | 19 | id: 5 | 19 | metric: 0 |
2098 | 20 | metric: 0 | 20 | - gateway: 172.23.31.1 |
2099 | 21 | destination: 192.168.0.0/16 | 21 | network: 10.200.0.0/16 |
2100 | 22 | gateway: 172.23.31.1 | 22 | metric: 1 |
2086 | 23 | - type: route | ||
2087 | 24 | id: 6 | ||
2088 | 25 | metric: 1 | ||
2089 | 26 | destination: 10.200.0.0/16 | ||
2090 | 27 | gateway: 172.23.31.1 | ||
2101 | 28 | 23 | ||
2102 | === added file 'examples/tests/network_v2_passthrough.yaml' | |||
2103 | --- examples/tests/network_v2_passthrough.yaml 1970-01-01 00:00:00 +0000 | |||
2104 | +++ examples/tests/network_v2_passthrough.yaml 2017-10-06 16:35:22 +0000 | |||
2105 | @@ -0,0 +1,8 @@ | |||
2106 | 1 | showtrace: true | ||
2107 | 2 | network: | ||
2108 | 3 | version: 2 | ||
2109 | 4 | ethernets: | ||
2110 | 5 | interface0: | ||
2111 | 6 | match: | ||
2112 | 7 | mac_address: "52:54:00:12:34:00" | ||
2113 | 8 | dhcp4: true | ||
2114 | 0 | 9 | ||
2115 | === modified file 'setup.py' | |||
2116 | --- setup.py 2016-10-03 18:42:29 +0000 | |||
2117 | +++ setup.py 2017-10-06 16:35:22 +0000 | |||
2118 | @@ -1,6 +1,7 @@ | |||
2119 | 1 | from distutils.core import setup | 1 | from distutils.core import setup |
2120 | 2 | from glob import glob | 2 | from glob import glob |
2121 | 3 | import os | 3 | import os |
2122 | 4 | import sys | ||
2123 | 4 | 5 | ||
2124 | 5 | import curtin | 6 | import curtin |
2125 | 6 | 7 | ||
2126 | @@ -8,6 +9,19 @@ | |||
2127 | 8 | def is_f(p): | 9 | def is_f(p): |
2128 | 9 | return os.path.isfile(p) | 10 | return os.path.isfile(p) |
2129 | 10 | 11 | ||
2130 | 12 | |||
2131 | 13 | def in_virtualenv(): | ||
2132 | 14 | try: | ||
2133 | 15 | if sys.real_prefix == sys.prefix: | ||
2134 | 16 | return False | ||
2135 | 17 | else: | ||
2136 | 18 | return True | ||
2137 | 19 | except AttributeError: | ||
2138 | 20 | return False | ||
2139 | 21 | |||
2140 | 22 | |||
2141 | 23 | USR = "usr" if in_virtualenv() else "/usr" | ||
2142 | 24 | |||
2143 | 11 | setup( | 25 | setup( |
2144 | 12 | name="curtin", | 26 | name="curtin", |
2145 | 13 | description='The curtin installer', | 27 | description='The curtin installer', |
2146 | @@ -27,9 +41,9 @@ | |||
2147 | 27 | ], | 41 | ], |
2148 | 28 | scripts=glob('bin/*'), | 42 | scripts=glob('bin/*'), |
2149 | 29 | data_files=[ | 43 | data_files=[ |
2151 | 30 | ('/usr/share/doc/curtin', | 44 | (USR + '/share/doc/curtin', |
2152 | 31 | [f for f in glob('doc/*') if is_f(f)]), | 45 | [f for f in glob('doc/*') if is_f(f)]), |
2154 | 32 | ('/usr/lib/curtin/helpers', | 46 | (USR + '/lib/curtin/helpers', |
2155 | 33 | [f for f in glob('helpers/*') if is_f(f)]) | 47 | [f for f in glob('helpers/*') if is_f(f)]) |
2156 | 34 | ] | 48 | ] |
2157 | 35 | ) | 49 | ) |
2158 | 36 | 50 | ||
2159 | === modified file 'tests/unittests/helpers.py' | |||
2160 | --- tests/unittests/helpers.py 2017-02-08 22:22:44 +0000 | |||
2161 | +++ tests/unittests/helpers.py 2017-10-06 16:35:22 +0000 | |||
2162 | @@ -19,6 +19,10 @@ | |||
2163 | 19 | import imp | 19 | import imp |
2164 | 20 | import importlib | 20 | import importlib |
2165 | 21 | import mock | 21 | import mock |
2166 | 22 | import os | ||
2167 | 23 | import shutil | ||
2168 | 24 | import tempfile | ||
2169 | 25 | from unittest import TestCase | ||
2170 | 22 | 26 | ||
2171 | 23 | 27 | ||
2172 | 24 | def builtin_module_name(): | 28 | def builtin_module_name(): |
2173 | @@ -43,3 +47,35 @@ | |||
2174 | 43 | m_patch = '{}.open'.format(mod_name) | 47 | m_patch = '{}.open'.format(mod_name) |
2175 | 44 | with mock.patch(m_patch, m_open, create=True): | 48 | with mock.patch(m_patch, m_open, create=True): |
2176 | 45 | yield m_open | 49 | yield m_open |
2177 | 50 | |||
2178 | 51 | |||
2179 | 52 | class CiTestCase(TestCase): | ||
2180 | 53 | """Common testing class which all curtin unit tests subclass.""" | ||
2181 | 54 | |||
2182 | 55 | def add_patch(self, target, attr, **kwargs): | ||
2183 | 56 | """Patches specified target object and sets it as attr on test | ||
2184 | 57 | instance also schedules cleanup""" | ||
2185 | 58 | if 'autospec' not in kwargs: | ||
2186 | 59 | kwargs['autospec'] = True | ||
2187 | 60 | m = mock.patch(target, **kwargs) | ||
2188 | 61 | p = m.start() | ||
2189 | 62 | self.addCleanup(m.stop) | ||
2190 | 63 | setattr(self, attr, p) | ||
2191 | 64 | |||
2192 | 65 | def tmp_dir(self, dir=None, cleanup=True): | ||
2193 | 66 | """Return a full path to a temporary directory for the test run.""" | ||
2194 | 67 | if dir is None: | ||
2195 | 68 | tmpd = tempfile.mkdtemp( | ||
2196 | 69 | prefix="curtin-ci-%s." % self.__class__.__name__) | ||
2197 | 70 | else: | ||
2198 | 71 | tmpd = tempfile.mkdtemp(dir=dir) | ||
2199 | 72 | self.addCleanup(shutil.rmtree, tmpd) | ||
2200 | 73 | return tmpd | ||
2201 | 74 | |||
2202 | 75 | def tmp_path(self, path, _dir=None): | ||
2203 | 76 | # return an absolute path to 'path' under dir. | ||
2204 | 77 | # if dir is None, one will be created with tmp_dir() | ||
2205 | 78 | # the file is not created or modified. | ||
2206 | 79 | if _dir is None: | ||
2207 | 80 | _dir = self.tmp_dir() | ||
2208 | 81 | return os.path.normpath(os.path.abspath(os.path.join(_dir, path))) | ||
2209 | 46 | 82 | ||
2210 | === modified file 'tests/unittests/test_apt_custom_sources_list.py' | |||
2211 | --- tests/unittests/test_apt_custom_sources_list.py 2016-10-03 18:42:29 +0000 | |||
2212 | +++ tests/unittests/test_apt_custom_sources_list.py 2017-10-06 16:35:22 +0000 | |||
2213 | @@ -3,10 +3,7 @@ | |||
2214 | 3 | """ | 3 | """ |
2215 | 4 | import logging | 4 | import logging |
2216 | 5 | import os | 5 | import os |
2217 | 6 | import shutil | ||
2218 | 7 | import tempfile | ||
2219 | 8 | 6 | ||
2220 | 9 | from unittest import TestCase | ||
2221 | 10 | 7 | ||
2222 | 11 | import yaml | 8 | import yaml |
2223 | 12 | import mock | 9 | import mock |
2224 | @@ -14,6 +11,7 @@ | |||
2225 | 14 | 11 | ||
2226 | 15 | from curtin import util | 12 | from curtin import util |
2227 | 16 | from curtin.commands import apt_config | 13 | from curtin.commands import apt_config |
2228 | 14 | from .helpers import CiTestCase | ||
2229 | 17 | 15 | ||
2230 | 18 | LOG = logging.getLogger(__name__) | 16 | LOG = logging.getLogger(__name__) |
2231 | 19 | 17 | ||
2232 | @@ -85,12 +83,11 @@ | |||
2233 | 85 | """) | 83 | """) |
2234 | 86 | 84 | ||
2235 | 87 | 85 | ||
2237 | 88 | class TestAptSourceConfigSourceList(TestCase): | 86 | class TestAptSourceConfigSourceList(CiTestCase): |
2238 | 89 | """TestAptSourceConfigSourceList - Class to test sources list rendering""" | 87 | """TestAptSourceConfigSourceList - Class to test sources list rendering""" |
2239 | 90 | def setUp(self): | 88 | def setUp(self): |
2240 | 91 | super(TestAptSourceConfigSourceList, self).setUp() | 89 | super(TestAptSourceConfigSourceList, self).setUp() |
2243 | 92 | self.new_root = tempfile.mkdtemp() | 90 | self.new_root = self.tmp_dir() |
2242 | 93 | self.addCleanup(shutil.rmtree, self.new_root) | ||
2244 | 94 | # self.patchUtils(self.new_root) | 91 | # self.patchUtils(self.new_root) |
2245 | 95 | 92 | ||
2246 | 96 | @staticmethod | 93 | @staticmethod |
2247 | 97 | 94 | ||
2248 | === modified file 'tests/unittests/test_apt_source.py' | |||
2249 | --- tests/unittests/test_apt_source.py 2017-03-01 16:13:56 +0000 | |||
2250 | +++ tests/unittests/test_apt_source.py 2017-10-06 16:35:22 +0000 | |||
2251 | @@ -4,11 +4,8 @@ | |||
2252 | 4 | import glob | 4 | import glob |
2253 | 5 | import os | 5 | import os |
2254 | 6 | import re | 6 | import re |
2255 | 7 | import shutil | ||
2256 | 8 | import socket | 7 | import socket |
2257 | 9 | import tempfile | ||
2258 | 10 | 8 | ||
2259 | 11 | from unittest import TestCase | ||
2260 | 12 | 9 | ||
2261 | 13 | import mock | 10 | import mock |
2262 | 14 | from mock import call | 11 | from mock import call |
2263 | @@ -16,6 +13,7 @@ | |||
2264 | 16 | from curtin import util | 13 | from curtin import util |
2265 | 17 | from curtin import gpg | 14 | from curtin import gpg |
2266 | 18 | from curtin.commands import apt_config | 15 | from curtin.commands import apt_config |
2267 | 16 | from .helpers import CiTestCase | ||
2268 | 19 | 17 | ||
2269 | 20 | 18 | ||
2270 | 21 | EXPECTEDKEY = u"""-----BEGIN PGP PUBLIC KEY BLOCK----- | 19 | EXPECTEDKEY = u"""-----BEGIN PGP PUBLIC KEY BLOCK----- |
2271 | @@ -62,14 +60,13 @@ | |||
2272 | 62 | ChrootableTargetStr = "curtin.commands.apt_config.util.ChrootableTarget" | 60 | ChrootableTargetStr = "curtin.commands.apt_config.util.ChrootableTarget" |
2273 | 63 | 61 | ||
2274 | 64 | 62 | ||
2276 | 65 | class TestAptSourceConfig(TestCase): | 63 | class TestAptSourceConfig(CiTestCase): |
2277 | 66 | """ TestAptSourceConfig | 64 | """ TestAptSourceConfig |
2278 | 67 | Main Class to test apt configs | 65 | Main Class to test apt configs |
2279 | 68 | """ | 66 | """ |
2280 | 69 | def setUp(self): | 67 | def setUp(self): |
2281 | 70 | super(TestAptSourceConfig, self).setUp() | 68 | super(TestAptSourceConfig, self).setUp() |
2284 | 71 | self.tmp = tempfile.mkdtemp() | 69 | self.tmp = self.tmp_dir() |
2283 | 72 | self.addCleanup(shutil.rmtree, self.tmp) | ||
2285 | 73 | self.aptlistfile = os.path.join(self.tmp, "single-deb.list") | 70 | self.aptlistfile = os.path.join(self.tmp, "single-deb.list") |
2286 | 74 | self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") | 71 | self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") |
2287 | 75 | self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") | 72 | self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") |
2288 | @@ -930,7 +927,7 @@ | |||
2289 | 930 | orig, apt_config.disable_suites(["proposed"], orig, rel)) | 927 | orig, apt_config.disable_suites(["proposed"], orig, rel)) |
2290 | 931 | 928 | ||
2291 | 932 | 929 | ||
2293 | 933 | class TestDebconfSelections(TestCase): | 930 | class TestDebconfSelections(CiTestCase): |
2294 | 934 | 931 | ||
2295 | 935 | @mock.patch("curtin.commands.apt_config.debconf_set_selections") | 932 | @mock.patch("curtin.commands.apt_config.debconf_set_selections") |
2296 | 936 | def test_no_set_sel_if_none_to_set(self, m_set_sel): | 933 | def test_no_set_sel_if_none_to_set(self, m_set_sel): |
2297 | 937 | 934 | ||
2298 | === modified file 'tests/unittests/test_basic.py' | |||
2299 | --- tests/unittests/test_basic.py 2013-07-29 16:12:09 +0000 | |||
2300 | +++ tests/unittests/test_basic.py 2017-10-06 16:35:22 +0000 | |||
2301 | @@ -1,7 +1,7 @@ | |||
2306 | 1 | from unittest import TestCase | 1 | from .helpers import CiTestCase |
2307 | 2 | 2 | ||
2308 | 3 | 3 | ||
2309 | 4 | class TestImport(TestCase): | 4 | class TestImport(CiTestCase): |
2310 | 5 | def test_import(self): | 5 | def test_import(self): |
2311 | 6 | import curtin | 6 | import curtin |
2312 | 7 | self.assertFalse(getattr(curtin, 'BOGUS_ENTRY', None)) | 7 | self.assertFalse(getattr(curtin, 'BOGUS_ENTRY', None)) |
2313 | 8 | 8 | ||
2314 | === modified file 'tests/unittests/test_block.py' | |||
2315 | --- tests/unittests/test_block.py 2017-06-12 20:39:06 +0000 | |||
2316 | +++ tests/unittests/test_block.py 2017-10-06 16:35:22 +0000 | |||
2317 | @@ -1,19 +1,16 @@ | |||
2318 | 1 | from unittest import TestCase | ||
2319 | 2 | import functools | 1 | import functools |
2320 | 3 | import os | 2 | import os |
2321 | 4 | import mock | 3 | import mock |
2322 | 5 | import tempfile | ||
2323 | 6 | import shutil | ||
2324 | 7 | import sys | 4 | import sys |
2325 | 8 | 5 | ||
2326 | 9 | from collections import OrderedDict | 6 | from collections import OrderedDict |
2327 | 10 | 7 | ||
2329 | 11 | from .helpers import simple_mocked_open | 8 | from .helpers import CiTestCase, simple_mocked_open |
2330 | 12 | from curtin import util | 9 | from curtin import util |
2331 | 13 | from curtin import block | 10 | from curtin import block |
2332 | 14 | 11 | ||
2333 | 15 | 12 | ||
2335 | 16 | class TestBlock(TestCase): | 13 | class TestBlock(CiTestCase): |
2336 | 17 | 14 | ||
2337 | 18 | @mock.patch("curtin.block.util") | 15 | @mock.patch("curtin.block.util") |
2338 | 19 | def test_get_volume_uuid(self, mock_util): | 16 | def test_get_volume_uuid(self, mock_util): |
2339 | @@ -103,7 +100,7 @@ | |||
2340 | 103 | block.lookup_disk(serial) | 100 | block.lookup_disk(serial) |
2341 | 104 | 101 | ||
2342 | 105 | 102 | ||
2344 | 106 | class TestSysBlockPath(TestCase): | 103 | class TestSysBlockPath(CiTestCase): |
2345 | 107 | @mock.patch("curtin.block.get_blockdev_for_partition") | 104 | @mock.patch("curtin.block.get_blockdev_for_partition") |
2346 | 108 | @mock.patch("os.path.exists") | 105 | @mock.patch("os.path.exists") |
2347 | 109 | def test_existing_valid_devname(self, m_os_path_exists, m_get_blk): | 106 | def test_existing_valid_devname(self, m_os_path_exists, m_get_blk): |
2348 | @@ -177,19 +174,13 @@ | |||
2349 | 177 | block.sys_block_path('/dev/cciss/c0d0p1')) | 174 | block.sys_block_path('/dev/cciss/c0d0p1')) |
2350 | 178 | 175 | ||
2351 | 179 | 176 | ||
2353 | 180 | class TestWipeFile(TestCase): | 177 | class TestWipeFile(CiTestCase): |
2354 | 181 | def __init__(self, *args, **kwargs): | 178 | def __init__(self, *args, **kwargs): |
2355 | 182 | super(TestWipeFile, self).__init__(*args, **kwargs) | 179 | super(TestWipeFile, self).__init__(*args, **kwargs) |
2356 | 183 | 180 | ||
2357 | 184 | def tfile(self, *args): | ||
2358 | 185 | # return a temp file in a dir that will be cleaned up | ||
2359 | 186 | tmpdir = tempfile.mkdtemp() | ||
2360 | 187 | self.addCleanup(shutil.rmtree, tmpdir) | ||
2361 | 188 | return os.path.sep.join([tmpdir] + list(args)) | ||
2362 | 189 | |||
2363 | 190 | def test_non_exist_raises_file_not_found(self): | 181 | def test_non_exist_raises_file_not_found(self): |
2364 | 191 | try: | 182 | try: |
2366 | 192 | p = self.tfile("enofile") | 183 | p = self.tmp_path("enofile") |
2367 | 193 | block.wipe_file(p) | 184 | block.wipe_file(p) |
2368 | 194 | raise Exception("%s did not raise exception" % p) | 185 | raise Exception("%s did not raise exception" % p) |
2369 | 195 | except Exception as e: | 186 | except Exception as e: |
2370 | @@ -198,7 +189,7 @@ | |||
2371 | 198 | 189 | ||
2372 | 199 | def test_non_exist_dir_raises_file_not_found(self): | 190 | def test_non_exist_dir_raises_file_not_found(self): |
2373 | 200 | try: | 191 | try: |
2375 | 201 | p = self.tfile("enodir", "file") | 192 | p = self.tmp_path(os.path.sep.join(["enodir", "file"])) |
2376 | 202 | block.wipe_file(p) | 193 | block.wipe_file(p) |
2377 | 203 | raise Exception("%s did not raise exception" % p) | 194 | raise Exception("%s did not raise exception" % p) |
2378 | 204 | except Exception as e: | 195 | except Exception as e: |
2379 | @@ -207,7 +198,7 @@ | |||
2380 | 207 | 198 | ||
2381 | 208 | def test_default_is_zero(self): | 199 | def test_default_is_zero(self): |
2382 | 209 | flen = 1024 | 200 | flen = 1024 |
2384 | 210 | myfile = self.tfile("def_zero") | 201 | myfile = self.tmp_path("def_zero") |
2385 | 211 | util.write_file(myfile, flen * b'\1', omode="wb") | 202 | util.write_file(myfile, flen * b'\1', omode="wb") |
2386 | 212 | block.wipe_file(myfile) | 203 | block.wipe_file(myfile) |
2387 | 213 | found = util.load_file(myfile, decode=False) | 204 | found = util.load_file(myfile, decode=False) |
2388 | @@ -219,7 +210,7 @@ | |||
2389 | 219 | def reader(size): | 210 | def reader(size): |
2390 | 220 | return size * b'\1' | 211 | return size * b'\1' |
2391 | 221 | 212 | ||
2393 | 222 | myfile = self.tfile("reader_used") | 213 | myfile = self.tmp_path("reader_used") |
2394 | 223 | # populate with nulls | 214 | # populate with nulls |
2395 | 224 | util.write_file(myfile, flen * b'\0', omode="wb") | 215 | util.write_file(myfile, flen * b'\0', omode="wb") |
2396 | 225 | block.wipe_file(myfile, reader=reader, buflen=flen) | 216 | block.wipe_file(myfile, reader=reader, buflen=flen) |
2397 | @@ -236,15 +227,15 @@ | |||
2398 | 236 | data['x'] = data['x'][size:] | 227 | data['x'] = data['x'][size:] |
2399 | 237 | return buf | 228 | return buf |
2400 | 238 | 229 | ||
2402 | 239 | myfile = self.tfile("reader_twice") | 230 | myfile = self.tmp_path("reader_twice") |
2403 | 240 | util.write_file(myfile, flen * b'\xff', omode="wb") | 231 | util.write_file(myfile, flen * b'\xff', omode="wb") |
2404 | 241 | block.wipe_file(myfile, reader=reader, buflen=20) | 232 | block.wipe_file(myfile, reader=reader, buflen=20) |
2405 | 242 | found = util.load_file(myfile, decode=False) | 233 | found = util.load_file(myfile, decode=False) |
2406 | 243 | self.assertEqual(found, expected) | 234 | self.assertEqual(found, expected) |
2407 | 244 | 235 | ||
2408 | 245 | def test_reader_fhandle(self): | 236 | def test_reader_fhandle(self): |
2411 | 246 | srcfile = self.tfile("fhandle_src") | 237 | srcfile = self.tmp_path("fhandle_src") |
2412 | 247 | trgfile = self.tfile("fhandle_trg") | 238 | trgfile = self.tmp_path("fhandle_trg") |
2413 | 248 | data = '\n'.join(["this is source file." for f in range(0, 10)] + []) | 239 | data = '\n'.join(["this is source file." for f in range(0, 10)] + []) |
2414 | 249 | util.write_file(srcfile, data) | 240 | util.write_file(srcfile, data) |
2415 | 250 | util.write_file(trgfile, 'a' * len(data)) | 241 | util.write_file(trgfile, 'a' * len(data)) |
2416 | @@ -254,7 +245,7 @@ | |||
2417 | 254 | self.assertEqual(data, found) | 245 | self.assertEqual(data, found) |
2418 | 255 | 246 | ||
2419 | 256 | def test_exclusive_open_raise_missing(self): | 247 | def test_exclusive_open_raise_missing(self): |
2421 | 257 | myfile = self.tfile("no-such-file") | 248 | myfile = self.tmp_path("no-such-file") |
2422 | 258 | 249 | ||
2423 | 259 | with self.assertRaises(ValueError): | 250 | with self.assertRaises(ValueError): |
2424 | 260 | with block.exclusive_open(myfile) as fp: | 251 | with block.exclusive_open(myfile) as fp: |
2425 | @@ -265,7 +256,7 @@ | |||
2426 | 265 | @mock.patch('os.open') | 256 | @mock.patch('os.open') |
2427 | 266 | def test_exclusive_open(self, mock_os_open, mock_os_fdopen, mock_os_close): | 257 | def test_exclusive_open(self, mock_os_open, mock_os_fdopen, mock_os_close): |
2428 | 267 | flen = 1024 | 258 | flen = 1024 |
2430 | 268 | myfile = self.tfile("my_exclusive_file") | 259 | myfile = self.tmp_path("my_exclusive_file") |
2431 | 269 | util.write_file(myfile, flen * b'\1', omode="wb") | 260 | util.write_file(myfile, flen * b'\1', omode="wb") |
2432 | 270 | mock_fd = 3 | 261 | mock_fd = 3 |
2433 | 271 | mock_os_open.return_value = mock_fd | 262 | mock_os_open.return_value = mock_fd |
2434 | @@ -288,7 +279,7 @@ | |||
2435 | 288 | mock_os_close, | 279 | mock_os_close, |
2436 | 289 | mock_util_fuser): | 280 | mock_util_fuser): |
2437 | 290 | flen = 1024 | 281 | flen = 1024 |
2439 | 291 | myfile = self.tfile("my_exclusive_file") | 282 | myfile = self.tmp_path("my_exclusive_file") |
2440 | 292 | util.write_file(myfile, flen * b'\1', omode="wb") | 283 | util.write_file(myfile, flen * b'\1', omode="wb") |
2441 | 293 | mock_os_open.side_effect = OSError("NO_O_EXCL") | 284 | mock_os_open.side_effect = OSError("NO_O_EXCL") |
2442 | 294 | mock_holders.return_value = ['md1'] | 285 | mock_holders.return_value = ['md1'] |
2443 | @@ -310,7 +301,7 @@ | |||
2444 | 310 | def test_exclusive_open_fdopen_failure(self, mock_os_open, | 301 | def test_exclusive_open_fdopen_failure(self, mock_os_open, |
2445 | 311 | mock_os_fdopen, mock_os_close): | 302 | mock_os_fdopen, mock_os_close): |
2446 | 312 | flen = 1024 | 303 | flen = 1024 |
2448 | 313 | myfile = self.tfile("my_exclusive_file") | 304 | myfile = self.tmp_path("my_exclusive_file") |
2449 | 314 | util.write_file(myfile, flen * b'\1', omode="wb") | 305 | util.write_file(myfile, flen * b'\1', omode="wb") |
2450 | 315 | mock_fd = 3 | 306 | mock_fd = 3 |
2451 | 316 | mock_os_open.return_value = mock_fd | 307 | mock_os_open.return_value = mock_fd |
2452 | @@ -328,7 +319,7 @@ | |||
2453 | 328 | self.assertEqual([], mock_os_close.call_args_list) | 319 | self.assertEqual([], mock_os_close.call_args_list) |
2454 | 329 | 320 | ||
2455 | 330 | 321 | ||
2457 | 331 | class TestWipeVolume(TestCase): | 322 | class TestWipeVolume(CiTestCase): |
2458 | 332 | dev = '/dev/null' | 323 | dev = '/dev/null' |
2459 | 333 | 324 | ||
2460 | 334 | @mock.patch('curtin.block.lvm') | 325 | @mock.patch('curtin.block.lvm') |
2461 | @@ -366,7 +357,7 @@ | |||
2462 | 366 | block.wipe_volume(self.dev, mode='invalidmode') | 357 | block.wipe_volume(self.dev, mode='invalidmode') |
2463 | 367 | 358 | ||
2464 | 368 | 359 | ||
2466 | 369 | class TestBlockKnames(TestCase): | 360 | class TestBlockKnames(CiTestCase): |
2467 | 370 | """Tests for some of the kname functions in block""" | 361 | """Tests for some of the kname functions in block""" |
2468 | 371 | def test_determine_partition_kname(self): | 362 | def test_determine_partition_kname(self): |
2469 | 372 | part_knames = [(('sda', 1), 'sda1'), | 363 | part_knames = [(('sda', 1), 'sda1'), |
2470 | @@ -430,7 +421,7 @@ | |||
2471 | 430 | block.kname_to_path(kname) | 421 | block.kname_to_path(kname) |
2472 | 431 | 422 | ||
2473 | 432 | 423 | ||
2475 | 433 | class TestPartTableSignature(TestCase): | 424 | class TestPartTableSignature(CiTestCase): |
2476 | 434 | blockdev = '/dev/null' | 425 | blockdev = '/dev/null' |
2477 | 435 | dos_content = b'\x00' * 0x1fe + b'\x55\xAA' + b'\x00' * 0xf00 | 426 | dos_content = b'\x00' * 0x1fe + b'\x55\xAA' + b'\x00' * 0xf00 |
2478 | 436 | gpt_content = b'\x00' * 0x200 + b'EFI PART' + b'\x00' * (0x200 - 8) | 427 | gpt_content = b'\x00' * 0x200 + b'EFI PART' + b'\x00' * (0x200 - 8) |
2479 | @@ -493,7 +484,7 @@ | |||
2480 | 493 | block.check_efi_signature(self.blockdev)) | 484 | block.check_efi_signature(self.blockdev)) |
2481 | 494 | 485 | ||
2482 | 495 | 486 | ||
2484 | 496 | class TestNonAscii(TestCase): | 487 | class TestNonAscii(CiTestCase): |
2485 | 497 | @mock.patch('curtin.block.util.subp') | 488 | @mock.patch('curtin.block.util.subp') |
2486 | 498 | def test_lsblk(self, mock_subp): | 489 | def test_lsblk(self, mock_subp): |
2487 | 499 | # lsblk can write non-ascii data, causing shlex to blow up | 490 | # lsblk can write non-ascii data, causing shlex to blow up |
2488 | @@ -519,14 +510,7 @@ | |||
2489 | 519 | block.blkid() | 510 | block.blkid() |
2490 | 520 | 511 | ||
2491 | 521 | 512 | ||
2500 | 522 | class TestSlaveKnames(TestCase): | 513 | class TestSlaveKnames(CiTestCase): |
2493 | 523 | def add_patch(self, target, attr, autospec=True): | ||
2494 | 524 | """Patches specified target object and sets it as attr on test | ||
2495 | 525 | instance also schedules cleanup""" | ||
2496 | 526 | m = mock.patch(target, autospec=autospec) | ||
2497 | 527 | p = m.start() | ||
2498 | 528 | self.addCleanup(m.stop) | ||
2499 | 529 | setattr(self, attr, p) | ||
2501 | 530 | 514 | ||
2502 | 531 | def setUp(self): | 515 | def setUp(self): |
2503 | 532 | super(TestSlaveKnames, self).setUp() | 516 | super(TestSlaveKnames, self).setUp() |
2504 | 533 | 517 | ||
2505 | === modified file 'tests/unittests/test_block_iscsi.py' | |||
2506 | --- tests/unittests/test_block_iscsi.py 2017-06-12 20:39:06 +0000 | |||
2507 | +++ tests/unittests/test_block_iscsi.py 2017-10-06 16:35:22 +0000 | |||
2508 | @@ -1,23 +1,13 @@ | |||
2509 | 1 | import mock | 1 | import mock |
2510 | 2 | import os | ||
2511 | 2 | 3 | ||
2512 | 3 | from unittest import TestCase | ||
2513 | 4 | from curtin.block import iscsi | 4 | from curtin.block import iscsi |
2530 | 5 | 5 | from curtin import util | |
2531 | 6 | 6 | from .helpers import CiTestCase | |
2532 | 7 | class IscsiTestBase(TestCase): | 7 | |
2533 | 8 | def setUp(self): | 8 | |
2534 | 9 | super(IscsiTestBase, self).setUp() | 9 | class TestBlockIscsiPortalParsing(CiTestCase): |
2535 | 10 | 10 | ||
2520 | 11 | def add_patch(self, target, attr): | ||
2521 | 12 | """Patches specified target object and sets it as attr on test | ||
2522 | 13 | instance also schedules cleanup""" | ||
2523 | 14 | m = mock.patch(target, autospec=True) | ||
2524 | 15 | p = m.start() | ||
2525 | 16 | self.addCleanup(m.stop) | ||
2526 | 17 | setattr(self, attr, p) | ||
2527 | 18 | |||
2528 | 19 | |||
2529 | 20 | class TestBlockIscsiPortalParsing(IscsiTestBase): | ||
2536 | 21 | def test_iscsi_portal_parsing_string(self): | 11 | def test_iscsi_portal_parsing_string(self): |
2537 | 22 | with self.assertRaisesRegexp(ValueError, 'not a string'): | 12 | with self.assertRaisesRegexp(ValueError, 'not a string'): |
2538 | 23 | iscsi.assert_valid_iscsi_portal(1234) | 13 | iscsi.assert_valid_iscsi_portal(1234) |
2539 | @@ -490,7 +480,7 @@ | |||
2540 | 490 | self.assertEquals(i.target, 'iqn.2017-04.com.example.test:target-name') | 480 | self.assertEquals(i.target, 'iqn.2017-04.com.example.test:target-name') |
2541 | 491 | 481 | ||
2542 | 492 | 482 | ||
2544 | 493 | class TestBlockIscsiVolPath(IscsiTestBase): | 483 | class TestBlockIscsiVolPath(CiTestCase): |
2545 | 494 | # non-iscsi backed disk returns false | 484 | # non-iscsi backed disk returns false |
2546 | 495 | # regular iscsi-backed disk returns true | 485 | # regular iscsi-backed disk returns true |
2547 | 496 | # layered setup without an iscsi member returns false | 486 | # layered setup without an iscsi member returns false |
2548 | @@ -569,4 +559,183 @@ | |||
2549 | 569 | with self.assertRaises(ValueError): | 559 | with self.assertRaises(ValueError): |
2550 | 570 | iscsi.volpath_is_iscsi(None) | 560 | iscsi.volpath_is_iscsi(None) |
2551 | 571 | 561 | ||
2552 | 562 | |||
2553 | 563 | class TestBlockIscsiDiskFromConfig(CiTestCase): | ||
2554 | 564 | # Test iscsi parsing of storage config for iscsi configure disks | ||
2555 | 565 | |||
2556 | 566 | def setUp(self): | ||
2557 | 567 | super(TestBlockIscsiDiskFromConfig, self).setUp() | ||
2558 | 568 | self.add_patch('curtin.block.iscsi.util.subp', 'mock_subp') | ||
2559 | 569 | |||
2560 | 570 | def test_parse_iscsi_disk_from_config(self): | ||
2561 | 571 | """Test parsing iscsi volume path creates the same iscsi disk""" | ||
2562 | 572 | target = 'curtin-659d5f45-4f23-46cb-b826-f2937b896e09' | ||
2563 | 573 | iscsi_path = 'iscsi:10.245.168.20::20112:1:' + target | ||
2564 | 574 | cfg = { | ||
2565 | 575 | 'storage': { | ||
2566 | 576 | 'config': [{'type': 'disk', | ||
2567 | 577 | 'id': 'iscsidev1', | ||
2568 | 578 | 'path': iscsi_path, | ||
2569 | 579 | 'name': 'iscsi_disk1', | ||
2570 | 580 | 'ptable': 'msdos', | ||
2571 | 581 | 'wipe': 'superblock'}] | ||
2572 | 582 | } | ||
2573 | 583 | } | ||
2574 | 584 | expected_iscsi_disk = iscsi.IscsiDisk(iscsi_path) | ||
2575 | 585 | iscsi_disk = iscsi.get_iscsi_disks_from_config(cfg).pop() | ||
2576 | 586 | # utilize IscsiDisk str method for equality check | ||
2577 | 587 | self.assertEqual(str(expected_iscsi_disk), str(iscsi_disk)) | ||
2578 | 588 | |||
2579 | 589 | def test_parse_iscsi_disk_from_config_no_iscsi(self): | ||
2580 | 590 | """Test parsing storage config with no iscsi disks included""" | ||
2581 | 591 | cfg = { | ||
2582 | 592 | 'storage': { | ||
2583 | 593 | 'config': [{'type': 'disk', | ||
2584 | 594 | 'id': 'ssd1', | ||
2585 | 595 | 'path': 'dev/slash/foo1', | ||
2586 | 596 | 'name': 'the-fast-one', | ||
2587 | 597 | 'ptable': 'gpt', | ||
2588 | 598 | 'wipe': 'superblock'}] | ||
2589 | 599 | } | ||
2590 | 600 | } | ||
2591 | 601 | expected_iscsi_disks = [] | ||
2592 | 602 | iscsi_disks = iscsi.get_iscsi_disks_from_config(cfg) | ||
2593 | 603 | self.assertEqual(expected_iscsi_disks, iscsi_disks) | ||
2594 | 604 | |||
2595 | 605 | def test_parse_iscsi_disk_from_config_invalid_iscsi(self): | ||
2596 | 606 | """Test parsing storage config with no iscsi disks included""" | ||
2597 | 607 | cfg = { | ||
2598 | 608 | 'storage': { | ||
2599 | 609 | 'config': [{'type': 'disk', | ||
2600 | 610 | 'id': 'iscsidev2', | ||
2601 | 611 | 'path': 'iscsi:garbage', | ||
2602 | 612 | 'name': 'noob-city', | ||
2603 | 613 | 'ptable': 'msdos', | ||
2604 | 614 | 'wipe': 'superblock'}] | ||
2605 | 615 | } | ||
2606 | 616 | } | ||
2607 | 617 | with self.assertRaises(ValueError): | ||
2608 | 618 | iscsi.get_iscsi_disks_from_config(cfg) | ||
2609 | 619 | |||
2610 | 620 | def test_parse_iscsi_disk_from_config_empty(self): | ||
2611 | 621 | """Test parse_iscsi_disks handles empty/invalid config""" | ||
2612 | 622 | expected_iscsi_disks = [] | ||
2613 | 623 | iscsi_disks = iscsi.get_iscsi_disks_from_config({}) | ||
2614 | 624 | self.assertEqual(expected_iscsi_disks, iscsi_disks) | ||
2615 | 625 | |||
2616 | 626 | cfg = {'storage': {'config': []}} | ||
2617 | 627 | iscsi_disks = iscsi.get_iscsi_disks_from_config(cfg) | ||
2618 | 628 | self.assertEqual(expected_iscsi_disks, iscsi_disks) | ||
2619 | 629 | |||
2620 | 630 | def test_parse_iscsi_disk_from_config_none(self): | ||
2621 | 631 | """Test parse_iscsi_disks handles no config""" | ||
2622 | 632 | expected_iscsi_disks = [] | ||
2623 | 633 | iscsi_disks = iscsi.get_iscsi_disks_from_config({}) | ||
2624 | 634 | self.assertEqual(expected_iscsi_disks, iscsi_disks) | ||
2625 | 635 | |||
2626 | 636 | cfg = None | ||
2627 | 637 | iscsi_disks = iscsi.get_iscsi_disks_from_config(cfg) | ||
2628 | 638 | self.assertEqual(expected_iscsi_disks, iscsi_disks) | ||
2629 | 639 | |||
2630 | 640 | |||
2631 | 641 | class TestBlockIscsiDisconnect(CiTestCase): | ||
2632 | 642 | # test that when disconnecting iscsi targets we | ||
2633 | 643 | # check that the target has an active session before | ||
2634 | 644 | # issuing a disconnect command | ||
2635 | 645 | |||
2636 | 646 | def setUp(self): | ||
2637 | 647 | super(TestBlockIscsiDisconnect, self).setUp() | ||
2638 | 648 | self.add_patch('curtin.block.iscsi.util.subp', 'mock_subp') | ||
2639 | 649 | self.add_patch('curtin.block.iscsi.iscsiadm_sessions', | ||
2640 | 650 | 'mock_iscsi_sessions') | ||
2641 | 651 | # fake target_root + iscsi nodes dir | ||
2642 | 652 | self.target_path = self.tmp_dir() | ||
2643 | 653 | self.iscsi_nodes = os.path.join(self.target_path, 'etc/iscsi/nodes') | ||
2644 | 654 | util.ensure_dir(self.iscsi_nodes) | ||
2645 | 655 | |||
2646 | 656 | def _fmt_disconnect(self, target, portal): | ||
2647 | 657 | return ['iscsiadm', '--mode=node', '--targetname=%s' % target, | ||
2648 | 658 | '--portal=%s' % portal, '--logout'] | ||
2649 | 659 | |||
2650 | 660 | def _setup_nodes(self, sessions, connection): | ||
2651 | 661 | # setup iscsi_nodes dir (<fakeroot>/etc/iscsi/nodes) with content | ||
2652 | 662 | for s in sessions: | ||
2653 | 663 | sdir = os.path.join(self.iscsi_nodes, s) | ||
2654 | 664 | connpath = os.path.join(sdir, connection) | ||
2655 | 665 | util.ensure_dir(sdir) | ||
2656 | 666 | util.write_file(connpath, content="") | ||
2657 | 667 | |||
2658 | 668 | def test_disconnect_target_disk(self): | ||
2659 | 669 | """Test iscsi disconnecting multiple sessions, all present""" | ||
2660 | 670 | |||
2661 | 671 | sessions = [ | ||
2662 | 672 | 'curtin-53ab23ff-a887-449a-80a8-288151208091', | ||
2663 | 673 | 'curtin-94b62de1-c579-42c0-879e-8a28178e64c5', | ||
2664 | 674 | 'curtin-556aeecd-a227-41b7-83d7-2bb471c574b4', | ||
2665 | 675 | 'curtin-fd0f644b-7858-420f-9997-3ea2aefe87b9' | ||
2666 | 676 | ] | ||
2667 | 677 | connection = '10.245.168.20,16395,1' | ||
2668 | 678 | self._setup_nodes(sessions, connection) | ||
2669 | 679 | |||
2670 | 680 | self.mock_iscsi_sessions.return_value = "\n".join(sessions) | ||
2671 | 681 | |||
2672 | 682 | iscsi.disconnect_target_disks(self.target_path) | ||
2673 | 683 | |||
2674 | 684 | expected_calls = [] | ||
2675 | 685 | for session in sessions: | ||
2676 | 686 | (host, port, _) = connection.split(',') | ||
2677 | 687 | disconnect = self._fmt_disconnect(session, "%s:%s" % (host, port)) | ||
2678 | 688 | calls = [ | ||
2679 | 689 | mock.call(['sync']), | ||
2680 | 690 | mock.call(disconnect, capture=True, log_captured=True), | ||
2681 | 691 | mock.call(['udevadm', 'settle']), | ||
2682 | 692 | ] | ||
2683 | 693 | expected_calls.extend(calls) | ||
2684 | 694 | |||
2685 | 695 | self.mock_subp.assert_has_calls(expected_calls, any_order=True) | ||
2686 | 696 | |||
2687 | 697 | def test_disconnect_target_disk_skip_disconnected(self): | ||
2688 | 698 | """Test iscsi does not attempt to disconnect already closed sessions""" | ||
2689 | 699 | sessions = [ | ||
2690 | 700 | 'curtin-53ab23ff-a887-449a-80a8-288151208091', | ||
2691 | 701 | 'curtin-94b62de1-c579-42c0-879e-8a28178e64c5', | ||
2692 | 702 | 'curtin-556aeecd-a227-41b7-83d7-2bb471c574b4', | ||
2693 | 703 | 'curtin-fd0f644b-7858-420f-9997-3ea2aefe87b9' | ||
2694 | 704 | ] | ||
2695 | 705 | connection = '10.245.168.20,16395,1' | ||
2696 | 706 | self._setup_nodes(sessions, connection) | ||
2697 | 707 | # Test with all sessions are already disconnected | ||
2698 | 708 | self.mock_iscsi_sessions.return_value = "" | ||
2699 | 709 | |||
2700 | 710 | iscsi.disconnect_target_disks(self.target_path) | ||
2701 | 711 | |||
2702 | 712 | self.mock_subp.assert_has_calls([], any_order=True) | ||
2703 | 713 | |||
2704 | 714 | @mock.patch('curtin.block.iscsi.iscsiadm_logout') | ||
2705 | 715 | def test_disconnect_target_disk_raises_runtime_error(self, mock_logout): | ||
2706 | 716 | """Test iscsi raises RuntimeError if we fail to logout""" | ||
2707 | 717 | sessions = [ | ||
2708 | 718 | 'curtin-53ab23ff-a887-449a-80a8-288151208091', | ||
2709 | 719 | ] | ||
2710 | 720 | connection = '10.245.168.20,16395,1' | ||
2711 | 721 | self._setup_nodes(sessions, connection) | ||
2712 | 722 | self.mock_iscsi_sessions.return_value = "\n".join(sessions) | ||
2713 | 723 | mock_logout.side_effect = util.ProcessExecutionError() | ||
2714 | 724 | |||
2715 | 725 | with self.assertRaises(RuntimeError): | ||
2716 | 726 | iscsi.disconnect_target_disks(self.target_path) | ||
2717 | 727 | |||
2718 | 728 | expected_calls = [] | ||
2719 | 729 | for session in sessions: | ||
2720 | 730 | (host, port, _) = connection.split(',') | ||
2721 | 731 | disconnect = self._fmt_disconnect(session, "%s:%s" % (host, port)) | ||
2722 | 732 | calls = [ | ||
2723 | 733 | mock.call(['sync']), | ||
2724 | 734 | mock.call(disconnect, capture=True, log_captured=True), | ||
2725 | 735 | mock.call(['udevadm', 'settle']), | ||
2726 | 736 | ] | ||
2727 | 737 | expected_calls.extend(calls) | ||
2728 | 738 | |||
2729 | 739 | self.mock_subp.assert_has_calls([], any_order=True) | ||
2730 | 740 | |||
2731 | 572 | # vi: ts=4 expandtab syntax=python | 741 | # vi: ts=4 expandtab syntax=python |
2732 | 573 | 742 | ||
2733 | === modified file 'tests/unittests/test_block_lvm.py' | |||
2734 | --- tests/unittests/test_block_lvm.py 2016-10-03 18:42:29 +0000 | |||
2735 | +++ tests/unittests/test_block_lvm.py 2017-10-06 16:35:22 +0000 | |||
2736 | @@ -1,10 +1,10 @@ | |||
2737 | 1 | from curtin.block import lvm | 1 | from curtin.block import lvm |
2738 | 2 | 2 | ||
2740 | 3 | from unittest import TestCase | 3 | from .helpers import CiTestCase |
2741 | 4 | import mock | 4 | import mock |
2742 | 5 | 5 | ||
2743 | 6 | 6 | ||
2745 | 7 | class TestBlockLvm(TestCase): | 7 | class TestBlockLvm(CiTestCase): |
2746 | 8 | vg_name = 'ubuntu-volgroup' | 8 | vg_name = 'ubuntu-volgroup' |
2747 | 9 | 9 | ||
2748 | 10 | @mock.patch('curtin.block.lvm.util') | 10 | @mock.patch('curtin.block.lvm.util') |
2749 | 11 | 11 | ||
2750 | === modified file 'tests/unittests/test_block_mdadm.py' | |||
2751 | --- tests/unittests/test_block_mdadm.py 2017-06-12 20:39:06 +0000 | |||
2752 | +++ tests/unittests/test_block_mdadm.py 2017-10-06 16:35:22 +0000 | |||
2753 | @@ -1,27 +1,15 @@ | |||
2754 | 1 | from unittest import TestCase | ||
2755 | 2 | from mock import call, patch | 1 | from mock import call, patch |
2756 | 3 | from curtin.block import dev_short | 2 | from curtin.block import dev_short |
2757 | 4 | from curtin.block import mdadm | 3 | from curtin.block import mdadm |
2758 | 5 | from curtin import util | 4 | from curtin import util |
2759 | 5 | from .helpers import CiTestCase | ||
2760 | 6 | import os | 6 | import os |
2761 | 7 | import subprocess | 7 | import subprocess |
2762 | 8 | import textwrap | 8 | import textwrap |
2763 | 9 | 9 | ||
2764 | 10 | 10 | ||
2779 | 11 | class MdadmTestBase(TestCase): | 11 | class TestBlockMdadmAssemble(CiTestCase): |
2780 | 12 | def setUp(self): | 12 | |
2767 | 13 | super(MdadmTestBase, self).setUp() | ||
2768 | 14 | |||
2769 | 15 | def add_patch(self, target, attr): | ||
2770 | 16 | """Patches specified target object and sets it as attr on test | ||
2771 | 17 | instance also schedules cleanup""" | ||
2772 | 18 | m = patch(target, autospec=True) | ||
2773 | 19 | p = m.start() | ||
2774 | 20 | self.addCleanup(m.stop) | ||
2775 | 21 | setattr(self, attr, p) | ||
2776 | 22 | |||
2777 | 23 | |||
2778 | 24 | class TestBlockMdadmAssemble(MdadmTestBase): | ||
2781 | 25 | def setUp(self): | 13 | def setUp(self): |
2782 | 26 | super(TestBlockMdadmAssemble, self).setUp() | 14 | super(TestBlockMdadmAssemble, self).setUp() |
2783 | 27 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 15 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2784 | @@ -94,7 +82,7 @@ | |||
2785 | 94 | rcs=[0, 1, 2]) | 82 | rcs=[0, 1, 2]) |
2786 | 95 | 83 | ||
2787 | 96 | 84 | ||
2789 | 97 | class TestBlockMdadmCreate(MdadmTestBase): | 85 | class TestBlockMdadmCreate(CiTestCase): |
2790 | 98 | def setUp(self): | 86 | def setUp(self): |
2791 | 99 | super(TestBlockMdadmCreate, self).setUp() | 87 | super(TestBlockMdadmCreate, self).setUp() |
2792 | 100 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 88 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2793 | @@ -243,7 +231,7 @@ | |||
2794 | 243 | self.mock_util.subp.assert_has_calls(expected_calls) | 231 | self.mock_util.subp.assert_has_calls(expected_calls) |
2795 | 244 | 232 | ||
2796 | 245 | 233 | ||
2798 | 246 | class TestBlockMdadmExamine(MdadmTestBase): | 234 | class TestBlockMdadmExamine(CiTestCase): |
2799 | 247 | def setUp(self): | 235 | def setUp(self): |
2800 | 248 | super(TestBlockMdadmExamine, self).setUp() | 236 | super(TestBlockMdadmExamine, self).setUp() |
2801 | 249 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 237 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2802 | @@ -328,7 +316,7 @@ | |||
2803 | 328 | self.assertEqual(data, {}) | 316 | self.assertEqual(data, {}) |
2804 | 329 | 317 | ||
2805 | 330 | 318 | ||
2807 | 331 | class TestBlockMdadmStop(MdadmTestBase): | 319 | class TestBlockMdadmStop(CiTestCase): |
2808 | 332 | def setUp(self): | 320 | def setUp(self): |
2809 | 333 | super(TestBlockMdadmStop, self).setUp() | 321 | super(TestBlockMdadmStop, self).setUp() |
2810 | 334 | self.add_patch('curtin.block.mdadm.util.lsb_release', 'mock_util_lsb') | 322 | self.add_patch('curtin.block.mdadm.util.lsb_release', 'mock_util_lsb') |
2811 | @@ -495,7 +483,7 @@ | |||
2812 | 495 | self.mock_util_write_file.assert_has_calls(expected_writes) | 483 | self.mock_util_write_file.assert_has_calls(expected_writes) |
2813 | 496 | 484 | ||
2814 | 497 | 485 | ||
2816 | 498 | class TestBlockMdadmRemove(MdadmTestBase): | 486 | class TestBlockMdadmRemove(CiTestCase): |
2817 | 499 | def setUp(self): | 487 | def setUp(self): |
2818 | 500 | super(TestBlockMdadmRemove, self).setUp() | 488 | super(TestBlockMdadmRemove, self).setUp() |
2819 | 501 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 489 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2820 | @@ -521,7 +509,7 @@ | |||
2821 | 521 | self.mock_util.subp.assert_has_calls(expected_calls) | 509 | self.mock_util.subp.assert_has_calls(expected_calls) |
2822 | 522 | 510 | ||
2823 | 523 | 511 | ||
2825 | 524 | class TestBlockMdadmQueryDetail(MdadmTestBase): | 512 | class TestBlockMdadmQueryDetail(CiTestCase): |
2826 | 525 | def setUp(self): | 513 | def setUp(self): |
2827 | 526 | super(TestBlockMdadmQueryDetail, self).setUp() | 514 | super(TestBlockMdadmQueryDetail, self).setUp() |
2828 | 527 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 515 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2829 | @@ -599,7 +587,7 @@ | |||
2830 | 599 | '93a73e10:427f280b:b7076c02:204b8f7a') | 587 | '93a73e10:427f280b:b7076c02:204b8f7a') |
2831 | 600 | 588 | ||
2832 | 601 | 589 | ||
2834 | 602 | class TestBlockMdadmDetailScan(MdadmTestBase): | 590 | class TestBlockMdadmDetailScan(CiTestCase): |
2835 | 603 | def setUp(self): | 591 | def setUp(self): |
2836 | 604 | super(TestBlockMdadmDetailScan, self).setUp() | 592 | super(TestBlockMdadmDetailScan, self).setUp() |
2837 | 605 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 593 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2838 | @@ -634,7 +622,7 @@ | |||
2839 | 634 | self.assertEqual(None, data) | 622 | self.assertEqual(None, data) |
2840 | 635 | 623 | ||
2841 | 636 | 624 | ||
2843 | 637 | class TestBlockMdadmMdHelpers(MdadmTestBase): | 625 | class TestBlockMdadmMdHelpers(CiTestCase): |
2844 | 638 | def setUp(self): | 626 | def setUp(self): |
2845 | 639 | super(TestBlockMdadmMdHelpers, self).setUp() | 627 | super(TestBlockMdadmMdHelpers, self).setUp() |
2846 | 640 | self.add_patch('curtin.block.mdadm.util', 'mock_util') | 628 | self.add_patch('curtin.block.mdadm.util', 'mock_util') |
2847 | 641 | 629 | ||
2848 | === modified file 'tests/unittests/test_block_mkfs.py' | |||
2849 | --- tests/unittests/test_block_mkfs.py 2016-10-03 18:42:29 +0000 | |||
2850 | +++ tests/unittests/test_block_mkfs.py 2017-10-06 16:35:22 +0000 | |||
2851 | @@ -1,10 +1,10 @@ | |||
2852 | 1 | from curtin.block import mkfs | 1 | from curtin.block import mkfs |
2853 | 2 | 2 | ||
2855 | 3 | from unittest import TestCase | 3 | from .helpers import CiTestCase |
2856 | 4 | import mock | 4 | import mock |
2857 | 5 | 5 | ||
2858 | 6 | 6 | ||
2860 | 7 | class TestBlockMkfs(TestCase): | 7 | class TestBlockMkfs(CiTestCase): |
2861 | 8 | test_uuid = "fb26cc6c-ae73-11e5-9e38-2fb63f0c3155" | 8 | test_uuid = "fb26cc6c-ae73-11e5-9e38-2fb63f0c3155" |
2862 | 9 | 9 | ||
2863 | 10 | def _get_config(self, fstype): | 10 | def _get_config(self, fstype): |
2864 | 11 | 11 | ||
2865 | === modified file 'tests/unittests/test_clear_holders.py' | |||
2866 | --- tests/unittests/test_clear_holders.py 2017-06-12 20:39:06 +0000 | |||
2867 | +++ tests/unittests/test_clear_holders.py 2017-10-06 16:35:22 +0000 | |||
2868 | @@ -1,12 +1,12 @@ | |||
2869 | 1 | from unittest import TestCase | ||
2870 | 2 | import mock | 1 | import mock |
2871 | 3 | |||
2872 | 4 | from curtin.block import clear_holders | ||
2873 | 5 | import os | 2 | import os |
2874 | 6 | import textwrap | 3 | import textwrap |
2875 | 7 | 4 | ||
2878 | 8 | 5 | from curtin.block import clear_holders | |
2879 | 9 | class TestClearHolders(TestCase): | 6 | from .helpers import CiTestCase |
2880 | 7 | |||
2881 | 8 | |||
2882 | 9 | class TestClearHolders(CiTestCase): | ||
2883 | 10 | test_blockdev = '/dev/null' | 10 | test_blockdev = '/dev/null' |
2884 | 11 | test_syspath = '/sys/class/block/null' | 11 | test_syspath = '/sys/class/block/null' |
2885 | 12 | remove_retries = [0.2] * 150 # clear_holders defaults to 30 seconds | 12 | remove_retries = [0.2] * 150 # clear_holders defaults to 30 seconds |
2886 | 13 | 13 | ||
2887 | === added file 'tests/unittests/test_commands_apply_net.py' | |||
2888 | --- tests/unittests/test_commands_apply_net.py 1970-01-01 00:00:00 +0000 | |||
2889 | +++ tests/unittests/test_commands_apply_net.py 2017-10-06 16:35:22 +0000 | |||
2890 | @@ -0,0 +1,334 @@ | |||
2891 | 1 | from mock import patch, call | ||
2892 | 2 | import copy | ||
2893 | 3 | import os | ||
2894 | 4 | |||
2895 | 5 | from curtin.commands import apply_net | ||
2896 | 6 | from curtin import util | ||
2897 | 7 | from .helpers import CiTestCase | ||
2898 | 8 | |||
2899 | 9 | |||
2900 | 10 | class TestApplyNet(CiTestCase): | ||
2901 | 11 | |||
2902 | 12 | def setUp(self): | ||
2903 | 13 | super(TestApplyNet, self).setUp() | ||
2904 | 14 | |||
2905 | 15 | base = 'curtin.commands.apply_net.' | ||
2906 | 16 | patches = [ | ||
2907 | 17 | (base + '_maybe_remove_legacy_eth0', 'm_legacy'), | ||
2908 | 18 | (base + '_disable_ipv6_privacy_extensions', 'm_ipv6_priv'), | ||
2909 | 19 | (base + '_patch_ifupdown_ipv6_mtu_hook', 'm_ipv6_mtu'), | ||
2910 | 20 | ('curtin.net.netconfig_passthrough_available', 'm_netpass_avail'), | ||
2911 | 21 | ('curtin.net.render_netconfig_passthrough', 'm_netpass_render'), | ||
2912 | 22 | ('curtin.net.parse_net_config_data', 'm_net_parsedata'), | ||
2913 | 23 | ('curtin.net.render_network_state', 'm_net_renderstate'), | ||
2914 | 24 | ('curtin.net.network_state.from_state_file', 'm_ns_from_file'), | ||
2915 | 25 | ('curtin.config.load_config', 'm_load_config'), | ||
2916 | 26 | ] | ||
2917 | 27 | for (tgt, attr) in patches: | ||
2918 | 28 | self.add_patch(tgt, attr) | ||
2919 | 29 | |||
2920 | 30 | self.target = "my_target" | ||
2921 | 31 | self.network_config = { | ||
2922 | 32 | 'network': { | ||
2923 | 33 | 'version': 1, | ||
2924 | 34 | 'config': {}, | ||
2925 | 35 | } | ||
2926 | 36 | } | ||
2927 | 37 | self.ns = { | ||
2928 | 38 | 'interfaces': {}, | ||
2929 | 39 | 'routes': [], | ||
2930 | 40 | 'dns': { | ||
2931 | 41 | 'nameservers': [], | ||
2932 | 42 | 'search': [], | ||
2933 | 43 | } | ||
2934 | 44 | } | ||
2935 | 45 | |||
2936 | 46 | def test_apply_net_notarget(self): | ||
2937 | 47 | self.assertRaises(Exception, | ||
2938 | 48 | apply_net.apply_net, None, "", "") | ||
2939 | 49 | |||
2940 | 50 | def test_apply_net_nostate_or_config(self): | ||
2941 | 51 | self.assertRaises(Exception, | ||
2942 | 52 | apply_net.apply_net, "") | ||
2943 | 53 | |||
2944 | 54 | def test_apply_net_target_and_state(self): | ||
2945 | 55 | self.m_ns_from_file.return_value = self.ns | ||
2946 | 56 | |||
2947 | 57 | self.assertRaises(ValueError, | ||
2948 | 58 | apply_net.apply_net, self.target, | ||
2949 | 59 | network_state=self.ns, network_config=None) | ||
2950 | 60 | |||
2951 | 61 | def test_apply_net_target_and_config(self): | ||
2952 | 62 | self.m_load_config.return_value = self.network_config | ||
2953 | 63 | self.m_netpass_avail.return_value = False | ||
2954 | 64 | self.m_net_parsedata.return_value = self.ns | ||
2955 | 65 | |||
2956 | 66 | apply_net.apply_net(self.target, network_state=None, | ||
2957 | 67 | network_config=self.network_config) | ||
2958 | 68 | |||
2959 | 69 | self.m_netpass_avail.assert_called_with(self.target) | ||
2960 | 70 | |||
2961 | 71 | self.m_net_renderstate.assert_called_with(target=self.target, | ||
2962 | 72 | network_state=self.ns) | ||
2963 | 73 | self.m_legacy.assert_called_with(self.target) | ||
2964 | 74 | self.m_ipv6_priv.assert_called_with(self.target) | ||
2965 | 75 | self.m_ipv6_mtu.assert_called_with(self.target) | ||
2966 | 76 | |||
2967 | 77 | def test_apply_net_target_and_config_passthrough(self): | ||
2968 | 78 | self.m_load_config.return_value = self.network_config | ||
2969 | 79 | self.m_netpass_avail.return_value = True | ||
2970 | 80 | |||
2971 | 81 | netcfg = "network_config.yaml" | ||
2972 | 82 | apply_net.apply_net(self.target, network_state=None, | ||
2973 | 83 | network_config=netcfg) | ||
2974 | 84 | |||
2975 | 85 | self.assertFalse(self.m_ns_from_file.called) | ||
2976 | 86 | self.m_load_config.assert_called_with(netcfg) | ||
2977 | 87 | self.m_netpass_avail.assert_called_with(self.target) | ||
2978 | 88 | nc = self.network_config | ||
2979 | 89 | self.m_netpass_render.assert_called_with(self.target, netconfig=nc) | ||
2980 | 90 | |||
2981 | 91 | self.assertFalse(self.m_net_renderstate.called) | ||
2982 | 92 | self.m_legacy.assert_called_with(self.target) | ||
2983 | 93 | self.m_ipv6_priv.assert_called_with(self.target) | ||
2984 | 94 | self.m_ipv6_mtu.assert_called_with(self.target) | ||
2985 | 95 | |||
2986 | 96 | def test_apply_net_target_and_config_passthrough_nonet(self): | ||
2987 | 97 | nc = {'storage': {}} | ||
2988 | 98 | self.m_load_config.return_value = nc | ||
2989 | 99 | self.m_netpass_avail.return_value = True | ||
2990 | 100 | |||
2991 | 101 | netcfg = "network_config.yaml" | ||
2992 | 102 | |||
2993 | 103 | apply_net.apply_net(self.target, network_state=None, | ||
2994 | 104 | network_config=netcfg) | ||
2995 | 105 | |||
2996 | 106 | self.assertFalse(self.m_ns_from_file.called) | ||
2997 | 107 | self.m_load_config.assert_called_with(netcfg) | ||
2998 | 108 | self.m_netpass_avail.assert_called_with(self.target) | ||
2999 | 109 | self.m_netpass_render.assert_called_with(self.target, netconfig=nc) | ||
3000 | 110 | |||
3001 | 111 | self.assertFalse(self.m_net_renderstate.called) | ||
3002 | 112 | self.m_legacy.assert_called_with(self.target) | ||
3003 | 113 | self.m_ipv6_priv.assert_called_with(self.target) | ||
3004 | 114 | self.m_ipv6_mtu.assert_called_with(self.target) | ||
3005 | 115 | |||
3006 | 116 | def test_apply_net_target_and_config_passthrough_v2_not_available(self): | ||
3007 | 117 | nc = copy.deepcopy(self.network_config) | ||
3008 | 118 | nc['network']['version'] = 2 | ||
3009 | 119 | self.m_load_config.return_value = nc | ||
3010 | 120 | self.m_netpass_avail.return_value = False | ||
3011 | 121 | self.m_net_parsedata.return_value = self.ns | ||
3012 | 122 | |||
3013 | 123 | netcfg = "network_config.yaml" | ||
3014 | 124 | |||
3015 | 125 | apply_net.apply_net(self.target, network_state=None, | ||
3016 | 126 | network_config=netcfg) | ||
3017 | 127 | |||
3018 | 128 | self.assertFalse(self.m_ns_from_file.called) | ||
3019 | 129 | self.m_load_config.assert_called_with(netcfg) | ||
3020 | 130 | self.m_netpass_avail.assert_called_with(self.target) | ||
3021 | 131 | self.assertFalse(self.m_netpass_render.called) | ||
3022 | 132 | self.m_net_parsedata.assert_called_with(nc['network']) | ||
3023 | 133 | |||
3024 | 134 | self.m_net_renderstate.assert_called_with( | ||
3025 | 135 | target=self.target, network_state=self.ns) | ||
3026 | 136 | self.m_legacy.assert_called_with(self.target) | ||
3027 | 137 | self.m_ipv6_priv.assert_called_with(self.target) | ||
3028 | 138 | self.m_ipv6_mtu.assert_called_with(self.target) | ||
3029 | 139 | |||
3030 | 140 | |||
3031 | 141 | class TestApplyNetPatchIfupdown(CiTestCase): | ||
3032 | 142 | |||
3033 | 143 | @patch('curtin.util.write_file') | ||
3034 | 144 | def test_apply_ipv6_mtu_hook(self, mock_write): | ||
3035 | 145 | target = 'mytarget' | ||
3036 | 146 | prehookfn = 'if-pre-up.d/mtuipv6' | ||
3037 | 147 | posthookfn = 'if-up.d/mtuipv6' | ||
3038 | 148 | mode = 0o755 | ||
3039 | 149 | |||
3040 | 150 | apply_net._patch_ifupdown_ipv6_mtu_hook(target, | ||
3041 | 151 | prehookfn=prehookfn, | ||
3042 | 152 | posthookfn=posthookfn) | ||
3043 | 153 | |||
3044 | 154 | precfg = util.target_path(target, path=prehookfn) | ||
3045 | 155 | postcfg = util.target_path(target, path=posthookfn) | ||
3046 | 156 | precontents = apply_net.IFUPDOWN_IPV6_MTU_PRE_HOOK | ||
3047 | 157 | postcontents = apply_net.IFUPDOWN_IPV6_MTU_POST_HOOK | ||
3048 | 158 | |||
3049 | 159 | hook_calls = [ | ||
3050 | 160 | call(precfg, precontents, mode=mode), | ||
3051 | 161 | call(postcfg, postcontents, mode=mode), | ||
3052 | 162 | ] | ||
3053 | 163 | mock_write.assert_has_calls(hook_calls) | ||
3054 | 164 | |||
3055 | 165 | @patch('curtin.util.write_file') | ||
3056 | 166 | def test_apply_ipv6_mtu_hook_write_fail(self, mock_write): | ||
3057 | 167 | """Write failure raises IOError""" | ||
3058 | 168 | target = 'mytarget' | ||
3059 | 169 | prehookfn = 'if-pre-up.d/mtuipv6' | ||
3060 | 170 | posthookfn = 'if-up.d/mtuipv6' | ||
3061 | 171 | mock_write.side_effect = (IOError) | ||
3062 | 172 | |||
3063 | 173 | self.assertRaises(IOError, | ||
3064 | 174 | apply_net._patch_ifupdown_ipv6_mtu_hook, | ||
3065 | 175 | target, | ||
3066 | 176 | prehookfn=prehookfn, | ||
3067 | 177 | posthookfn=posthookfn) | ||
3068 | 178 | self.assertEqual(1, mock_write.call_count) | ||
3069 | 179 | |||
3070 | 180 | @patch('curtin.util.write_file') | ||
3071 | 181 | def test_apply_ipv6_mtu_hook_invalid_target(self, mock_write): | ||
3072 | 182 | """Invalid target path fail before calling util.write_file""" | ||
3073 | 183 | invalid_target = {} | ||
3074 | 184 | prehookfn = 'if-pre-up.d/mtuipv6' | ||
3075 | 185 | posthookfn = 'if-up.d/mtuipv6' | ||
3076 | 186 | |||
3077 | 187 | self.assertRaises(ValueError, | ||
3078 | 188 | apply_net._patch_ifupdown_ipv6_mtu_hook, | ||
3079 | 189 | invalid_target, | ||
3080 | 190 | prehookfn=prehookfn, | ||
3081 | 191 | posthookfn=posthookfn) | ||
3082 | 192 | self.assertEqual(0, mock_write.call_count) | ||
3083 | 193 | |||
3084 | 194 | @patch('curtin.util.write_file') | ||
3085 | 195 | def test_apply_ipv6_mtu_hook_invalid_prepost_fn(self, mock_write): | ||
3086 | 196 | """Invalid prepost filenames fail before calling util.write_file""" | ||
3087 | 197 | target = "mytarget" | ||
3088 | 198 | invalid_prehookfn = {'a': 1} | ||
3089 | 199 | invalid_posthookfn = {'b': 2} | ||
3090 | 200 | |||
3091 | 201 | self.assertRaises(ValueError, | ||
3092 | 202 | apply_net._patch_ifupdown_ipv6_mtu_hook, | ||
3093 | 203 | target, | ||
3094 | 204 | prehookfn=invalid_prehookfn, | ||
3095 | 205 | posthookfn=invalid_posthookfn) | ||
3096 | 206 | self.assertEqual(0, mock_write.call_count) | ||
3097 | 207 | |||
3098 | 208 | |||
3099 | 209 | class TestApplyNetPatchIpv6Priv(CiTestCase): | ||
3100 | 210 | |||
3101 | 211 | @patch('curtin.util.del_file') | ||
3102 | 212 | @patch('curtin.util.load_file') | ||
3103 | 213 | @patch('os.path') | ||
3104 | 214 | @patch('curtin.util.write_file') | ||
3105 | 215 | def test_disable_ipv6_priv_extentions(self, mock_write, mock_ospath, | ||
3106 | 216 | mock_load, mock_del): | ||
3107 | 217 | target = 'mytarget' | ||
3108 | 218 | path = 'etc/sysctl.d/10-ipv6-privacy.conf' | ||
3109 | 219 | ipv6_priv_contents = ( | ||
3110 | 220 | 'net.ipv6.conf.all.use_tempaddr = 2\n' | ||
3111 | 221 | 'net.ipv6.conf.default.use_tempaddr = 2') | ||
3112 | 222 | expected_ipv6_priv_contents = '\n'.join( | ||
3113 | 223 | ["# IPv6 Privacy Extensions (RFC 4941)", | ||
3114 | 224 | "# Disabled by curtin", | ||
3115 | 225 | "# net.ipv6.conf.all.use_tempaddr = 2", | ||
3116 | 226 | "# net.ipv6.conf.default.use_tempaddr = 2"]) | ||
3117 | 227 | mock_ospath.exists.return_value = True | ||
3118 | 228 | mock_load.side_effect = [ipv6_priv_contents] | ||
3119 | 229 | |||
3120 | 230 | apply_net._disable_ipv6_privacy_extensions(target) | ||
3121 | 231 | |||
3122 | 232 | cfg = util.target_path(target, path=path) | ||
3123 | 233 | mock_write.assert_called_with(cfg, expected_ipv6_priv_contents) | ||
3124 | 234 | |||
3125 | 235 | @patch('curtin.util.load_file') | ||
3126 | 236 | @patch('os.path') | ||
3127 | 237 | def test_disable_ipv6_priv_extentions_decoderror(self, mock_ospath, | ||
3128 | 238 | mock_load): | ||
3129 | 239 | target = 'mytarget' | ||
3130 | 240 | mock_ospath.exists.return_value = True | ||
3131 | 241 | |||
3132 | 242 | # simulate loading of binary data | ||
3133 | 243 | mock_load.side_effect = (Exception) | ||
3134 | 244 | |||
3135 | 245 | self.assertRaises(Exception, | ||
3136 | 246 | apply_net._disable_ipv6_privacy_extensions, | ||
3137 | 247 | target) | ||
3138 | 248 | |||
3139 | 249 | @patch('curtin.util.load_file') | ||
3140 | 250 | @patch('os.path') | ||
3141 | 251 | def test_disable_ipv6_priv_extentions_notfound(self, mock_ospath, | ||
3142 | 252 | mock_load): | ||
3143 | 253 | target = 'mytarget' | ||
3144 | 254 | path = 'foo.conf' | ||
3145 | 255 | mock_ospath.exists.return_value = False | ||
3146 | 256 | |||
3147 | 257 | apply_net._disable_ipv6_privacy_extensions(target, path=path) | ||
3148 | 258 | |||
3149 | 259 | # source file not found | ||
3150 | 260 | cfg = util.target_path(target, path) | ||
3151 | 261 | mock_ospath.exists.assert_called_with(cfg) | ||
3152 | 262 | self.assertEqual(0, mock_load.call_count) | ||
3153 | 263 | |||
3154 | 264 | |||
3155 | 265 | class TestApplyNetRemoveLegacyEth0(CiTestCase): | ||
3156 | 266 | |||
3157 | 267 | @patch('curtin.util.del_file') | ||
3158 | 268 | @patch('curtin.util.load_file') | ||
3159 | 269 | @patch('os.path') | ||
3160 | 270 | def test_remove_legacy_eth0(self, mock_ospath, mock_load, mock_del): | ||
3161 | 271 | target = 'mytarget' | ||
3162 | 272 | path = 'eth0.cfg' | ||
3163 | 273 | cfg = util.target_path(target, path) | ||
3164 | 274 | legacy_eth0_contents = ( | ||
3165 | 275 | 'auto eth0\n' | ||
3166 | 276 | 'iface eth0 inet dhcp') | ||
3167 | 277 | |||
3168 | 278 | mock_ospath.exists.return_value = True | ||
3169 | 279 | mock_load.side_effect = [legacy_eth0_contents] | ||
3170 | 280 | |||
3171 | 281 | apply_net._maybe_remove_legacy_eth0(target, path) | ||
3172 | 282 | |||
3173 | 283 | mock_del.assert_called_with(cfg) | ||
3174 | 284 | |||
3175 | 285 | @patch('curtin.util.del_file') | ||
3176 | 286 | @patch('curtin.util.load_file') | ||
3177 | 287 | @patch('os.path') | ||
3178 | 288 | def test_remove_legacy_eth0_nomatch(self, mock_ospath, mock_load, | ||
3179 | 289 | mock_del): | ||
3180 | 290 | target = 'mytarget' | ||
3181 | 291 | path = 'eth0.cfg' | ||
3182 | 292 | legacy_eth0_contents = "nomatch" | ||
3183 | 293 | mock_ospath.join.side_effect = os.path.join | ||
3184 | 294 | mock_ospath.exists.return_value = True | ||
3185 | 295 | mock_load.side_effect = [legacy_eth0_contents] | ||
3186 | 296 | |||
3187 | 297 | self.assertRaises(Exception, | ||
3188 | 298 | apply_net._maybe_remove_legacy_eth0, | ||
3189 | 299 | target, path) | ||
3190 | 300 | |||
3191 | 301 | self.assertEqual(0, mock_del.call_count) | ||
3192 | 302 | |||
3193 | 303 | @patch('curtin.util.del_file') | ||
3194 | 304 | @patch('curtin.util.load_file') | ||
3195 | 305 | @patch('os.path') | ||
3196 | 306 | def test_remove_legacy_eth0_badload(self, mock_ospath, mock_load, | ||
3197 | 307 | mock_del): | ||
3198 | 308 | target = 'mytarget' | ||
3199 | 309 | path = 'eth0.cfg' | ||
3200 | 310 | mock_ospath.exists.return_value = True | ||
3201 | 311 | mock_load.side_effect = (Exception) | ||
3202 | 312 | |||
3203 | 313 | self.assertRaises(Exception, | ||
3204 | 314 | apply_net._maybe_remove_legacy_eth0, | ||
3205 | 315 | target, path) | ||
3206 | 316 | |||
3207 | 317 | self.assertEqual(0, mock_del.call_count) | ||
3208 | 318 | |||
3209 | 319 | @patch('curtin.util.del_file') | ||
3210 | 320 | @patch('curtin.util.load_file') | ||
3211 | 321 | @patch('os.path') | ||
3212 | 322 | def test_remove_legacy_eth0_notfound(self, mock_ospath, mock_load, | ||
3213 | 323 | mock_del): | ||
3214 | 324 | target = 'mytarget' | ||
3215 | 325 | path = 'eth0.conf' | ||
3216 | 326 | mock_ospath.exists.return_value = False | ||
3217 | 327 | |||
3218 | 328 | apply_net._maybe_remove_legacy_eth0(target, path) | ||
3219 | 329 | |||
3220 | 330 | # source file not found | ||
3221 | 331 | cfg = util.target_path(target, path) | ||
3222 | 332 | mock_ospath.exists.assert_called_with(cfg) | ||
3223 | 333 | self.assertEqual(0, mock_load.call_count) | ||
3224 | 334 | self.assertEqual(0, mock_del.call_count) | ||
3225 | 0 | 335 | ||
3226 | === modified file 'tests/unittests/test_commands_block_meta.py' | |||
3227 | --- tests/unittests/test_commands_block_meta.py 2017-06-12 20:39:06 +0000 | |||
3228 | +++ tests/unittests/test_commands_block_meta.py 2017-10-06 16:35:22 +0000 | |||
3229 | @@ -1,24 +1,11 @@ | |||
3230 | 1 | from unittest import TestCase | ||
3231 | 2 | from mock import patch, call | 1 | from mock import patch, call |
3232 | 3 | from argparse import Namespace | 2 | from argparse import Namespace |
3233 | 4 | 3 | ||
3234 | 5 | from curtin.commands import block_meta | 4 | from curtin.commands import block_meta |
3251 | 6 | 5 | from .helpers import CiTestCase | |
3252 | 7 | 6 | ||
3253 | 8 | class BlockMetaTestBase(TestCase): | 7 | |
3254 | 9 | def setUp(self): | 8 | class TestBlockMetaSimple(CiTestCase): |
3239 | 10 | super(BlockMetaTestBase, self).setUp() | ||
3240 | 11 | |||
3241 | 12 | def add_patch(self, target, attr): | ||
3242 | 13 | """Patches specified target object and sets it as attr on test | ||
3243 | 14 | instance also schedules cleanup""" | ||
3244 | 15 | m = patch(target, autospec=True) | ||
3245 | 16 | p = m.start() | ||
3246 | 17 | self.addCleanup(m.stop) | ||
3247 | 18 | setattr(self, attr, p) | ||
3248 | 19 | |||
3249 | 20 | |||
3250 | 21 | class TestBlockMetaSimple(BlockMetaTestBase): | ||
3255 | 22 | def setUp(self): | 9 | def setUp(self): |
3256 | 23 | super(TestBlockMetaSimple, self).setUp() | 10 | super(TestBlockMetaSimple, self).setUp() |
3257 | 24 | self.target = "my_target" | 11 | self.target = "my_target" |
3258 | @@ -120,10 +107,10 @@ | |||
3259 | 120 | [call(['mount', devname, self.target])]) | 107 | [call(['mount', devname, self.target])]) |
3260 | 121 | 108 | ||
3261 | 122 | 109 | ||
3263 | 123 | class TestBlockMeta(BlockMetaTestBase): | 110 | class TestBlockMeta(CiTestCase): |
3264 | 111 | |||
3265 | 124 | def setUp(self): | 112 | def setUp(self): |
3266 | 125 | super(TestBlockMeta, self).setUp() | 113 | super(TestBlockMeta, self).setUp() |
3267 | 126 | # self.target = tempfile.mkdtemp() | ||
3268 | 127 | 114 | ||
3269 | 128 | basepath = 'curtin.commands.block_meta.' | 115 | basepath = 'curtin.commands.block_meta.' |
3270 | 129 | self.add_patch(basepath + 'get_path_to_storage_volume', 'mock_getpath') | 116 | self.add_patch(basepath + 'get_path_to_storage_volume', 'mock_getpath') |
3271 | 130 | 117 | ||
3272 | === added file 'tests/unittests/test_commands_install.py' | |||
3273 | --- tests/unittests/test_commands_install.py 1970-01-01 00:00:00 +0000 | |||
3274 | +++ tests/unittests/test_commands_install.py 2017-10-06 16:35:22 +0000 | |||
3275 | @@ -0,0 +1,22 @@ | |||
3276 | 1 | import copy | ||
3277 | 2 | |||
3278 | 3 | from curtin.commands import install | ||
3279 | 4 | from .helpers import CiTestCase | ||
3280 | 5 | |||
3281 | 6 | |||
3282 | 7 | class TestMigrateProxy(CiTestCase): | ||
3283 | 8 | def test_legacy_moved_over(self): | ||
3284 | 9 | """Legacy setting should get moved over.""" | ||
3285 | 10 | proxy = "http://my.proxy:3128" | ||
3286 | 11 | cfg = {'http_proxy': proxy} | ||
3287 | 12 | install.migrate_proxy_settings(cfg) | ||
3288 | 13 | self.assertEqual(cfg, {'proxy': {'http_proxy': proxy}}) | ||
3289 | 14 | |||
3290 | 15 | def test_no_legacy_new_only(self): | ||
3291 | 16 | """If only new 'proxy', then no change is expected.""" | ||
3292 | 17 | proxy = "http://my.proxy:3128" | ||
3293 | 18 | cfg = {'proxy': {'http_proxy': proxy, 'https_proxy': proxy, | ||
3294 | 19 | 'no_proxy': "10.2.2.2"}} | ||
3295 | 20 | expected = copy.deepcopy(cfg) | ||
3296 | 21 | install.migrate_proxy_settings(cfg) | ||
3297 | 22 | self.assertEqual(expected, cfg) | ||
3298 | 0 | 23 | ||
3299 | === modified file 'tests/unittests/test_config.py' | |||
3300 | --- tests/unittests/test_config.py 2015-10-02 16:19:07 +0000 | |||
3301 | +++ tests/unittests/test_config.py 2017-10-06 16:35:22 +0000 | |||
3302 | @@ -1,12 +1,12 @@ | |||
3303 | 1 | from unittest import TestCase | ||
3304 | 2 | import copy | 1 | import copy |
3305 | 3 | import json | 2 | import json |
3306 | 4 | import textwrap | 3 | import textwrap |
3307 | 5 | 4 | ||
3308 | 6 | from curtin import config | 5 | from curtin import config |
3312 | 7 | 6 | from .helpers import CiTestCase | |
3313 | 8 | 7 | ||
3314 | 9 | class TestMerge(TestCase): | 8 | |
3315 | 9 | class TestMerge(CiTestCase): | ||
3316 | 10 | def test_merge_cfg_string(self): | 10 | def test_merge_cfg_string(self): |
3317 | 11 | d1 = {'str1': 'str_one'} | 11 | d1 = {'str1': 'str_one'} |
3318 | 12 | d2 = {'dict1': {'d1.e1': 'd1-e1'}} | 12 | d2 = {'dict1': {'d1.e1': 'd1-e1'}} |
3319 | @@ -16,7 +16,7 @@ | |||
3320 | 16 | self.assertEqual(d1, expected) | 16 | self.assertEqual(d1, expected) |
3321 | 17 | 17 | ||
3322 | 18 | 18 | ||
3324 | 19 | class TestCmdArg2Cfg(TestCase): | 19 | class TestCmdArg2Cfg(CiTestCase): |
3325 | 20 | def test_cmdarg_flat(self): | 20 | def test_cmdarg_flat(self): |
3326 | 21 | self.assertEqual(config.cmdarg2cfg("foo=bar"), {'foo': 'bar'}) | 21 | self.assertEqual(config.cmdarg2cfg("foo=bar"), {'foo': 'bar'}) |
3327 | 22 | 22 | ||
3328 | @@ -50,7 +50,7 @@ | |||
3329 | 50 | self.assertEqual(via_merge, via_merge_cmdarg) | 50 | self.assertEqual(via_merge, via_merge_cmdarg) |
3330 | 51 | 51 | ||
3331 | 52 | 52 | ||
3333 | 53 | class TestConfigArchive(TestCase): | 53 | class TestConfigArchive(CiTestCase): |
3334 | 54 | def test_archive_dict(self): | 54 | def test_archive_dict(self): |
3335 | 55 | myarchive = _replace_consts(textwrap.dedent(""" | 55 | myarchive = _replace_consts(textwrap.dedent(""" |
3336 | 56 | _ARCH_HEAD_ | 56 | _ARCH_HEAD_ |
3337 | 57 | 57 | ||
3338 | === modified file 'tests/unittests/test_curthooks.py' | |||
3339 | --- tests/unittests/test_curthooks.py 2017-06-12 20:39:06 +0000 | |||
3340 | +++ tests/unittests/test_curthooks.py 2017-10-06 16:35:22 +0000 | |||
3341 | @@ -1,29 +1,14 @@ | |||
3342 | 1 | import os | 1 | import os |
3343 | 2 | from unittest import TestCase | ||
3344 | 3 | from mock import call, patch, MagicMock | 2 | from mock import call, patch, MagicMock |
3345 | 4 | import shutil | ||
3346 | 5 | import tempfile | ||
3347 | 6 | 3 | ||
3348 | 7 | from curtin.commands import curthooks | 4 | from curtin.commands import curthooks |
3349 | 8 | from curtin import util | 5 | from curtin import util |
3350 | 9 | from curtin import config | 6 | from curtin import config |
3351 | 10 | from curtin.reporter import events | 7 | from curtin.reporter import events |
3368 | 11 | 8 | from .helpers import CiTestCase | |
3369 | 12 | 9 | ||
3370 | 13 | class CurthooksBase(TestCase): | 10 | |
3371 | 14 | def setUp(self): | 11 | class TestGetFlashKernelPkgs(CiTestCase): |
3356 | 15 | super(CurthooksBase, self).setUp() | ||
3357 | 16 | |||
3358 | 17 | def add_patch(self, target, attr, autospec=True): | ||
3359 | 18 | """Patches specified target object and sets it as attr on test | ||
3360 | 19 | instance also schedules cleanup""" | ||
3361 | 20 | m = patch(target, autospec=autospec) | ||
3362 | 21 | p = m.start() | ||
3363 | 22 | self.addCleanup(m.stop) | ||
3364 | 23 | setattr(self, attr, p) | ||
3365 | 24 | |||
3366 | 25 | |||
3367 | 26 | class TestGetFlashKernelPkgs(CurthooksBase): | ||
3372 | 27 | def setUp(self): | 12 | def setUp(self): |
3373 | 28 | super(TestGetFlashKernelPkgs, self).setUp() | 13 | super(TestGetFlashKernelPkgs, self).setUp() |
3374 | 29 | self.add_patch('curtin.util.subp', 'mock_subp') | 14 | self.add_patch('curtin.util.subp', 'mock_subp') |
3375 | @@ -57,7 +42,7 @@ | |||
3376 | 57 | self.mock_is_uefi_bootable.assert_called_once_with() | 42 | self.mock_is_uefi_bootable.assert_called_once_with() |
3377 | 58 | 43 | ||
3378 | 59 | 44 | ||
3380 | 60 | class TestCurthooksInstallKernel(CurthooksBase): | 45 | class TestCurthooksInstallKernel(CiTestCase): |
3381 | 61 | def setUp(self): | 46 | def setUp(self): |
3382 | 62 | super(TestCurthooksInstallKernel, self).setUp() | 47 | super(TestCurthooksInstallKernel, self).setUp() |
3383 | 63 | self.add_patch('curtin.util.has_pkg_available', 'mock_haspkg') | 48 | self.add_patch('curtin.util.has_pkg_available', 'mock_haspkg') |
3384 | @@ -70,7 +55,7 @@ | |||
3385 | 70 | 'fallback-package': 'mock-fallback', | 55 | 'fallback-package': 'mock-fallback', |
3386 | 71 | 'mapping': {}}} | 56 | 'mapping': {}}} |
3387 | 72 | # Tests don't actually install anything so we just need a name | 57 | # Tests don't actually install anything so we just need a name |
3389 | 73 | self.target = tempfile.mktemp() | 58 | self.target = self.tmp_dir() |
3390 | 74 | 59 | ||
3391 | 75 | def test__installs_flash_kernel_packages_when_needed(self): | 60 | def test__installs_flash_kernel_packages_when_needed(self): |
3392 | 76 | kernel_package = self.kernel_cfg.get('kernel', {}).get('package', {}) | 61 | kernel_package = self.kernel_cfg.get('kernel', {}).get('package', {}) |
3393 | @@ -94,14 +79,11 @@ | |||
3394 | 94 | [kernel_package], target=self.target) | 79 | [kernel_package], target=self.target) |
3395 | 95 | 80 | ||
3396 | 96 | 81 | ||
3398 | 97 | class TestUpdateInitramfs(CurthooksBase): | 82 | class TestUpdateInitramfs(CiTestCase): |
3399 | 98 | def setUp(self): | 83 | def setUp(self): |
3400 | 99 | super(TestUpdateInitramfs, self).setUp() | 84 | super(TestUpdateInitramfs, self).setUp() |
3401 | 100 | self.add_patch('curtin.util.subp', 'mock_subp') | 85 | self.add_patch('curtin.util.subp', 'mock_subp') |
3406 | 101 | self.target = tempfile.mkdtemp() | 86 | self.target = self.tmp_dir() |
3403 | 102 | |||
3404 | 103 | def tearDown(self): | ||
3405 | 104 | shutil.rmtree(self.target) | ||
3407 | 105 | 87 | ||
3408 | 106 | def _mnt_call(self, point): | 88 | def _mnt_call(self, point): |
3409 | 107 | target = os.path.join(self.target, point) | 89 | target = os.path.join(self.target, point) |
3410 | @@ -134,7 +116,7 @@ | |||
3411 | 134 | self.mock_subp.assert_has_calls(subp_calls) | 116 | self.mock_subp.assert_has_calls(subp_calls) |
3412 | 135 | 117 | ||
3413 | 136 | 118 | ||
3415 | 137 | class TestInstallMissingPkgs(CurthooksBase): | 119 | class TestInstallMissingPkgs(CiTestCase): |
3416 | 138 | def setUp(self): | 120 | def setUp(self): |
3417 | 139 | super(TestInstallMissingPkgs, self).setUp() | 121 | super(TestInstallMissingPkgs, self).setUp() |
3418 | 140 | self.add_patch('platform.machine', 'mock_machine') | 122 | self.add_patch('platform.machine', 'mock_machine') |
3419 | @@ -176,11 +158,38 @@ | |||
3420 | 176 | self.assertEqual([], self.mock_install_packages.call_args_list) | 158 | self.assertEqual([], self.mock_install_packages.call_args_list) |
3421 | 177 | 159 | ||
3422 | 178 | 160 | ||
3424 | 179 | class TestSetupGrub(CurthooksBase): | 161 | class TestSetupZipl(CiTestCase): |
3425 | 162 | |||
3426 | 163 | def setUp(self): | ||
3427 | 164 | super(TestSetupZipl, self).setUp() | ||
3428 | 165 | self.target = self.tmp_dir() | ||
3429 | 166 | |||
3430 | 167 | @patch('curtin.block.get_devices_for_mp') | ||
3431 | 168 | @patch('platform.machine') | ||
3432 | 169 | def test_noop_non_s390x(self, m_machine, m_get_devices): | ||
3433 | 170 | m_machine.return_value = 'non-s390x' | ||
3434 | 171 | curthooks.setup_zipl(None, self.target) | ||
3435 | 172 | self.assertEqual(0, m_get_devices.call_count) | ||
3436 | 173 | |||
3437 | 174 | @patch('curtin.block.get_devices_for_mp') | ||
3438 | 175 | @patch('platform.machine') | ||
3439 | 176 | def test_setup_zipl_writes_etc_zipl_conf(self, m_machine, m_get_devices): | ||
3440 | 177 | m_machine.return_value = 's390x' | ||
3441 | 178 | m_get_devices.return_value = ['/dev/mapper/ubuntu--vg-root'] | ||
3442 | 179 | curthooks.setup_zipl(None, self.target) | ||
3443 | 180 | m_get_devices.assert_called_with(self.target) | ||
3444 | 181 | with open(os.path.join(self.target, 'etc', 'zipl.conf')) as stream: | ||
3445 | 182 | content = stream.read() | ||
3446 | 183 | self.assertIn( | ||
3447 | 184 | '# This has been modified by the MAAS curtin installer', | ||
3448 | 185 | content) | ||
3449 | 186 | |||
3450 | 187 | |||
3451 | 188 | class TestSetupGrub(CiTestCase): | ||
3452 | 180 | 189 | ||
3453 | 181 | def setUp(self): | 190 | def setUp(self): |
3454 | 182 | super(TestSetupGrub, self).setUp() | 191 | super(TestSetupGrub, self).setUp() |
3456 | 183 | self.target = tempfile.mkdtemp() | 192 | self.target = self.tmp_dir() |
3457 | 184 | self.add_patch('curtin.util.lsb_release', 'mock_lsb_release') | 193 | self.add_patch('curtin.util.lsb_release', 'mock_lsb_release') |
3458 | 185 | self.mock_lsb_release.return_value = { | 194 | self.mock_lsb_release.return_value = { |
3459 | 186 | 'codename': 'xenial', | 195 | 'codename': 'xenial', |
3460 | @@ -203,9 +212,6 @@ | |||
3461 | 203 | self.mock_in_chroot_subp.side_effect = iter(self.in_chroot_subp_output) | 212 | self.mock_in_chroot_subp.side_effect = iter(self.in_chroot_subp_output) |
3462 | 204 | self.mock_chroot.return_value = self.mock_in_chroot | 213 | self.mock_chroot.return_value = self.mock_in_chroot |
3463 | 205 | 214 | ||
3464 | 206 | def tearDown(self): | ||
3465 | 207 | shutil.rmtree(self.target) | ||
3466 | 208 | |||
3467 | 209 | def test_uses_old_grub_install_devices_in_cfg(self): | 215 | def test_uses_old_grub_install_devices_in_cfg(self): |
3468 | 210 | cfg = { | 216 | cfg = { |
3469 | 211 | 'grub_install_devices': ['/dev/vdb'] | 217 | 'grub_install_devices': ['/dev/vdb'] |
3470 | @@ -434,17 +440,13 @@ | |||
3471 | 434 | self.mock_in_chroot_subp.call_args_list[0][0]) | 440 | self.mock_in_chroot_subp.call_args_list[0][0]) |
3472 | 435 | 441 | ||
3473 | 436 | 442 | ||
3475 | 437 | class TestUbuntuCoreHooks(CurthooksBase): | 443 | class TestUbuntuCoreHooks(CiTestCase): |
3476 | 438 | def setUp(self): | 444 | def setUp(self): |
3477 | 439 | super(TestUbuntuCoreHooks, self).setUp() | 445 | super(TestUbuntuCoreHooks, self).setUp() |
3478 | 440 | self.target = None | 446 | self.target = None |
3479 | 441 | 447 | ||
3480 | 442 | def tearDown(self): | ||
3481 | 443 | if self.target: | ||
3482 | 444 | shutil.rmtree(self.target) | ||
3483 | 445 | |||
3484 | 446 | def test_target_is_ubuntu_core(self): | 448 | def test_target_is_ubuntu_core(self): |
3486 | 447 | self.target = tempfile.mkdtemp() | 449 | self.target = self.tmp_dir() |
3487 | 448 | ubuntu_core_path = os.path.join(self.target, 'system-data', | 450 | ubuntu_core_path = os.path.join(self.target, 'system-data', |
3488 | 449 | 'var/lib/snapd') | 451 | 'var/lib/snapd') |
3489 | 450 | util.ensure_dir(ubuntu_core_path) | 452 | util.ensure_dir(ubuntu_core_path) |
3490 | @@ -457,7 +459,7 @@ | |||
3491 | 457 | self.assertFalse(is_core) | 459 | self.assertFalse(is_core) |
3492 | 458 | 460 | ||
3493 | 459 | def test_target_is_ubuntu_core_noncore_target(self): | 461 | def test_target_is_ubuntu_core_noncore_target(self): |
3495 | 460 | self.target = tempfile.mkdtemp() | 462 | self.target = self.tmp_dir() |
3496 | 461 | non_core_path = os.path.join(self.target, 'curtin') | 463 | non_core_path = os.path.join(self.target, 'curtin') |
3497 | 462 | util.ensure_dir(non_core_path) | 464 | util.ensure_dir(non_core_path) |
3498 | 463 | self.assertTrue(os.path.isdir(non_core_path)) | 465 | self.assertTrue(os.path.isdir(non_core_path)) |
3499 | @@ -469,7 +471,7 @@ | |||
3500 | 469 | @patch('curtin.commands.curthooks.handle_cloudconfig') | 471 | @patch('curtin.commands.curthooks.handle_cloudconfig') |
3501 | 470 | def test_curthooks_no_config(self, mock_handle_cc, mock_del_file, | 472 | def test_curthooks_no_config(self, mock_handle_cc, mock_del_file, |
3502 | 471 | mock_write_file): | 473 | mock_write_file): |
3504 | 472 | self.target = tempfile.mkdtemp() | 474 | self.target = self.tmp_dir() |
3505 | 473 | cfg = {} | 475 | cfg = {} |
3506 | 474 | curthooks.ubuntu_core_curthooks(cfg, target=self.target) | 476 | curthooks.ubuntu_core_curthooks(cfg, target=self.target) |
3507 | 475 | self.assertEqual(len(mock_handle_cc.call_args_list), 0) | 477 | self.assertEqual(len(mock_handle_cc.call_args_list), 0) |
3508 | @@ -478,7 +480,7 @@ | |||
3509 | 478 | 480 | ||
3510 | 479 | @patch('curtin.commands.curthooks.handle_cloudconfig') | 481 | @patch('curtin.commands.curthooks.handle_cloudconfig') |
3511 | 480 | def test_curthooks_cloud_config_remove_disabled(self, mock_handle_cc): | 482 | def test_curthooks_cloud_config_remove_disabled(self, mock_handle_cc): |
3513 | 481 | self.target = tempfile.mkdtemp() | 483 | self.target = self.tmp_dir() |
3514 | 482 | uc_cloud = os.path.join(self.target, 'system-data', 'etc/cloud') | 484 | uc_cloud = os.path.join(self.target, 'system-data', 'etc/cloud') |
3515 | 483 | cc_disabled = os.path.join(uc_cloud, 'cloud-init.disabled') | 485 | cc_disabled = os.path.join(uc_cloud, 'cloud-init.disabled') |
3516 | 484 | cc_path = os.path.join(uc_cloud, 'cloud.cfg.d') | 486 | cc_path = os.path.join(uc_cloud, 'cloud.cfg.d') |
3517 | @@ -496,7 +498,7 @@ | |||
3518 | 496 | curthooks.ubuntu_core_curthooks(cfg, target=self.target) | 498 | curthooks.ubuntu_core_curthooks(cfg, target=self.target) |
3519 | 497 | 499 | ||
3520 | 498 | mock_handle_cc.assert_called_with(cfg.get('cloudconfig'), | 500 | mock_handle_cc.assert_called_with(cfg.get('cloudconfig'), |
3522 | 499 | target=cc_path) | 501 | base_dir=cc_path) |
3523 | 500 | self.assertFalse(os.path.exists(cc_disabled)) | 502 | self.assertFalse(os.path.exists(cc_disabled)) |
3524 | 501 | 503 | ||
3525 | 502 | @patch('curtin.util.write_file') | 504 | @patch('curtin.util.write_file') |
3526 | @@ -504,7 +506,7 @@ | |||
3527 | 504 | @patch('curtin.commands.curthooks.handle_cloudconfig') | 506 | @patch('curtin.commands.curthooks.handle_cloudconfig') |
3528 | 505 | def test_curthooks_cloud_config(self, mock_handle_cc, mock_del_file, | 507 | def test_curthooks_cloud_config(self, mock_handle_cc, mock_del_file, |
3529 | 506 | mock_write_file): | 508 | mock_write_file): |
3531 | 507 | self.target = tempfile.mkdtemp() | 509 | self.target = self.tmp_dir() |
3532 | 508 | cfg = { | 510 | cfg = { |
3533 | 509 | 'cloudconfig': { | 511 | 'cloudconfig': { |
3534 | 510 | 'file1': { | 512 | 'file1': { |
3535 | @@ -518,7 +520,7 @@ | |||
3536 | 518 | cc_path = os.path.join(self.target, | 520 | cc_path = os.path.join(self.target, |
3537 | 519 | 'system-data/etc/cloud/cloud.cfg.d') | 521 | 'system-data/etc/cloud/cloud.cfg.d') |
3538 | 520 | mock_handle_cc.assert_called_with(cfg.get('cloudconfig'), | 522 | mock_handle_cc.assert_called_with(cfg.get('cloudconfig'), |
3540 | 521 | target=cc_path) | 523 | base_dir=cc_path) |
3541 | 522 | self.assertEqual(len(mock_write_file.call_args_list), 0) | 524 | self.assertEqual(len(mock_write_file.call_args_list), 0) |
3542 | 523 | 525 | ||
3543 | 524 | @patch('curtin.util.write_file') | 526 | @patch('curtin.util.write_file') |
3544 | @@ -526,7 +528,7 @@ | |||
3545 | 526 | @patch('curtin.commands.curthooks.handle_cloudconfig') | 528 | @patch('curtin.commands.curthooks.handle_cloudconfig') |
3546 | 527 | def test_curthooks_net_config(self, mock_handle_cc, mock_del_file, | 529 | def test_curthooks_net_config(self, mock_handle_cc, mock_del_file, |
3547 | 528 | mock_write_file): | 530 | mock_write_file): |
3549 | 529 | self.target = tempfile.mkdtemp() | 531 | self.target = self.tmp_dir() |
3550 | 530 | cfg = { | 532 | cfg = { |
3551 | 531 | 'network': { | 533 | 'network': { |
3552 | 532 | 'version': '1', | 534 | 'version': '1', |
3553 | @@ -541,13 +543,13 @@ | |||
3554 | 541 | netcfg_path = os.path.join(self.target, | 543 | netcfg_path = os.path.join(self.target, |
3555 | 542 | 'system-data', | 544 | 'system-data', |
3556 | 543 | 'etc/cloud/cloud.cfg.d', | 545 | 'etc/cloud/cloud.cfg.d', |
3558 | 544 | '50-network-config.cfg') | 546 | '50-curtin-networking.cfg') |
3559 | 545 | netcfg = config.dump_config({'network': cfg.get('network')}) | 547 | netcfg = config.dump_config({'network': cfg.get('network')}) |
3560 | 546 | mock_write_file.assert_called_with(netcfg_path, | 548 | mock_write_file.assert_called_with(netcfg_path, |
3561 | 547 | content=netcfg) | 549 | content=netcfg) |
3562 | 548 | self.assertEqual(len(mock_del_file.call_args_list), 0) | 550 | self.assertEqual(len(mock_del_file.call_args_list), 0) |
3563 | 549 | 551 | ||
3565 | 550 | @patch('curtin.commands.curthooks.write_files') | 552 | @patch('curtin.commands.curthooks.futil.write_files') |
3566 | 551 | def test_handle_cloudconfig(self, mock_write_files): | 553 | def test_handle_cloudconfig(self, mock_write_files): |
3567 | 552 | cc_target = "tmpXXXX/systemd-data/etc/cloud/cloud.cfg.d" | 554 | cc_target = "tmpXXXX/systemd-data/etc/cloud/cloud.cfg.d" |
3568 | 553 | cloudconfig = { | 555 | cloudconfig = { |
3569 | @@ -561,20 +563,202 @@ | |||
3570 | 561 | } | 563 | } |
3571 | 562 | 564 | ||
3572 | 563 | expected_cfg = { | 565 | expected_cfg = { |
3581 | 564 | 'write_files': { | 566 | 'file1': { |
3582 | 565 | 'file1': { | 567 | 'path': '50-cloudconfig-file1.cfg', |
3583 | 566 | 'path': '50-cloudconfig-file1.cfg', | 568 | 'content': cloudconfig['file1']['content']}, |
3584 | 567 | 'content': cloudconfig['file1']['content']}, | 569 | 'foobar': { |
3585 | 568 | 'foobar': { | 570 | 'path': '50-cloudconfig-foobar.cfg', |
3586 | 569 | 'path': '50-cloudconfig-foobar.cfg', | 571 | 'content': cloudconfig['foobar']['content']} |
3579 | 570 | 'content': cloudconfig['foobar']['content']} | ||
3580 | 571 | } | ||
3587 | 572 | } | 572 | } |
3589 | 573 | curthooks.handle_cloudconfig(cloudconfig, target=cc_target) | 573 | curthooks.handle_cloudconfig(cloudconfig, base_dir=cc_target) |
3590 | 574 | mock_write_files.assert_called_with(expected_cfg, cc_target) | 574 | mock_write_files.assert_called_with(expected_cfg, cc_target) |
3591 | 575 | 575 | ||
3592 | 576 | def test_handle_cloudconfig_bad_config(self): | 576 | def test_handle_cloudconfig_bad_config(self): |
3593 | 577 | with self.assertRaises(ValueError): | 577 | with self.assertRaises(ValueError): |
3595 | 578 | curthooks.handle_cloudconfig([], target="foobar") | 578 | curthooks.handle_cloudconfig([], base_dir="foobar") |
3596 | 579 | |||
3597 | 580 | |||
3598 | 581 | class TestDetectRequiredPackages(CiTestCase): | ||
3599 | 582 | test_config = { | ||
3600 | 583 | 'storage': { | ||
3601 | 584 | 1: { | ||
3602 | 585 | 'bcache': { | ||
3603 | 586 | 'type': 'bcache', 'name': 'bcache0', 'id': 'cache0', | ||
3604 | 587 | 'backing_device': 'sda3', 'cache_device': 'sdb'}, | ||
3605 | 588 | 'lvm_partition': { | ||
3606 | 589 | 'id': 'lvol1', 'name': 'lv1', 'volgroup': 'vg1', | ||
3607 | 590 | 'type': 'lvm_partition'}, | ||
3608 | 591 | 'lvm_volgroup': { | ||
3609 | 592 | 'id': 'vol1', 'name': 'vg1', 'devices': ['sda', 'sdb'], | ||
3610 | 593 | 'type': 'lvm_volgroup'}, | ||
3611 | 594 | 'raid': { | ||
3612 | 595 | 'id': 'mddevice', 'name': 'md0', 'type': 'raid', | ||
3613 | 596 | 'raidlevel': 5, 'devices': ['sda1', 'sdb1', 'sdc1']}, | ||
3614 | 597 | 'ext2': { | ||
3615 | 598 | 'id': 'format0', 'fstype': 'ext2', 'type': 'format'}, | ||
3616 | 599 | 'ext3': { | ||
3617 | 600 | 'id': 'format1', 'fstype': 'ext3', 'type': 'format'}, | ||
3618 | 601 | 'ext4': { | ||
3619 | 602 | 'id': 'format2', 'fstype': 'ext4', 'type': 'format'}, | ||
3620 | 603 | 'btrfs': { | ||
3621 | 604 | 'id': 'format3', 'fstype': 'btrfs', 'type': 'format'}, | ||
3622 | 605 | 'xfs': { | ||
3623 | 606 | 'id': 'format4', 'fstype': 'xfs', 'type': 'format'}} | ||
3624 | 607 | }, | ||
3625 | 608 | 'network': { | ||
3626 | 609 | 1: { | ||
3627 | 610 | 'bond': { | ||
3628 | 611 | 'name': 'bond0', 'type': 'bond', | ||
3629 | 612 | 'bond_interfaces': ['interface0', 'interface1'], | ||
3630 | 613 | 'params': {'bond-mode': 'active-backup'}, | ||
3631 | 614 | 'subnets': [ | ||
3632 | 615 | {'type': 'static', 'address': '10.23.23.2/24'}, | ||
3633 | 616 | {'type': 'static', 'address': '10.23.24.2/24'}]}, | ||
3634 | 617 | 'vlan': { | ||
3635 | 618 | 'id': 'interface1.2667', 'mtu': 1500, 'name': | ||
3636 | 619 | 'interface1.2667', 'type': 'vlan', 'vlan_id': 2667, | ||
3637 | 620 | 'vlan_link': 'interface1', | ||
3638 | 621 | 'subnets': [{'address': '10.245.184.2/24', | ||
3639 | 622 | 'dns_nameservers': [], 'type': 'static'}]}, | ||
3640 | 623 | 'bridge': { | ||
3641 | 624 | 'name': 'br0', 'bridge_interfaces': ['eth0', 'eth1'], | ||
3642 | 625 | 'type': 'bridge', 'params': { | ||
3643 | 626 | 'bridge_stp': 'off', 'bridge_fd': 0, | ||
3644 | 627 | 'bridge_maxwait': 0}, | ||
3645 | 628 | 'subnets': [ | ||
3646 | 629 | {'type': 'static', 'address': '192.168.14.2/24'}, | ||
3647 | 630 | {'type': 'static', 'address': '2001:1::1/64'}]}}, | ||
3648 | 631 | 2: { | ||
3649 | 632 | 'vlan': { | ||
3650 | 633 | 'vlans': { | ||
3651 | 634 | 'en-intra': {'id': 1, 'link': 'eno1', 'dhcp4': 'yes'}, | ||
3652 | 635 | 'en-vpn': {'id': 2, 'link': 'eno1'}}}, | ||
3653 | 636 | 'bridge': { | ||
3654 | 637 | 'bridges': { | ||
3655 | 638 | 'br0': { | ||
3656 | 639 | 'interfaces': ['wlp1s0', 'switchports'], | ||
3657 | 640 | 'dhcp4': True}}}} | ||
3658 | 641 | }, | ||
3659 | 642 | } | ||
3660 | 643 | |||
3661 | 644 | def _fmt_config(self, config_items): | ||
3662 | 645 | res = {} | ||
3663 | 646 | for item, item_confs in config_items.items(): | ||
3664 | 647 | version = item_confs['version'] | ||
3665 | 648 | res[item] = {'version': version} | ||
3666 | 649 | if version == 1: | ||
3667 | 650 | res[item]['config'] = [self.test_config[item][version][i] | ||
3668 | 651 | for i in item_confs['items']] | ||
3669 | 652 | elif version == 2 and item == 'network': | ||
3670 | 653 | for cfg_item in item_confs['items']: | ||
3671 | 654 | res[item].update(self.test_config[item][version][cfg_item]) | ||
3672 | 655 | else: | ||
3673 | 656 | raise NotImplementedError | ||
3674 | 657 | return res | ||
3675 | 658 | |||
3676 | 659 | def _test_req_mappings(self, req_mappings): | ||
3677 | 660 | for (config_items, expected_reqs) in req_mappings: | ||
3678 | 661 | cfg = self._fmt_config(config_items) | ||
3679 | 662 | actual_reqs = curthooks.detect_required_packages(cfg) | ||
3680 | 663 | self.assertEqual(set(actual_reqs), set(expected_reqs), | ||
3681 | 664 | 'failed for config: {}'.format(config_items)) | ||
3682 | 665 | |||
3683 | 666 | def test_storage_v1_detect(self): | ||
3684 | 667 | self._test_req_mappings(( | ||
3685 | 668 | ({'storage': { | ||
3686 | 669 | 'version': 1, | ||
3687 | 670 | 'items': ('lvm_partition', 'lvm_volgroup', 'btrfs', 'xfs')}}, | ||
3688 | 671 | ('lvm2', 'xfsprogs', 'btrfs-tools')), | ||
3689 | 672 | ({'storage': { | ||
3690 | 673 | 'version': 1, | ||
3691 | 674 | 'items': ('raid', 'bcache', 'ext3', 'xfs')}}, | ||
3692 | 675 | ('mdadm', 'bcache-tools', 'e2fsprogs', 'xfsprogs')), | ||
3693 | 676 | ({'storage': { | ||
3694 | 677 | 'version': 1, | ||
3695 | 678 | 'items': ('raid', 'lvm_volgroup', 'lvm_partition', 'ext3', | ||
3696 | 679 | 'ext4', 'btrfs')}}, | ||
3697 | 680 | ('lvm2', 'mdadm', 'e2fsprogs', 'btrfs-tools')), | ||
3698 | 681 | ({'storage': { | ||
3699 | 682 | 'version': 1, | ||
3700 | 683 | 'items': ('bcache', 'lvm_volgroup', 'lvm_partition', 'ext2')}}, | ||
3701 | 684 | ('bcache-tools', 'lvm2', 'e2fsprogs')), | ||
3702 | 685 | )) | ||
3703 | 686 | |||
3704 | 687 | def test_network_v1_detect(self): | ||
3705 | 688 | self._test_req_mappings(( | ||
3706 | 689 | ({'network': { | ||
3707 | 690 | 'version': 1, | ||
3708 | 691 | 'items': ('bridge',)}}, | ||
3709 | 692 | ('bridge-utils',)), | ||
3710 | 693 | ({'network': { | ||
3711 | 694 | 'version': 1, | ||
3712 | 695 | 'items': ('vlan', 'bond')}}, | ||
3713 | 696 | ('vlan', 'ifenslave')), | ||
3714 | 697 | ({'network': { | ||
3715 | 698 | 'version': 1, | ||
3716 | 699 | 'items': ('bond', 'bridge')}}, | ||
3717 | 700 | ('ifenslave', 'bridge-utils')), | ||
3718 | 701 | ({'network': { | ||
3719 | 702 | 'version': 1, | ||
3720 | 703 | 'items': ('vlan', 'bridge', 'bond')}}, | ||
3721 | 704 | ('ifenslave', 'bridge-utils', 'vlan')), | ||
3722 | 705 | )) | ||
3723 | 706 | |||
3724 | 707 | def test_mixed_v1_detect(self): | ||
3725 | 708 | self._test_req_mappings(( | ||
3726 | 709 | ({'storage': { | ||
3727 | 710 | 'version': 1, | ||
3728 | 711 | 'items': ('raid', 'bcache', 'ext4')}, | ||
3729 | 712 | 'network': { | ||
3730 | 713 | 'version': 1, | ||
3731 | 714 | 'items': ('vlan',)}}, | ||
3732 | 715 | ('mdadm', 'bcache-tools', 'e2fsprogs', 'vlan')), | ||
3733 | 716 | ({'storage': { | ||
3734 | 717 | 'version': 1, | ||
3735 | 718 | 'items': ('lvm_partition', 'lvm_volgroup', 'xfs')}, | ||
3736 | 719 | 'network': { | ||
3737 | 720 | 'version': 1, | ||
3738 | 721 | 'items': ('bridge', 'bond')}}, | ||
3739 | 722 | ('lvm2', 'xfsprogs', 'bridge-utils', 'ifenslave')), | ||
3740 | 723 | ({'storage': { | ||
3741 | 724 | 'version': 1, | ||
3742 | 725 | 'items': ('ext3', 'ext4', 'btrfs')}, | ||
3743 | 726 | 'network': { | ||
3744 | 727 | 'version': 1, | ||
3745 | 728 | 'items': ('bond', 'vlan')}}, | ||
3746 | 729 | ('e2fsprogs', 'btrfs-tools', 'vlan', 'ifenslave')), | ||
3747 | 730 | )) | ||
3748 | 731 | |||
3749 | 732 | def test_network_v2_detect(self): | ||
3750 | 733 | self._test_req_mappings(( | ||
3751 | 734 | ({'network': { | ||
3752 | 735 | 'version': 2, | ||
3753 | 736 | 'items': ('bridge',)}}, | ||
3754 | 737 | ('bridge-utils',)), | ||
3755 | 738 | ({'network': { | ||
3756 | 739 | 'version': 2, | ||
3757 | 740 | 'items': ('vlan',)}}, | ||
3758 | 741 | ('vlan',)), | ||
3759 | 742 | ({'network': { | ||
3760 | 743 | 'version': 2, | ||
3761 | 744 | 'items': ('vlan', 'bridge')}}, | ||
3762 | 745 | ('vlan', 'bridge-utils')), | ||
3763 | 746 | )) | ||
3764 | 747 | |||
3765 | 748 | def test_mixed_storage_v1_network_v2_detect(self): | ||
3766 | 749 | self._test_req_mappings(( | ||
3767 | 750 | ({'network': { | ||
3768 | 751 | 'version': 2, | ||
3769 | 752 | 'items': ('bridge', 'vlan')}, | ||
3770 | 753 | 'storage': { | ||
3771 | 754 | 'version': 1, | ||
3772 | 755 | 'items': ('raid', 'bcache', 'ext4')}}, | ||
3773 | 756 | ('vlan', 'bridge-utils', 'mdadm', 'bcache-tools', 'e2fsprogs')), | ||
3774 | 757 | )) | ||
3775 | 758 | |||
3776 | 759 | def test_invalid_version_in_config(self): | ||
3777 | 760 | with self.assertRaises(ValueError): | ||
3778 | 761 | curthooks.detect_required_packages({'network': {'version': 3}}) | ||
3779 | 762 | |||
3780 | 579 | 763 | ||
3781 | 580 | # vi: ts=4 expandtab syntax=python | 764 | # vi: ts=4 expandtab syntax=python |
3782 | 581 | 765 | ||
3783 | === modified file 'tests/unittests/test_feature.py' | |||
3784 | --- tests/unittests/test_feature.py 2017-06-12 20:39:06 +0000 | |||
3785 | +++ tests/unittests/test_feature.py 2017-10-06 16:35:22 +0000 | |||
3786 | @@ -1,9 +1,9 @@ | |||
3788 | 1 | from unittest import TestCase | 1 | from .helpers import CiTestCase |
3789 | 2 | 2 | ||
3790 | 3 | import curtin | 3 | import curtin |
3791 | 4 | 4 | ||
3792 | 5 | 5 | ||
3794 | 6 | class TestExportsFeatures(TestCase): | 6 | class TestExportsFeatures(CiTestCase): |
3795 | 7 | def test_has_storage_v1(self): | 7 | def test_has_storage_v1(self): |
3796 | 8 | self.assertIn('STORAGE_CONFIG_V1', curtin.FEATURES) | 8 | self.assertIn('STORAGE_CONFIG_V1', curtin.FEATURES) |
3797 | 9 | 9 | ||
3798 | @@ -15,3 +15,6 @@ | |||
3799 | 15 | 15 | ||
3800 | 16 | def test_has_reporting_events_webhook(self): | 16 | def test_has_reporting_events_webhook(self): |
3801 | 17 | self.assertIn('REPORTING_EVENTS_WEBHOOK', curtin.FEATURES) | 17 | self.assertIn('REPORTING_EVENTS_WEBHOOK', curtin.FEATURES) |
3802 | 18 | |||
3803 | 19 | def test_has_centos_apply_network_config(self): | ||
3804 | 20 | self.assertIn('CENTOS_APPLY_NETWORK_CONFIG', curtin.FEATURES) | ||
3805 | 18 | 21 | ||
3806 | === modified file 'tests/unittests/test_gpg.py' | |||
3807 | --- tests/unittests/test_gpg.py 2017-02-08 22:22:44 +0000 | |||
3808 | +++ tests/unittests/test_gpg.py 2017-10-06 16:35:22 +0000 | |||
3809 | @@ -1,12 +1,12 @@ | |||
3810 | 1 | from unittest import TestCase | ||
3811 | 2 | from mock import call, patch | 1 | from mock import call, patch |
3812 | 3 | import textwrap | 2 | import textwrap |
3813 | 4 | 3 | ||
3814 | 5 | from curtin import gpg | 4 | from curtin import gpg |
3815 | 6 | from curtin import util | 5 | from curtin import util |
3819 | 7 | 6 | from .helpers import CiTestCase | |
3820 | 8 | 7 | ||
3821 | 9 | class TestCurtinGpg(TestCase): | 8 | |
3822 | 9 | class TestCurtinGpg(CiTestCase): | ||
3823 | 10 | 10 | ||
3824 | 11 | @patch('curtin.util.subp') | 11 | @patch('curtin.util.subp') |
3825 | 12 | def test_export_armour(self, mock_subp): | 12 | def test_export_armour(self, mock_subp): |
3826 | 13 | 13 | ||
3827 | === modified file 'tests/unittests/test_make_dname.py' | |||
3828 | --- tests/unittests/test_make_dname.py 2016-10-03 18:42:29 +0000 | |||
3829 | +++ tests/unittests/test_make_dname.py 2017-10-06 16:35:22 +0000 | |||
3830 | @@ -1,13 +1,13 @@ | |||
3831 | 1 | from unittest import TestCase | ||
3832 | 2 | import mock | 1 | import mock |
3833 | 3 | 2 | ||
3834 | 4 | import textwrap | 3 | import textwrap |
3835 | 5 | import uuid | 4 | import uuid |
3836 | 6 | 5 | ||
3837 | 7 | from curtin.commands import block_meta | 6 | from curtin.commands import block_meta |
3841 | 8 | 7 | from .helpers import CiTestCase | |
3842 | 9 | 8 | ||
3843 | 10 | class TestMakeDname(TestCase): | 9 | |
3844 | 10 | class TestMakeDname(CiTestCase): | ||
3845 | 11 | state = {'scratch': '/tmp/null'} | 11 | state = {'scratch': '/tmp/null'} |
3846 | 12 | rules_d = '/tmp/null/rules.d' | 12 | rules_d = '/tmp/null/rules.d' |
3847 | 13 | rule_file = '/tmp/null/rules.d/{}.rules' | 13 | rule_file = '/tmp/null/rules.d/{}.rules' |
3848 | 14 | 14 | ||
3849 | === modified file 'tests/unittests/test_net.py' | |||
3850 | --- tests/unittests/test_net.py 2017-03-01 16:13:56 +0000 | |||
3851 | +++ tests/unittests/test_net.py 2017-10-06 16:35:22 +0000 | |||
3852 | @@ -1,15 +1,14 @@ | |||
3854 | 1 | from unittest import TestCase | 1 | import mock |
3855 | 2 | import os | 2 | import os |
3856 | 3 | import shutil | ||
3857 | 4 | import tempfile | ||
3858 | 5 | import yaml | 3 | import yaml |
3859 | 6 | 4 | ||
3861 | 7 | from curtin import net | 5 | from curtin import config, net, util |
3862 | 8 | import curtin.net.network_state as network_state | 6 | import curtin.net.network_state as network_state |
3863 | 7 | from .helpers import CiTestCase | ||
3864 | 9 | from textwrap import dedent | 8 | from textwrap import dedent |
3865 | 10 | 9 | ||
3866 | 11 | 10 | ||
3868 | 12 | class TestNetParserData(TestCase): | 11 | class TestNetParserData(CiTestCase): |
3869 | 13 | 12 | ||
3870 | 14 | def test_parse_deb_config_data_ignores_comments(self): | 13 | def test_parse_deb_config_data_ignores_comments(self): |
3871 | 15 | contents = dedent("""\ | 14 | contents = dedent("""\ |
3872 | @@ -234,13 +233,11 @@ | |||
3873 | 234 | }, ifaces) | 233 | }, ifaces) |
3874 | 235 | 234 | ||
3875 | 236 | 235 | ||
3877 | 237 | class TestNetParser(TestCase): | 236 | class TestNetParser(CiTestCase): |
3878 | 238 | 237 | ||
3879 | 239 | def setUp(self): | 238 | def setUp(self): |
3884 | 240 | self.target = tempfile.mkdtemp() | 239 | super(TestNetParser, self).setUp() |
3885 | 241 | 240 | self.target = self.tmp_dir() | |
3882 | 242 | def tearDown(self): | ||
3883 | 243 | shutil.rmtree(self.target) | ||
3886 | 244 | 241 | ||
3887 | 245 | def make_config(self, path=None, name=None, contents=None, | 242 | def make_config(self, path=None, name=None, contents=None, |
3888 | 246 | parse=True): | 243 | parse=True): |
3889 | @@ -386,9 +383,10 @@ | |||
3890 | 386 | self.assertEqual({}, observed) | 383 | self.assertEqual({}, observed) |
3891 | 387 | 384 | ||
3892 | 388 | 385 | ||
3894 | 389 | class TestNetConfig(TestCase): | 386 | class TestNetConfig(CiTestCase): |
3895 | 390 | def setUp(self): | 387 | def setUp(self): |
3897 | 391 | self.target = tempfile.mkdtemp() | 388 | super(TestNetConfig, self).setUp() |
3898 | 389 | self.target = self.tmp_dir() | ||
3899 | 392 | self.config_f = os.path.join(self.target, 'config') | 390 | self.config_f = os.path.join(self.target, 'config') |
3900 | 393 | self.config = ''' | 391 | self.config = ''' |
3901 | 394 | # YAML example of a simple network config | 392 | # YAML example of a simple network config |
3902 | @@ -435,9 +433,6 @@ | |||
3903 | 435 | ns.parse_config() | 433 | ns.parse_config() |
3904 | 436 | return ns | 434 | return ns |
3905 | 437 | 435 | ||
3906 | 438 | def tearDown(self): | ||
3907 | 439 | shutil.rmtree(self.target) | ||
3908 | 440 | |||
3909 | 441 | def test_parse_net_config_data(self): | 436 | def test_parse_net_config_data(self): |
3910 | 442 | ns = self.get_net_state() | 437 | ns = self.get_net_state() |
3911 | 443 | net_state_from_cls = ns.network_state | 438 | net_state_from_cls = ns.network_state |
3912 | @@ -503,24 +498,19 @@ | |||
3913 | 503 | auto interface1 | 498 | auto interface1 |
3914 | 504 | iface interface1 inet manual | 499 | iface interface1 inet manual |
3915 | 505 | bond-mode active-backup | 500 | bond-mode active-backup |
3917 | 506 | bond-master bond0 | 501 | bond-master bond1 |
3918 | 507 | 502 | ||
3919 | 508 | auto interface2 | 503 | auto interface2 |
3920 | 509 | iface interface2 inet manual | 504 | iface interface2 inet manual |
3921 | 510 | bond-mode active-backup | 505 | bond-mode active-backup |
3923 | 511 | bond-master bond0 | 506 | bond-master bond1 |
3924 | 512 | 507 | ||
3927 | 513 | auto bond0 | 508 | auto bond1 |
3928 | 514 | iface bond0 inet static | 509 | iface bond1 inet static |
3929 | 515 | address 10.23.23.2/24 | 510 | address 10.23.23.2/24 |
3930 | 516 | bond-mode active-backup | 511 | bond-mode active-backup |
3931 | 517 | hwaddress ether 52:54:00:12:34:06 | ||
3932 | 518 | bond-slaves none | 512 | bond-slaves none |
3933 | 519 | 513 | ||
3934 | 520 | # control-alias bond0 | ||
3935 | 521 | iface bond0 inet static | ||
3936 | 522 | address 10.23.24.2/24 | ||
3937 | 523 | |||
3938 | 524 | source /etc/network/interfaces.d/*.cfg | 514 | source /etc/network/interfaces.d/*.cfg |
3939 | 525 | """) | 515 | """) |
3940 | 526 | net_ifaces = net.render_interfaces(ns.network_state) | 516 | net_ifaces = net.render_interfaces(ns.network_state) |
3941 | @@ -654,6 +644,91 @@ | |||
3942 | 654 | self.assertEqual(sorted(ifaces.split('\n')), | 644 | self.assertEqual(sorted(ifaces.split('\n')), |
3943 | 655 | sorted(net_ifaces.split('\n'))) | 645 | sorted(net_ifaces.split('\n'))) |
3944 | 656 | 646 | ||
3945 | 647 | @mock.patch('curtin.util.subp') | ||
3946 | 648 | @mock.patch('curtin.util.which') | ||
3947 | 649 | @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) | ||
3948 | 650 | def test_netconfig_passthrough_available(self, mock_which, mock_subp): | ||
3949 | 651 | cloud_init = '/usr/bin/cloud-init' | ||
3950 | 652 | mock_which.return_value = cloud_init | ||
3951 | 653 | mock_subp.return_value = ("NETWORK_CONFIG_V1\nNETWORK_CONFIG_V2\n", '') | ||
3952 | 654 | |||
3953 | 655 | available = net.netconfig_passthrough_available(self.target) | ||
3954 | 656 | |||
3955 | 657 | self.assertEqual(True, available, | ||
3956 | 658 | "netconfig passthrough was NOT available") | ||
3957 | 659 | mock_which.assert_called_with('cloud-init', target=self.target) | ||
3958 | 660 | mock_subp.assert_called_with([cloud_init, 'features'], | ||
3959 | 661 | capture=True, target=self.target) | ||
3960 | 662 | |||
3961 | 663 | @mock.patch('curtin.net.LOG') | ||
3962 | 664 | @mock.patch('curtin.util.subp') | ||
3963 | 665 | @mock.patch('curtin.util.which') | ||
3964 | 666 | @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) | ||
3965 | 667 | def test_netconfig_passthrough_available_no_cloudinit(self, mock_which, | ||
3966 | 668 | mock_subp, mock_log): | ||
3967 | 669 | mock_which.return_value = None | ||
3968 | 670 | |||
3969 | 671 | available = net.netconfig_passthrough_available(self.target) | ||
3970 | 672 | |||
3971 | 673 | self.assertEqual(False, available, | ||
3972 | 674 | "netconfig passthrough was available") | ||
3973 | 675 | self.assertTrue(mock_log.warning.called) | ||
3974 | 676 | self.assertFalse(mock_subp.called) | ||
3975 | 677 | |||
3976 | 678 | @mock.patch('curtin.util.subp') | ||
3977 | 679 | @mock.patch('curtin.util.which') | ||
3978 | 680 | @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) | ||
3979 | 681 | def test_netconfig_passthrough_available_feature_not_found(self, | ||
3980 | 682 | mock_which, | ||
3981 | 683 | mock_subp): | ||
3982 | 684 | cloud_init = '/usr/bin/cloud-init' | ||
3983 | 685 | mock_which.return_value = cloud_init | ||
3984 | 686 | mock_subp.return_value = ('NETWORK_CONFIG_V1\n', '') | ||
3985 | 687 | |||
3986 | 688 | available = net.netconfig_passthrough_available(self.target) | ||
3987 | 689 | |||
3988 | 690 | self.assertEqual(False, available, | ||
3989 | 691 | "netconfig passthrough was available") | ||
3990 | 692 | mock_which.assert_called_with('cloud-init', target=self.target) | ||
3991 | 693 | mock_subp.assert_called_with([cloud_init, 'features'], | ||
3992 | 694 | capture=True, target=self.target) | ||
3993 | 695 | |||
3994 | 696 | @mock.patch('curtin.net.LOG') | ||
3995 | 697 | @mock.patch('curtin.util.subp') | ||
3996 | 698 | @mock.patch('curtin.util.which') | ||
3997 | 699 | @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) | ||
3998 | 700 | def test_netconfig_passthrough_available_exc(self, mock_which, mock_subp, | ||
3999 | 701 | mock_log): | ||
4000 | 702 | cloud_init = '/usr/bin/cloud-init' | ||
4001 | 703 | mock_which.return_value = cloud_init | ||
4002 | 704 | mock_subp.side_effect = util.ProcessExecutionError | ||
4003 | 705 | |||
4004 | 706 | available = net.netconfig_passthrough_available(self.target) | ||
4005 | 707 | |||
4006 | 708 | self.assertEqual(False, available, | ||
4007 | 709 | "netconfig passthrough was available") | ||
4008 | 710 | mock_which.assert_called_with('cloud-init', target=self.target) | ||
4009 | 711 | mock_subp.assert_called_with([cloud_init, 'features'], | ||
4010 | 712 | capture=True, target=self.target) | ||
4011 | 713 | self.assertTrue(mock_log.warning.called) | ||
4012 | 714 | |||
4013 | 715 | @mock.patch('curtin.util.write_file') | ||
4014 | 716 | def test_render_netconfig_passthrough(self, mock_writefile): | ||
4015 | 717 | netcfg = yaml.safe_load(self.config) | ||
4016 | 718 | pt_config = 'etc/cloud/cloud.cfg.d/50-curtin-networking.cfg' | ||
4017 | 719 | target_config = os.path.sep.join((self.target, pt_config),) | ||
4018 | 720 | |||
4019 | 721 | net.render_netconfig_passthrough(self.target, netconfig=netcfg) | ||
4020 | 722 | |||
4021 | 723 | content = config.dump_config(netcfg) | ||
4022 | 724 | mock_writefile.assert_called_with(target_config, content=content) | ||
4023 | 725 | |||
4024 | 726 | def test_render_netconfig_passthrough_nonetcfg(self): | ||
4025 | 727 | netcfg = None | ||
4026 | 728 | self.assertRaises(ValueError, | ||
4027 | 729 | net.render_netconfig_passthrough, | ||
4028 | 730 | self.target, netconfig=netcfg) | ||
4029 | 731 | |||
4030 | 657 | def test_routes_rendered(self): | 732 | def test_routes_rendered(self): |
4031 | 658 | # as reported in bug 1649652 | 733 | # as reported in bug 1649652 |
4032 | 659 | conf = [ | 734 | conf = [ |
4033 | 660 | 735 | ||
4034 | === modified file 'tests/unittests/test_partitioning.py' | |||
4035 | --- tests/unittests/test_partitioning.py 2015-10-02 16:19:07 +0000 | |||
4036 | +++ tests/unittests/test_partitioning.py 2017-10-06 16:35:22 +0000 | |||
4037 | @@ -1,6 +1,7 @@ | |||
4039 | 1 | import unittest | 1 | from unittest import skip |
4040 | 2 | import mock | 2 | import mock |
4041 | 3 | import curtin.commands.block_meta | 3 | import curtin.commands.block_meta |
4042 | 4 | from .helpers import CiTestCase | ||
4043 | 4 | 5 | ||
4044 | 5 | from sys import version_info | 6 | from sys import version_info |
4045 | 6 | if version_info.major == 2: | 7 | if version_info.major == 2: |
4046 | @@ -11,8 +12,8 @@ | |||
4047 | 11 | parted = None # FIXME: remove these tests entirely. This is here for flake8 | 12 | parted = None # FIXME: remove these tests entirely. This is here for flake8 |
4048 | 12 | 13 | ||
4049 | 13 | 14 | ||
4052 | 14 | @unittest.skip | 15 | @skip |
4053 | 15 | class TestBlock(unittest.TestCase): | 16 | class TestBlock(CiTestCase): |
4054 | 16 | storage_config = { | 17 | storage_config = { |
4055 | 17 | "sda": {"id": "sda", "type": "disk", "ptable": "msdos", | 18 | "sda": {"id": "sda", "type": "disk", "ptable": "msdos", |
4056 | 18 | "serial": "DISK_1", "grub_device": "True"}, | 19 | "serial": "DISK_1", "grub_device": "True"}, |
4057 | 19 | 20 | ||
4058 | === added file 'tests/unittests/test_public.py' | |||
4059 | --- tests/unittests/test_public.py 1970-01-01 00:00:00 +0000 | |||
4060 | +++ tests/unittests/test_public.py 2017-10-06 16:35:22 +0000 | |||
4061 | @@ -0,0 +1,54 @@ | |||
4062 | 1 | |||
4063 | 2 | from curtin import block | ||
4064 | 3 | from curtin import config | ||
4065 | 4 | from curtin import futil | ||
4066 | 5 | from curtin import util | ||
4067 | 6 | |||
4068 | 7 | from curtin.commands import curthooks | ||
4069 | 8 | from .helpers import CiTestCase | ||
4070 | 9 | |||
4071 | 10 | |||
4072 | 11 | class TestPublicAPI(CiTestCase): | ||
4073 | 12 | """Test entry points known to be used externally. | ||
4074 | 13 | |||
4075 | 14 | Curtin's only known external library user is the curthooks | ||
4076 | 15 | that are present in the MAAS images. This will test for presense | ||
4077 | 16 | of the modules and entry points that are used there. | ||
4078 | 17 | |||
4079 | 18 | This unit test is present to just test entry points. Function | ||
4080 | 19 | behavior should be present elsewhere.""" | ||
4081 | 20 | |||
4082 | 21 | def assert_has_callables(self, module, expected): | ||
4083 | 22 | self.assertEqual(expected, _module_has(module, expected, callable)) | ||
4084 | 23 | |||
4085 | 24 | def test_block(self): | ||
4086 | 25 | """Verify expected attributes in curtin.block.""" | ||
4087 | 26 | self.assert_has_callables( | ||
4088 | 27 | block, | ||
4089 | 28 | ['get_devices_for_mp', 'get_blockdev_for_partition', '_lsblock']) | ||
4090 | 29 | |||
4091 | 30 | def test_config(self): | ||
4092 | 31 | """Verify exported attributes in curtin.config.""" | ||
4093 | 32 | self.assert_has_callables(config, ['load_config']) | ||
4094 | 33 | |||
4095 | 34 | def test_util(self): | ||
4096 | 35 | """Verify exported attributes in curtin.util.""" | ||
4097 | 36 | self.assert_has_callables( | ||
4098 | 37 | util, ['RunInChroot', 'load_command_environment']) | ||
4099 | 38 | |||
4100 | 39 | def test_centos_apply_network_config(self): | ||
4101 | 40 | """MAAS images use centos_apply_network_config from cmd.curthooks.""" | ||
4102 | 41 | self.assert_has_callables(curthooks, ['centos_apply_network_config']) | ||
4103 | 42 | |||
4104 | 43 | def test_futil(self): | ||
4105 | 44 | """Verify exported attributes in curtin.futil.""" | ||
4106 | 45 | self.assert_has_callables(futil, ['write_files']) | ||
4107 | 46 | |||
4108 | 47 | |||
4109 | 48 | def _module_has(module, names, nfilter=None): | ||
4110 | 49 | found = [(name, getattr(module, name)) | ||
4111 | 50 | for name in names if hasattr(module, name)] | ||
4112 | 51 | if nfilter is not None: | ||
4113 | 52 | found = [(name, attr) for name, attr in found if nfilter(attr)] | ||
4114 | 53 | |||
4115 | 54 | return [name for name, _ in found] | ||
4116 | 0 | 55 | ||
4117 | === modified file 'tests/unittests/test_reporter.py' | |||
4118 | --- tests/unittests/test_reporter.py 2017-03-01 16:13:56 +0000 | |||
4119 | +++ tests/unittests/test_reporter.py 2017-10-06 16:35:22 +0000 | |||
4120 | @@ -21,7 +21,6 @@ | |||
4121 | 21 | unicode_literals, | 21 | unicode_literals, |
4122 | 22 | ) | 22 | ) |
4123 | 23 | 23 | ||
4124 | 24 | from unittest import TestCase | ||
4125 | 25 | from mock import patch | 24 | from mock import patch |
4126 | 26 | 25 | ||
4127 | 27 | from curtin.reporter.legacy import ( | 26 | from curtin.reporter.legacy import ( |
4128 | @@ -39,13 +38,12 @@ | |||
4129 | 39 | from curtin.reporter import handlers | 38 | from curtin.reporter import handlers |
4130 | 40 | from curtin import url_helper | 39 | from curtin import url_helper |
4131 | 41 | from curtin.reporter import events | 40 | from curtin.reporter import events |
4132 | 41 | from .helpers import CiTestCase | ||
4133 | 42 | 42 | ||
4134 | 43 | import os | ||
4135 | 44 | import tempfile | ||
4136 | 45 | import base64 | 43 | import base64 |
4137 | 46 | 44 | ||
4138 | 47 | 45 | ||
4140 | 48 | class TestLegacyReporter(TestCase): | 46 | class TestLegacyReporter(CiTestCase): |
4141 | 49 | 47 | ||
4142 | 50 | @patch('curtin.reporter.legacy.LOG') | 48 | @patch('curtin.reporter.legacy.LOG') |
4143 | 51 | def test_load_reporter_logs_empty_cfg(self, mock_LOG): | 49 | def test_load_reporter_logs_empty_cfg(self, mock_LOG): |
4144 | @@ -72,7 +70,7 @@ | |||
4145 | 72 | self.assertTrue(mock_LOG.error.called) | 70 | self.assertTrue(mock_LOG.error.called) |
4146 | 73 | 71 | ||
4147 | 74 | 72 | ||
4149 | 75 | class TestMAASReporter(TestCase): | 73 | class TestMAASReporter(CiTestCase): |
4150 | 76 | def test_load_factory_raises_exception_wrong_options(self): | 74 | def test_load_factory_raises_exception_wrong_options(self): |
4151 | 77 | options = {'wrong': 'wrong'} | 75 | options = {'wrong': 'wrong'} |
4152 | 78 | self.assertRaises( | 76 | self.assertRaises( |
4153 | @@ -86,7 +84,7 @@ | |||
4154 | 86 | self.assertIsInstance(reporter, MAASReporter) | 84 | self.assertIsInstance(reporter, MAASReporter) |
4155 | 87 | 85 | ||
4156 | 88 | 86 | ||
4158 | 89 | class TestReporter(TestCase): | 87 | class TestReporter(CiTestCase): |
4159 | 90 | config = {'element1': {'type': 'webhook', 'level': 'INFO', | 88 | config = {'element1': {'type': 'webhook', 'level': 'INFO', |
4160 | 91 | 'consumer_key': "ck_foo", | 89 | 'consumer_key': "ck_foo", |
4161 | 92 | 'consumer_secret': 'cs_foo', | 90 | 'consumer_secret': 'cs_foo', |
4162 | @@ -175,39 +173,32 @@ | |||
4163 | 175 | @patch('curtin.reporter.events.report_event') | 173 | @patch('curtin.reporter.events.report_event') |
4164 | 176 | def test_report_finished_post_files(self, mock_report_event): | 174 | def test_report_finished_post_files(self, mock_report_event): |
4165 | 177 | test_data = b'abcdefg' | 175 | test_data = b'abcdefg' |
4181 | 178 | tmp = tempfile.mkstemp() | 176 | tmpfname = self.tmp_path('testfile') |
4182 | 179 | try: | 177 | with open(tmpfname, 'wb') as fp: |
4183 | 180 | with open(tmp[1], 'wb') as fp: | 178 | fp.write(test_data) |
4184 | 181 | fp.write(test_data) | 179 | events.report_finish_event(self.ev_name, self.ev_desc, |
4185 | 182 | events.report_finish_event(self.ev_name, self.ev_desc, | 180 | post_files=[tmpfname]) |
4186 | 183 | post_files=[tmp[1]]) | 181 | event = self._get_reported_event(mock_report_event) |
4187 | 184 | event = self._get_reported_event(mock_report_event) | 182 | files = event.as_dict().get('files') |
4188 | 185 | files = event.as_dict().get('files') | 183 | self.assertTrue(len(files) == 1) |
4189 | 186 | self.assertTrue(len(files) == 1) | 184 | self.assertEqual(files[0].get('path'), tmpfname) |
4190 | 187 | self.assertEqual(files[0].get('path'), tmp[1]) | 185 | self.assertEqual(files[0].get('encoding'), 'base64') |
4191 | 188 | self.assertEqual(files[0].get('encoding'), 'base64') | 186 | self.assertEqual(files[0].get('content'), |
4192 | 189 | self.assertEqual(files[0].get('content'), | 187 | base64.b64encode(test_data).decode()) |
4178 | 190 | base64.b64encode(test_data).decode()) | ||
4179 | 191 | finally: | ||
4180 | 192 | os.remove(tmp[1]) | ||
4193 | 193 | 188 | ||
4194 | 194 | @patch('curtin.url_helper.OauthUrlHelper') | 189 | @patch('curtin.url_helper.OauthUrlHelper') |
4195 | 195 | def test_webhook_handler_post_files(self, mock_url_helper): | 190 | def test_webhook_handler_post_files(self, mock_url_helper): |
4196 | 196 | test_data = b'abcdefg' | 191 | test_data = b'abcdefg' |
4214 | 197 | tmp = tempfile.mkstemp() | 192 | tmpfname = self.tmp_path('testfile') |
4215 | 198 | tmpfname = tmp[1] | 193 | with open(tmpfname, 'wb') as fp: |
4216 | 199 | try: | 194 | fp.write(test_data) |
4217 | 200 | with open(tmpfname, 'wb') as fp: | 195 | event = events.FinishReportingEvent('test_event_name', |
4218 | 201 | fp.write(test_data) | 196 | 'test event description', |
4219 | 202 | event = events.FinishReportingEvent('test_event_name', | 197 | post_files=[tmpfname], |
4220 | 203 | 'test event description', | 198 | level='INFO') |
4221 | 204 | post_files=[tmpfname], | 199 | webhook_handler = handlers.WebHookHandler('127.0.0.1:8000', |
4222 | 205 | level='INFO') | 200 | level='INFO') |
4223 | 206 | webhook_handler = handlers.WebHookHandler('127.0.0.1:8000', | 201 | webhook_handler.publish_event(event) |
4224 | 207 | level='INFO') | 202 | webhook_handler.oauth_helper.geturl.assert_called_with( |
4225 | 208 | webhook_handler.publish_event(event) | 203 | url='127.0.0.1:8000', data=event.as_dict(), |
4226 | 209 | webhook_handler.oauth_helper.geturl.assert_called_with( | 204 | headers=webhook_handler.headers, retries=None) |
4210 | 210 | url='127.0.0.1:8000', data=event.as_dict(), | ||
4211 | 211 | headers=webhook_handler.headers, retries=None) | ||
4212 | 212 | finally: | ||
4213 | 213 | os.remove(tmpfname) | ||
4227 | 214 | 205 | ||
4228 | === modified file 'tests/unittests/test_util.py' | |||
4229 | --- tests/unittests/test_util.py 2017-06-12 20:39:06 +0000 | |||
4230 | +++ tests/unittests/test_util.py 2017-10-06 16:35:22 +0000 | |||
4231 | @@ -1,16 +1,14 @@ | |||
4233 | 1 | from unittest import TestCase, skipIf | 1 | from unittest import skipIf |
4234 | 2 | import mock | 2 | import mock |
4235 | 3 | import os | 3 | import os |
4236 | 4 | import stat | 4 | import stat |
4237 | 5 | import shutil | ||
4238 | 6 | import tempfile | ||
4239 | 7 | from textwrap import dedent | 5 | from textwrap import dedent |
4240 | 8 | 6 | ||
4241 | 9 | from curtin import util | 7 | from curtin import util |
4246 | 10 | from .helpers import simple_mocked_open | 8 | from .helpers import CiTestCase, simple_mocked_open |
4247 | 11 | 9 | ||
4248 | 12 | 10 | ||
4249 | 13 | class TestLogTimer(TestCase): | 11 | class TestLogTimer(CiTestCase): |
4250 | 14 | def test_logger_called(self): | 12 | def test_logger_called(self): |
4251 | 15 | data = {} | 13 | data = {} |
4252 | 16 | 14 | ||
4253 | @@ -24,16 +22,14 @@ | |||
4254 | 24 | self.assertIn("mymessage", data['msg']) | 22 | self.assertIn("mymessage", data['msg']) |
4255 | 25 | 23 | ||
4256 | 26 | 24 | ||
4258 | 27 | class TestDisableDaemons(TestCase): | 25 | class TestDisableDaemons(CiTestCase): |
4259 | 28 | prcpath = "usr/sbin/policy-rc.d" | 26 | prcpath = "usr/sbin/policy-rc.d" |
4260 | 29 | 27 | ||
4261 | 30 | def setUp(self): | 28 | def setUp(self): |
4263 | 31 | self.target = tempfile.mkdtemp() | 29 | super(TestDisableDaemons, self).setUp() |
4264 | 30 | self.target = self.tmp_dir() | ||
4265 | 32 | self.temp_prc = os.path.join(self.target, self.prcpath) | 31 | self.temp_prc = os.path.join(self.target, self.prcpath) |
4266 | 33 | 32 | ||
4267 | 34 | def tearDown(self): | ||
4268 | 35 | shutil.rmtree(self.target) | ||
4269 | 36 | |||
4270 | 37 | def test_disable_daemons_in_root_works(self): | 33 | def test_disable_daemons_in_root_works(self): |
4271 | 38 | ret = util.disable_daemons_in_root(self.target) | 34 | ret = util.disable_daemons_in_root(self.target) |
4272 | 39 | self.assertTrue(ret) | 35 | self.assertTrue(ret) |
4273 | @@ -55,8 +51,10 @@ | |||
4274 | 55 | self.assertTrue(os.path.exists(self.temp_prc)) | 51 | self.assertTrue(os.path.exists(self.temp_prc)) |
4275 | 56 | 52 | ||
4276 | 57 | 53 | ||
4278 | 58 | class TestWhich(TestCase): | 54 | class TestWhich(CiTestCase): |
4279 | 55 | |||
4280 | 59 | def setUp(self): | 56 | def setUp(self): |
4281 | 57 | super(TestWhich, self).setUp() | ||
4282 | 60 | self.orig_is_exe = util.is_exe | 58 | self.orig_is_exe = util.is_exe |
4283 | 61 | util.is_exe = self.my_is_exe | 59 | util.is_exe = self.my_is_exe |
4284 | 62 | self.orig_path = os.environ.get("PATH") | 60 | self.orig_path = os.environ.get("PATH") |
4285 | @@ -103,8 +101,10 @@ | |||
4286 | 103 | self.assertEqual(found, "/usr/bin2/fuzz") | 101 | self.assertEqual(found, "/usr/bin2/fuzz") |
4287 | 104 | 102 | ||
4288 | 105 | 103 | ||
4290 | 106 | class TestLsbRelease(TestCase): | 104 | class TestLsbRelease(CiTestCase): |
4291 | 105 | |||
4292 | 107 | def setUp(self): | 106 | def setUp(self): |
4293 | 107 | super(TestLsbRelease, self).setUp() | ||
4294 | 108 | self._reset_cache() | 108 | self._reset_cache() |
4295 | 109 | 109 | ||
4296 | 110 | def _reset_cache(self): | 110 | def _reset_cache(self): |
4297 | @@ -143,7 +143,7 @@ | |||
4298 | 143 | self.assertEqual(util.lsb_release(), expected) | 143 | self.assertEqual(util.lsb_release(), expected) |
4299 | 144 | 144 | ||
4300 | 145 | 145 | ||
4302 | 146 | class TestSubp(TestCase): | 146 | class TestSubp(CiTestCase): |
4303 | 147 | 147 | ||
4304 | 148 | stdin2err = ['bash', '-c', 'cat >&2'] | 148 | stdin2err = ['bash', '-c', 'cat >&2'] |
4305 | 149 | stdin2out = ['cat'] | 149 | stdin2out = ['cat'] |
4306 | @@ -160,6 +160,12 @@ | |||
4307 | 160 | decode_type = str | 160 | decode_type = str |
4308 | 161 | nodecode_type = bytes | 161 | nodecode_type = bytes |
4309 | 162 | 162 | ||
4310 | 163 | def setUp(self): | ||
4311 | 164 | super(TestSubp, self).setUp() | ||
4312 | 165 | self.add_patch( | ||
4313 | 166 | 'curtin.util._get_unshare_pid_args', 'mock_get_unshare_pid_args', | ||
4314 | 167 | return_value=[]) | ||
4315 | 168 | |||
4316 | 163 | def printf_cmd(self, *args): | 169 | def printf_cmd(self, *args): |
4317 | 164 | # bash's printf supports \xaa. So does /usr/bin/printf | 170 | # bash's printf supports \xaa. So does /usr/bin/printf |
4318 | 165 | # but by using bash, we remove dependency on another program. | 171 | # but by using bash, we remove dependency on another program. |
4319 | @@ -296,12 +302,29 @@ | |||
4320 | 296 | calls = m_popen.call_args_list | 302 | calls = m_popen.call_args_list |
4321 | 297 | popen_args, popen_kwargs = calls[-1] | 303 | popen_args, popen_kwargs = calls[-1] |
4322 | 298 | target = util.target_path(kwargs.get('target', None)) | 304 | target = util.target_path(kwargs.get('target', None)) |
4323 | 305 | unshcmd = self.mock_get_unshare_pid_args.return_value | ||
4324 | 299 | if target == "/": | 306 | if target == "/": |
4326 | 300 | self.assertEqual(cmd, popen_args[0]) | 307 | self.assertEqual(unshcmd + list(cmd), popen_args[0]) |
4327 | 301 | else: | 308 | else: |
4329 | 302 | self.assertEqual(['chroot', target] + list(cmd), popen_args[0]) | 309 | self.assertEqual(unshcmd + ['chroot', target] + list(cmd), |
4330 | 310 | popen_args[0]) | ||
4331 | 303 | return calls | 311 | return calls |
4332 | 304 | 312 | ||
4333 | 313 | def test_args_can_be_a_tuple(self): | ||
4334 | 314 | """subp can take a tuple for cmd rather than a list.""" | ||
4335 | 315 | my_cmd = tuple(['echo', 'hi', 'mom']) | ||
4336 | 316 | calls = self._subp_wrap_popen(my_cmd, {}) | ||
4337 | 317 | args, kwargs = calls[0] | ||
4338 | 318 | # subp was called with cmd as a tuple. That may get converted to | ||
4339 | 319 | # a list before subprocess.popen. So only compare as lists. | ||
4340 | 320 | self.assertEqual(1, len(calls)) | ||
4341 | 321 | self.assertEqual(list(my_cmd), list(args[0])) | ||
4342 | 322 | |||
4343 | 323 | def test_args_can_be_a_string(self): | ||
4344 | 324 | """subp("cat") is acceptable, as suprocess.call("cat") works fine.""" | ||
4345 | 325 | out, err = util.subp("cat", data=b'hi mom', capture=True, decode=False) | ||
4346 | 326 | self.assertEqual(b'hi mom', out) | ||
4347 | 327 | |||
4348 | 305 | def test_with_target_gets_chroot(self): | 328 | def test_with_target_gets_chroot(self): |
4349 | 306 | args, kwargs = self._subp_wrap_popen(["my-command"], | 329 | args, kwargs = self._subp_wrap_popen(["my-command"], |
4350 | 307 | {'target': "/mytarget"})[0] | 330 | {'target': "/mytarget"})[0] |
4351 | @@ -342,8 +365,94 @@ | |||
4352 | 342 | # since we fail a few times, it needs to have been called again. | 365 | # since we fail a few times, it needs to have been called again. |
4353 | 343 | self.assertEqual(len(r), len(rcs)) | 366 | self.assertEqual(len(r), len(rcs)) |
4354 | 344 | 367 | ||
4357 | 345 | 368 | def test_unshare_pid_return_is_used(self): | |
4358 | 346 | class TestHuman2Bytes(TestCase): | 369 | """The return of _get_unshare_pid_return needs to be in command.""" |
4359 | 370 | my_unshare_cmd = ['do-unshare-command', 'arg0', 'arg1', '--'] | ||
4360 | 371 | self.mock_get_unshare_pid_args.return_value = my_unshare_cmd | ||
4361 | 372 | my_kwargs = {'target': '/target', 'unshare_pid': True} | ||
4362 | 373 | r = self._subp_wrap_popen(['apt-get', 'install'], my_kwargs) | ||
4363 | 374 | self.assertEqual(1, len(r)) | ||
4364 | 375 | args, kwargs = r[0] | ||
4365 | 376 | self.assertEqual( | ||
4366 | 377 | [mock.call(my_kwargs['unshare_pid'], my_kwargs['target'])], | ||
4367 | 378 | self.mock_get_unshare_pid_args.call_args_list) | ||
4368 | 379 | expected = (my_unshare_cmd + ['chroot', '/target'] + | ||
4369 | 380 | ['apt-get', 'install']) | ||
4370 | 381 | self.assertEqual(expected, args[0]) | ||
4371 | 382 | |||
4372 | 383 | |||
4373 | 384 | class TestGetUnsharePidArgs(CiTestCase): | ||
4374 | 385 | """Test the internal implementation for when to unshare.""" | ||
4375 | 386 | |||
4376 | 387 | def setUp(self): | ||
4377 | 388 | super(TestGetUnsharePidArgs, self).setUp() | ||
4378 | 389 | self.add_patch('curtin.util._has_unshare_pid', 'mock_has_unshare_pid', | ||
4379 | 390 | return_value=True) | ||
4380 | 391 | # our trusty tox environment with mock 1.0.1 will stack trace | ||
4381 | 392 | # if autospec is not disabled here. | ||
4382 | 393 | self.add_patch('curtin.util.os.geteuid', 'mock_geteuid', | ||
4383 | 394 | autospec=False, return_value=0) | ||
4384 | 395 | |||
4385 | 396 | def assertOff(self, result): | ||
4386 | 397 | self.assertEqual([], result) | ||
4387 | 398 | |||
4388 | 399 | def assertOn(self, result): | ||
4389 | 400 | self.assertEqual(['unshare', '--fork', '--pid', '--'], result) | ||
4390 | 401 | |||
4391 | 402 | def test_unshare_pid_none_and_not_root_means_off(self): | ||
4392 | 403 | """If not root, then expect off.""" | ||
4393 | 404 | self.assertOff(util._get_unshare_pid_args(None, "/foo", 500)) | ||
4394 | 405 | self.assertOff(util._get_unshare_pid_args(None, "/", 500)) | ||
4395 | 406 | |||
4396 | 407 | self.mock_geteuid.return_value = 500 | ||
4397 | 408 | self.assertOff(util._get_unshare_pid_args(None, "/")) | ||
4398 | 409 | self.assertOff( | ||
4399 | 410 | util._get_unshare_pid_args(unshare_pid=None, target="/foo")) | ||
4400 | 411 | |||
4401 | 412 | def test_unshare_pid_none_and_no_unshare_pid_means_off(self): | ||
4402 | 413 | """No unshare support and unshare_pid is None means off.""" | ||
4403 | 414 | self.mock_has_unshare_pid.return_value = False | ||
4404 | 415 | self.assertOff(util._get_unshare_pid_args(None, "/target", 0)) | ||
4405 | 416 | |||
4406 | 417 | def test_unshare_pid_true_and_no_unshare_pid_raises(self): | ||
4407 | 418 | """Passing unshare_pid in as True and no command should raise.""" | ||
4408 | 419 | self.mock_has_unshare_pid.return_value = False | ||
4409 | 420 | expected_msg = 'no unshare command' | ||
4410 | 421 | with self.assertRaisesRegexp(RuntimeError, expected_msg): | ||
4411 | 422 | util._get_unshare_pid_args(True) | ||
4412 | 423 | |||
4413 | 424 | with self.assertRaisesRegexp(RuntimeError, expected_msg): | ||
4414 | 425 | util._get_unshare_pid_args(True, "/foo", 0) | ||
4415 | 426 | |||
4416 | 427 | def test_unshare_pid_true_and_not_root_raises(self): | ||
4417 | 428 | """When unshare_pid is True for non-root an error is raised.""" | ||
4418 | 429 | expected_msg = 'euid.* != 0' | ||
4419 | 430 | with self.assertRaisesRegexp(RuntimeError, expected_msg): | ||
4420 | 431 | util._get_unshare_pid_args(True, "/foo", 500) | ||
4421 | 432 | |||
4422 | 433 | self.mock_geteuid.return_value = 500 | ||
4423 | 434 | with self.assertRaisesRegexp(RuntimeError, expected_msg): | ||
4424 | 435 | util._get_unshare_pid_args(True) | ||
4425 | 436 | |||
4426 | 437 | def test_euid0_target_not_slash(self): | ||
4427 | 438 | """If root and target is not /, then expect on.""" | ||
4428 | 439 | self.assertOn(util._get_unshare_pid_args(None, target="/foo", euid=0)) | ||
4429 | 440 | |||
4430 | 441 | def test_euid0_target_slash(self): | ||
4431 | 442 | """If root and target is /, then expect off.""" | ||
4432 | 443 | self.assertOff(util._get_unshare_pid_args(None, "/", 0)) | ||
4433 | 444 | self.assertOff(util._get_unshare_pid_args(None, target=None, euid=0)) | ||
4434 | 445 | |||
4435 | 446 | def test_unshare_pid_of_false_means_off(self): | ||
4436 | 447 | """Any unshare_pid value false-ish other than None means no unshare.""" | ||
4437 | 448 | self.assertOff( | ||
4438 | 449 | util._get_unshare_pid_args(unshare_pid=False, target=None)) | ||
4439 | 450 | self.assertOff(util._get_unshare_pid_args(False, "/target", 1)) | ||
4440 | 451 | self.assertOff(util._get_unshare_pid_args(False, "/", 0)) | ||
4441 | 452 | self.assertOff(util._get_unshare_pid_args("", "/target", 0)) | ||
4442 | 453 | |||
4443 | 454 | |||
4444 | 455 | class TestHuman2Bytes(CiTestCase): | ||
4445 | 347 | GB = 1024 * 1024 * 1024 | 456 | GB = 1024 * 1024 * 1024 |
4446 | 348 | MB = 1024 * 1024 | 457 | MB = 1024 * 1024 |
4447 | 349 | 458 | ||
4448 | @@ -397,52 +506,42 @@ | |||
4449 | 397 | util.bytes2human(util.human2bytes(size_str)), size_str) | 506 | util.bytes2human(util.human2bytes(size_str)), size_str) |
4450 | 398 | 507 | ||
4451 | 399 | 508 | ||
4453 | 400 | class TestSetUnExecutable(TestCase): | 509 | class TestSetUnExecutable(CiTestCase): |
4454 | 401 | tmpf = None | 510 | tmpf = None |
4455 | 402 | tmpd = None | 511 | tmpd = None |
4456 | 403 | 512 | ||
4472 | 404 | def tearDown(self): | 513 | def setUp(self): |
4473 | 405 | if self.tmpf: | 514 | super(CiTestCase, self).setUp() |
4474 | 406 | if os.path.exists(self.tmpf): | 515 | self.tmpd = self.tmp_dir() |
4460 | 407 | os.unlink(self.tmpf) | ||
4461 | 408 | self.tmpf = None | ||
4462 | 409 | if self.tmpd: | ||
4463 | 410 | shutil.rmtree(self.tmpd) | ||
4464 | 411 | self.tmpd = None | ||
4465 | 412 | |||
4466 | 413 | def tempfile(self, data=None): | ||
4467 | 414 | fp, self.tmpf = tempfile.mkstemp() | ||
4468 | 415 | if data: | ||
4469 | 416 | fp.write(data) | ||
4470 | 417 | os.close(fp) | ||
4471 | 418 | return self.tmpf | ||
4475 | 419 | 516 | ||
4476 | 420 | def test_change_needed_returns_original_mode(self): | 517 | def test_change_needed_returns_original_mode(self): |
4478 | 421 | tmpf = self.tempfile() | 518 | tmpf = self.tmp_path('testfile') |
4479 | 519 | util.write_file(tmpf, '') | ||
4480 | 422 | os.chmod(tmpf, 0o755) | 520 | os.chmod(tmpf, 0o755) |
4481 | 423 | ret = util.set_unexecutable(tmpf) | 521 | ret = util.set_unexecutable(tmpf) |
4482 | 424 | self.assertEqual(ret, 0o0755) | 522 | self.assertEqual(ret, 0o0755) |
4483 | 425 | 523 | ||
4484 | 426 | def test_no_change_needed_returns_none(self): | 524 | def test_no_change_needed_returns_none(self): |
4486 | 427 | tmpf = self.tempfile() | 525 | tmpf = self.tmp_path('testfile') |
4487 | 526 | util.write_file(tmpf, '') | ||
4488 | 428 | os.chmod(tmpf, 0o600) | 527 | os.chmod(tmpf, 0o600) |
4489 | 429 | ret = util.set_unexecutable(tmpf) | 528 | ret = util.set_unexecutable(tmpf) |
4490 | 430 | self.assertEqual(ret, None) | 529 | self.assertEqual(ret, None) |
4491 | 431 | 530 | ||
4492 | 432 | def test_change_does_as_expected(self): | 531 | def test_change_does_as_expected(self): |
4494 | 433 | tmpf = self.tempfile() | 532 | tmpf = self.tmp_path('testfile') |
4495 | 533 | util.write_file(tmpf, '') | ||
4496 | 434 | os.chmod(tmpf, 0o755) | 534 | os.chmod(tmpf, 0o755) |
4497 | 435 | ret = util.set_unexecutable(tmpf) | 535 | ret = util.set_unexecutable(tmpf) |
4498 | 436 | self.assertEqual(ret, 0o0755) | 536 | self.assertEqual(ret, 0o0755) |
4499 | 437 | self.assertEqual(stat.S_IMODE(os.stat(tmpf).st_mode), 0o0644) | 537 | self.assertEqual(stat.S_IMODE(os.stat(tmpf).st_mode), 0o0644) |
4500 | 438 | 538 | ||
4501 | 439 | def test_strict_no_exists_raises_exception(self): | 539 | def test_strict_no_exists_raises_exception(self): |
4502 | 440 | self.tmpd = tempfile.mkdtemp() | ||
4503 | 441 | bogus = os.path.join(self.tmpd, 'bogus') | 540 | bogus = os.path.join(self.tmpd, 'bogus') |
4504 | 442 | self.assertRaises(ValueError, util.set_unexecutable, bogus, True) | 541 | self.assertRaises(ValueError, util.set_unexecutable, bogus, True) |
4505 | 443 | 542 | ||
4506 | 444 | 543 | ||
4508 | 445 | class TestTargetPath(TestCase): | 544 | class TestTargetPath(CiTestCase): |
4509 | 446 | def test_target_empty_string(self): | 545 | def test_target_empty_string(self): |
4510 | 447 | self.assertEqual("/etc/passwd", util.target_path("", "/etc/passwd")) | 546 | self.assertEqual("/etc/passwd", util.target_path("", "/etc/passwd")) |
4511 | 448 | 547 | ||
4512 | @@ -484,7 +583,7 @@ | |||
4513 | 484 | util.target_path("/target/", "///my/path/")) | 583 | util.target_path("/target/", "///my/path/")) |
4514 | 485 | 584 | ||
4515 | 486 | 585 | ||
4517 | 487 | class TestRunInChroot(TestCase): | 586 | class TestRunInChroot(CiTestCase): |
4518 | 488 | """Test the legacy 'RunInChroot'. | 587 | """Test the legacy 'RunInChroot'. |
4519 | 489 | 588 | ||
4520 | 490 | The test works by mocking ChrootableTarget's __enter__ to do nothing. | 589 | The test works by mocking ChrootableTarget's __enter__ to do nothing. |
4521 | @@ -514,7 +613,7 @@ | |||
4522 | 514 | m_subp.assert_called_with(cmd, target=target) | 613 | m_subp.assert_called_with(cmd, target=target) |
4523 | 515 | 614 | ||
4524 | 516 | 615 | ||
4526 | 517 | class TestLoadFile(TestCase): | 616 | class TestLoadFile(CiTestCase): |
4527 | 518 | """Test utility 'load_file'""" | 617 | """Test utility 'load_file'""" |
4528 | 519 | 618 | ||
4529 | 520 | def test_load_file_simple(self): | 619 | def test_load_file_simple(self): |
4530 | @@ -545,7 +644,7 @@ | |||
4531 | 545 | self.assertEqual(loaded_contents, contents) | 644 | self.assertEqual(loaded_contents, contents) |
4532 | 546 | 645 | ||
4533 | 547 | 646 | ||
4535 | 548 | class TestIpAddress(TestCase): | 647 | class TestIpAddress(CiTestCase): |
4536 | 549 | """Test utility 'is_valid_ip{,v4,v6}_address'""" | 648 | """Test utility 'is_valid_ip{,v4,v6}_address'""" |
4537 | 550 | 649 | ||
4538 | 551 | def test_is_valid_ipv6_address(self): | 650 | def test_is_valid_ipv6_address(self): |
4539 | @@ -570,10 +669,11 @@ | |||
4540 | 570 | '2002:4559:1FE2:0000:0000:0000:4559:1FE2')) | 669 | '2002:4559:1FE2:0000:0000:0000:4559:1FE2')) |
4541 | 571 | 670 | ||
4542 | 572 | 671 | ||
4544 | 573 | class TestLoadCommandEnvironment(TestCase): | 672 | class TestLoadCommandEnvironment(CiTestCase): |
4545 | 673 | |||
4546 | 574 | def setUp(self): | 674 | def setUp(self): |
4549 | 575 | self.tmpd = tempfile.mkdtemp() | 675 | super(TestLoadCommandEnvironment, self).setUp() |
4550 | 576 | self.addCleanup(shutil.rmtree, self.tmpd) | 676 | self.tmpd = self.tmp_dir() |
4551 | 577 | all_names = { | 677 | all_names = { |
4552 | 578 | 'CONFIG', | 678 | 'CONFIG', |
4553 | 579 | 'OUTPUT_FSTAB', | 679 | 'OUTPUT_FSTAB', |
4554 | @@ -616,7 +716,7 @@ | |||
4555 | 616 | self.fail("unexpected key error raised: %s" % e) | 716 | self.fail("unexpected key error raised: %s" % e) |
4556 | 617 | 717 | ||
4557 | 618 | 718 | ||
4559 | 619 | class TestWaitForRemoval(TestCase): | 719 | class TestWaitForRemoval(CiTestCase): |
4560 | 620 | def test_wait_for_removal_missing_path(self): | 720 | def test_wait_for_removal_missing_path(self): |
4561 | 621 | with self.assertRaises(ValueError): | 721 | with self.assertRaises(ValueError): |
4562 | 622 | util.wait_for_removal(None) | 722 | util.wait_for_removal(None) |
4563 | @@ -684,14 +784,12 @@ | |||
4564 | 684 | ]) | 784 | ]) |
4565 | 685 | 785 | ||
4566 | 686 | 786 | ||
4568 | 687 | class TestGetEFIBootMGR(TestCase): | 787 | class TestGetEFIBootMGR(CiTestCase): |
4569 | 688 | 788 | ||
4570 | 689 | def setUp(self): | 789 | def setUp(self): |
4571 | 690 | super(TestGetEFIBootMGR, self).setUp() | 790 | super(TestGetEFIBootMGR, self).setUp() |
4576 | 691 | mock_chroot = mock.patch( | 791 | self.add_patch( |
4577 | 692 | 'curtin.util.ChrootableTarget', autospec=False) | 792 | 'curtin.util.ChrootableTarget', 'mock_chroot', autospec=False) |
4574 | 693 | self.mock_chroot = mock_chroot.start() | ||
4575 | 694 | self.addCleanup(mock_chroot.stop) | ||
4578 | 695 | self.mock_in_chroot = mock.MagicMock() | 793 | self.mock_in_chroot = mock.MagicMock() |
4579 | 696 | self.mock_in_chroot.__enter__.return_value = self.mock_in_chroot | 794 | self.mock_in_chroot.__enter__.return_value = self.mock_in_chroot |
4580 | 697 | self.in_chroot_subp_output = [] | 795 | self.in_chroot_subp_output = [] |
4581 | @@ -753,4 +851,55 @@ | |||
4582 | 753 | }, observed) | 851 | }, observed) |
4583 | 754 | 852 | ||
4584 | 755 | 853 | ||
4585 | 854 | class TestUsesSystemd(CiTestCase): | ||
4586 | 855 | |||
4587 | 856 | def setUp(self): | ||
4588 | 857 | super(TestUsesSystemd, self).setUp() | ||
4589 | 858 | self._reset_cache() | ||
4590 | 859 | self.add_patch('curtin.util.os.path.isdir', 'mock_isdir') | ||
4591 | 860 | |||
4592 | 861 | def _reset_cache(self): | ||
4593 | 862 | util._USES_SYSTEMD = None | ||
4594 | 863 | |||
4595 | 864 | def test_uses_systemd_on_systemd(self): | ||
4596 | 865 | """ Test that uses_systemd returns True if sdpath is a dir """ | ||
4597 | 866 | # systemd_enabled | ||
4598 | 867 | self.mock_isdir.return_value = True | ||
4599 | 868 | result = util.uses_systemd() | ||
4600 | 869 | self.assertEqual(True, result) | ||
4601 | 870 | self.assertEqual(1, len(self.mock_isdir.call_args_list)) | ||
4602 | 871 | |||
4603 | 872 | def test_uses_systemd_cached(self): | ||
4604 | 873 | """Test that we cache the uses_systemd result""" | ||
4605 | 874 | |||
4606 | 875 | # reset_cache should ensure it's unset | ||
4607 | 876 | self.assertEqual(None, util._USES_SYSTEMD) | ||
4608 | 877 | |||
4609 | 878 | # systemd enabled | ||
4610 | 879 | self.mock_isdir.return_value = True | ||
4611 | 880 | |||
4612 | 881 | # first time | ||
4613 | 882 | first_result = util.uses_systemd() | ||
4614 | 883 | |||
4615 | 884 | # check the cache value | ||
4616 | 885 | self.assertEqual(first_result, util._USES_SYSTEMD) | ||
4617 | 886 | |||
4618 | 887 | # second time | ||
4619 | 888 | second_result = util.uses_systemd() | ||
4620 | 889 | |||
4621 | 890 | # results should match between tries | ||
4622 | 891 | self.assertEqual(True, first_result) | ||
4623 | 892 | self.assertEqual(True, second_result) | ||
4624 | 893 | |||
4625 | 894 | # isdir should only be called once | ||
4626 | 895 | self.assertEqual(1, len(self.mock_isdir.call_args_list)) | ||
4627 | 896 | |||
4628 | 897 | def test_uses_systemd_on_non_systemd(self): | ||
4629 | 898 | """ Test that uses_systemd returns False if sdpath is not a dir """ | ||
4630 | 899 | # systemd not available | ||
4631 | 900 | self.mock_isdir.return_value = False | ||
4632 | 901 | result = util.uses_systemd() | ||
4633 | 902 | self.assertEqual(False, result) | ||
4634 | 903 | |||
4635 | 904 | |||
4636 | 756 | # vi: ts=4 expandtab syntax=python | 905 | # vi: ts=4 expandtab syntax=python |
4637 | 757 | 906 | ||
4638 | === modified file 'tests/unittests/test_version.py' | |||
4639 | --- tests/unittests/test_version.py 2017-02-08 22:22:44 +0000 | |||
4640 | +++ tests/unittests/test_version.py 2017-10-06 16:35:22 +0000 | |||
4641 | @@ -1,28 +1,16 @@ | |||
4642 | 1 | from unittest import TestCase | ||
4643 | 2 | import mock | 1 | import mock |
4644 | 3 | import subprocess | 2 | import subprocess |
4645 | 4 | import os | 3 | import os |
4646 | 5 | 4 | ||
4647 | 6 | from curtin import version | 5 | from curtin import version |
4648 | 7 | from curtin import __version__ as old_version | 6 | from curtin import __version__ as old_version |
4667 | 8 | 7 | from .helpers import CiTestCase | |
4668 | 9 | 8 | ||
4669 | 10 | class CurtinVersionBase(TestCase): | 9 | |
4670 | 11 | def setUp(self): | 10 | class TestCurtinVersion(CiTestCase): |
4671 | 12 | super(CurtinVersionBase, self).setUp() | 11 | |
4672 | 13 | 12 | def setUp(self): | |
4673 | 14 | def add_patch(self, target, attr): | 13 | super(TestCurtinVersion, self).setUp() |
4656 | 15 | """Patches specified target object and sets it as attr on test | ||
4657 | 16 | instance also schedules cleanup""" | ||
4658 | 17 | m = mock.patch(target, autospec=True) | ||
4659 | 18 | p = m.start() | ||
4660 | 19 | self.addCleanup(m.stop) | ||
4661 | 20 | setattr(self, attr, p) | ||
4662 | 21 | |||
4663 | 22 | |||
4664 | 23 | class TestCurtinVersion(CurtinVersionBase): | ||
4665 | 24 | |||
4666 | 25 | def setUp(self): | ||
4674 | 26 | self.add_patch('subprocess.check_output', 'mock_subp') | 14 | self.add_patch('subprocess.check_output', 'mock_subp') |
4675 | 27 | self.add_patch('os.path', 'mock_path') | 15 | self.add_patch('os.path', 'mock_path') |
4676 | 28 | 16 | ||
4677 | 29 | 17 | ||
4678 | === modified file 'tests/vmtests/__init__.py' | |||
4679 | --- tests/vmtests/__init__.py 2017-06-12 20:39:06 +0000 | |||
4680 | +++ tests/vmtests/__init__.py 2017-10-06 16:35:22 +0000 | |||
4681 | @@ -38,6 +38,7 @@ | |||
4682 | 38 | CURTIN_VMTEST_IMAGE_SYNC = os.environ.get("CURTIN_VMTEST_IMAGE_SYNC", "1") | 38 | CURTIN_VMTEST_IMAGE_SYNC = os.environ.get("CURTIN_VMTEST_IMAGE_SYNC", "1") |
4683 | 39 | IMAGE_SYNCS = [] | 39 | IMAGE_SYNCS = [] |
4684 | 40 | TARGET_IMAGE_FORMAT = "raw" | 40 | TARGET_IMAGE_FORMAT = "raw" |
4685 | 41 | TAR_DISKS = bool(int(os.environ.get("CURTIN_VMTEST_TAR_DISKS", "0"))) | ||
4686 | 41 | 42 | ||
4687 | 42 | 43 | ||
4688 | 43 | DEFAULT_BRIDGE = os.environ.get("CURTIN_VMTEST_BRIDGE", "user") | 44 | DEFAULT_BRIDGE = os.environ.get("CURTIN_VMTEST_BRIDGE", "user") |
4689 | @@ -335,7 +336,12 @@ | |||
4690 | 335 | __test__ = False | 336 | __test__ = False |
4691 | 336 | arch_skip = [] | 337 | arch_skip = [] |
4692 | 337 | boot_timeout = BOOT_TIMEOUT | 338 | boot_timeout = BOOT_TIMEOUT |
4694 | 338 | collect_scripts = [] | 339 | collect_scripts = [textwrap.dedent(""" |
4695 | 340 | cd OUTPUT_COLLECT_D | ||
4696 | 341 | dpkg-query --show \ | ||
4697 | 342 | --showformat='${db:Status-Abbrev}\t${Package}\t${Version}\n' \ | ||
4698 | 343 | > debian-packages.txt 2> debian-packages.txt.err | ||
4699 | 344 | """)] | ||
4700 | 339 | conf_file = "examples/tests/basic.yaml" | 345 | conf_file = "examples/tests/basic.yaml" |
4701 | 340 | nr_cpus = None | 346 | nr_cpus = None |
4702 | 341 | dirty_disks = False | 347 | dirty_disks = False |
4703 | @@ -368,6 +374,8 @@ | |||
4704 | 368 | target_krel = None | 374 | target_krel = None |
4705 | 369 | target_ftype = "vmtest.root-tgz" | 375 | target_ftype = "vmtest.root-tgz" |
4706 | 370 | 376 | ||
4707 | 377 | _debian_packages = None | ||
4708 | 378 | |||
4709 | 371 | def shortDescription(self): | 379 | def shortDescription(self): |
4710 | 372 | return None | 380 | return None |
4711 | 373 | 381 | ||
4712 | @@ -593,7 +601,7 @@ | |||
4713 | 593 | logger.debug("Interface name: {}".format(ifname)) | 601 | logger.debug("Interface name: {}".format(ifname)) |
4714 | 594 | iface = interfaces.get(ifname) | 602 | iface = interfaces.get(ifname) |
4715 | 595 | hwaddr = iface.get('mac_address') | 603 | hwaddr = iface.get('mac_address') |
4717 | 596 | if hwaddr: | 604 | if iface['type'] == 'physical' and hwaddr: |
4718 | 597 | macs.append(hwaddr) | 605 | macs.append(hwaddr) |
4719 | 598 | netdevs = [] | 606 | netdevs = [] |
4720 | 599 | if len(macs) > 0: | 607 | if len(macs) > 0: |
4721 | @@ -685,6 +693,12 @@ | |||
4722 | 685 | configs.append(excfg) | 693 | configs.append(excfg) |
4723 | 686 | logger.debug('Added extra config {}'.format(excfg)) | 694 | logger.debug('Added extra config {}'.format(excfg)) |
4724 | 687 | 695 | ||
4725 | 696 | if cls.target_distro == "centos": | ||
4726 | 697 | centos_default = 'examples/tests/centos_defaults.yaml' | ||
4727 | 698 | configs.append(centos_default) | ||
4728 | 699 | logger.info('Detected centos, adding default config %s', | ||
4729 | 700 | centos_default) | ||
4730 | 701 | |||
4731 | 688 | if cls.multipath: | 702 | if cls.multipath: |
4732 | 689 | disks = disks * cls.multipath_num_paths | 703 | disks = disks * cls.multipath_num_paths |
4733 | 690 | 704 | ||
4734 | @@ -871,8 +885,11 @@ | |||
4735 | 871 | raise | 885 | raise |
4736 | 872 | 886 | ||
4737 | 873 | # capture curtin install log and webhook timings | 887 | # capture curtin install log and webhook timings |
4740 | 874 | util.subp(["tools/curtin-log-print", "--dumpfiles", cls.td.logs, | 888 | try: |
4741 | 875 | cls.reporting_log], capture=True) | 889 | util.subp(["tools/curtin-log-print", "--dumpfiles", cls.td.logs, |
4742 | 890 | cls.reporting_log], capture=True) | ||
4743 | 891 | except util.ProcessExecutionError as error: | ||
4744 | 892 | logger.debug('tools/curtin-log-print failed: %s', error) | ||
4745 | 876 | 893 | ||
4746 | 877 | logger.info( | 894 | logger.info( |
4747 | 878 | "%s: setUpClass finished. took %.02f seconds. Running testcases.", | 895 | "%s: setUpClass finished. took %.02f seconds. Running testcases.", |
4748 | @@ -929,7 +946,8 @@ | |||
4749 | 929 | clean_working_dir(cls.td.tmpdir, success, | 946 | clean_working_dir(cls.td.tmpdir, success, |
4750 | 930 | keep_pass=KEEP_DATA['pass'], | 947 | keep_pass=KEEP_DATA['pass'], |
4751 | 931 | keep_fail=KEEP_DATA['fail']) | 948 | keep_fail=KEEP_DATA['fail']) |
4753 | 932 | 949 | if TAR_DISKS: | |
4754 | 950 | tar_disks(cls.td.tmpdir) | ||
4755 | 933 | cls.cleanIscsiState(success, | 951 | cls.cleanIscsiState(success, |
4756 | 934 | keep_pass=KEEP_DATA['pass'], | 952 | keep_pass=KEEP_DATA['pass'], |
4757 | 935 | keep_fail=KEEP_DATA['fail']) | 953 | keep_fail=KEEP_DATA['fail']) |
4758 | @@ -1143,6 +1161,18 @@ | |||
4759 | 1143 | fp.write(json.dumps(data, indent=2, sort_keys=True, | 1161 | fp.write(json.dumps(data, indent=2, sort_keys=True, |
4760 | 1144 | separators=(',', ': ')) + "\n") | 1162 | separators=(',', ': ')) + "\n") |
4761 | 1145 | 1163 | ||
4762 | 1164 | @property | ||
4763 | 1165 | def debian_packages(self): | ||
4764 | 1166 | if self._debian_packages is None: | ||
4765 | 1167 | data = self.load_collect_file("debian-packages.txt") | ||
4766 | 1168 | pkgs = {} | ||
4767 | 1169 | for line in data.splitlines(): | ||
4768 | 1170 | # lines are <status>\t< | ||
4769 | 1171 | status, pkg, ver = line.split('\t') | ||
4770 | 1172 | pkgs[pkg] = {'status': status, 'version': ver} | ||
4771 | 1173 | self._debian_packages = pkgs | ||
4772 | 1174 | return self._debian_packages | ||
4773 | 1175 | |||
4774 | 1146 | 1176 | ||
4775 | 1147 | class PsuedoVMBaseClass(VMBaseClass): | 1177 | class PsuedoVMBaseClass(VMBaseClass): |
4776 | 1148 | # This mimics much of the VMBaseClass just with faster setUpClass | 1178 | # This mimics much of the VMBaseClass just with faster setUpClass |
4777 | @@ -1332,8 +1362,13 @@ | |||
4778 | 1332 | output_device = '/dev/disk/by-id/virtio-%s' % OUTPUT_DISK_NAME | 1362 | output_device = '/dev/disk/by-id/virtio-%s' % OUTPUT_DISK_NAME |
4779 | 1333 | 1363 | ||
4780 | 1334 | collect_prep = textwrap.dedent("mkdir -p " + output_dir) | 1364 | collect_prep = textwrap.dedent("mkdir -p " + output_dir) |
4783 | 1335 | collect_post = textwrap.dedent( | 1365 | collect_post = textwrap.dedent("""\ |
4784 | 1336 | 'tar -C "%s" -cf "%s" .' % (output_dir, output_device)) | 1366 | cd {output_dir}\n |
4785 | 1367 | # remove any symlinks, but archive information about them. | ||
4786 | 1368 | # %Y target's file type, %P = path, %l = target of symlink | ||
4787 | 1369 | find -type l -printf "%Y\t%P\t%l\n" -delete > symlinks.txt | ||
4788 | 1370 | tar -cf "{output_device}" . | ||
4789 | 1371 | """).format(output_dir=output_dir, output_device=output_device) | ||
4790 | 1337 | 1372 | ||
4791 | 1338 | # copy /root for curtin config and install.log | 1373 | # copy /root for curtin config and install.log |
4792 | 1339 | copy_rootdir = textwrap.dedent("cp -a /root " + output_dir) | 1374 | copy_rootdir = textwrap.dedent("cp -a /root " + output_dir) |
4793 | @@ -1410,6 +1445,23 @@ | |||
4794 | 1410 | KEEP_DATA.update(data) | 1445 | KEEP_DATA.update(data) |
4795 | 1411 | 1446 | ||
4796 | 1412 | 1447 | ||
4797 | 1448 | def tar_disks(tmpdir, outfile="disks.tar", diskmatch=".img"): | ||
4798 | 1449 | """ Tar up files in ``tmpdir``/disks that ends with the pattern supplied""" | ||
4799 | 1450 | |||
4800 | 1451 | disks_dir = os.path.join(tmpdir, "disks") | ||
4801 | 1452 | if os.path.exists(disks_dir): | ||
4802 | 1453 | outfile = os.path.join(disks_dir, outfile) | ||
4803 | 1454 | disks = [os.path.join(disks_dir, disk) for disk in | ||
4804 | 1455 | os.listdir(disks_dir) if disk.endswith(diskmatch)] | ||
4805 | 1456 | cmd = ["tar", "--create", "--file=%s" % outfile, | ||
4806 | 1457 | "--verbose", "--remove-files", "--sparse"] | ||
4807 | 1458 | cmd.extend(disks) | ||
4808 | 1459 | logger.info('Taring %s disks sparsely to %s', len(disks), outfile) | ||
4809 | 1460 | util.subp(cmd, capture=True) | ||
4810 | 1461 | else: | ||
4811 | 1462 | logger.error('Failed to find "disks" dir under tmpdir: %s', tmpdir) | ||
4812 | 1463 | |||
4813 | 1464 | |||
4814 | 1413 | def boot_log_wrap(name, func, cmd, console_log, timeout, purpose): | 1465 | def boot_log_wrap(name, func, cmd, console_log, timeout, purpose): |
4815 | 1414 | logger.debug("%s[%s]: booting with timeout=%s log=%s cmd: %s", | 1466 | logger.debug("%s[%s]: booting with timeout=%s log=%s cmd: %s", |
4816 | 1415 | name, purpose, timeout, console_log, ' '.join(cmd)) | 1467 | name, purpose, timeout, console_log, ' '.join(cmd)) |
4817 | 1416 | 1468 | ||
4818 | === modified file 'tests/vmtests/releases.py' | |||
4819 | --- tests/vmtests/releases.py 2017-06-12 20:39:06 +0000 | |||
4820 | +++ tests/vmtests/releases.py 2017-10-06 16:35:22 +0000 | |||
4821 | @@ -77,22 +77,10 @@ | |||
4822 | 77 | target_release = "trusty" | 77 | target_release = "trusty" |
4823 | 78 | 78 | ||
4824 | 79 | 79 | ||
4825 | 80 | class _VividBase(_UbuntuBase): | ||
4826 | 81 | release = "vivid" | ||
4827 | 82 | |||
4828 | 83 | |||
4829 | 84 | class _WilyBase(_UbuntuBase): | ||
4830 | 85 | release = "wily" | ||
4831 | 86 | |||
4832 | 87 | |||
4833 | 88 | class _XenialBase(_UbuntuBase): | 80 | class _XenialBase(_UbuntuBase): |
4834 | 89 | release = "xenial" | 81 | release = "xenial" |
4835 | 90 | 82 | ||
4836 | 91 | 83 | ||
4837 | 92 | class _YakketyBase(_UbuntuBase): | ||
4838 | 93 | release = "yakkety" | ||
4839 | 94 | |||
4840 | 95 | |||
4841 | 96 | class _ZestyBase(_UbuntuBase): | 84 | class _ZestyBase(_UbuntuBase): |
4842 | 97 | release = "zesty" | 85 | release = "zesty" |
4843 | 98 | 86 | ||
4844 | @@ -110,10 +98,7 @@ | |||
4845 | 110 | trusty_hwe_w = _TrustyHWEW | 98 | trusty_hwe_w = _TrustyHWEW |
4846 | 111 | trusty_hwe_x = _TrustyHWEX | 99 | trusty_hwe_x = _TrustyHWEX |
4847 | 112 | trustyfromxenial = _TrustyFromXenial | 100 | trustyfromxenial = _TrustyFromXenial |
4848 | 113 | vivid = _VividBase | ||
4849 | 114 | wily = _WilyBase | ||
4850 | 115 | xenial = _XenialBase | 101 | xenial = _XenialBase |
4851 | 116 | yakkety = _YakketyBase | ||
4852 | 117 | zesty = _ZestyBase | 102 | zesty = _ZestyBase |
4853 | 118 | artful = _ArtfulBase | 103 | artful = _ArtfulBase |
4854 | 119 | 104 | ||
4855 | 120 | 105 | ||
4856 | === modified file 'tests/vmtests/test_apt_config_cmd.py' | |||
4857 | --- tests/vmtests/test_apt_config_cmd.py 2017-06-12 20:39:06 +0000 | |||
4858 | +++ tests/vmtests/test_apt_config_cmd.py 2017-10-06 16:35:22 +0000 | |||
4859 | @@ -55,10 +55,6 @@ | |||
4860 | 55 | __test__ = True | 55 | __test__ = True |
4861 | 56 | 56 | ||
4862 | 57 | 57 | ||
4863 | 58 | class YakketyTestAptConfigCMDCMD(relbase.yakkety, TestAptConfigCMD): | ||
4864 | 59 | __test__ = True | ||
4865 | 60 | |||
4866 | 61 | |||
4867 | 62 | class ZestyTestAptConfigCMDCMD(relbase.zesty, TestAptConfigCMD): | 58 | class ZestyTestAptConfigCMDCMD(relbase.zesty, TestAptConfigCMD): |
4868 | 63 | __test__ = True | 59 | __test__ = True |
4869 | 64 | 60 | ||
4870 | 65 | 61 | ||
4871 | === modified file 'tests/vmtests/test_basic.py' | |||
4872 | --- tests/vmtests/test_basic.py 2017-06-12 20:39:06 +0000 | |||
4873 | +++ tests/vmtests/test_basic.py 2017-10-06 16:35:22 +0000 | |||
4874 | @@ -202,19 +202,10 @@ | |||
4875 | 202 | __test__ = True | 202 | __test__ = True |
4876 | 203 | 203 | ||
4877 | 204 | 204 | ||
4878 | 205 | class WilyTestBasic(relbase.wily, TestBasicAbs): | ||
4879 | 206 | # EOL - 2016-07-28 | ||
4880 | 207 | __test__ = False | ||
4881 | 208 | |||
4882 | 209 | |||
4883 | 210 | class XenialTestBasic(relbase.xenial, TestBasicAbs): | 205 | class XenialTestBasic(relbase.xenial, TestBasicAbs): |
4884 | 211 | __test__ = True | 206 | __test__ = True |
4885 | 212 | 207 | ||
4886 | 213 | 208 | ||
4887 | 214 | class YakketyTestBasic(relbase.yakkety, TestBasicAbs): | ||
4888 | 215 | __test__ = True | ||
4889 | 216 | |||
4890 | 217 | |||
4891 | 218 | class ZestyTestBasic(relbase.zesty, TestBasicAbs): | 209 | class ZestyTestBasic(relbase.zesty, TestBasicAbs): |
4892 | 219 | __test__ = True | 210 | __test__ = True |
4893 | 220 | 211 | ||
4894 | @@ -323,10 +314,6 @@ | |||
4895 | 323 | __test__ = True | 314 | __test__ = True |
4896 | 324 | 315 | ||
4897 | 325 | 316 | ||
4898 | 326 | class YakketyTestScsiBasic(relbase.yakkety, TestBasicScsiAbs): | ||
4899 | 327 | __test__ = True | ||
4900 | 328 | |||
4901 | 329 | |||
4902 | 330 | class ZestyTestScsiBasic(relbase.zesty, TestBasicScsiAbs): | 317 | class ZestyTestScsiBasic(relbase.zesty, TestBasicScsiAbs): |
4903 | 331 | __test__ = True | 318 | __test__ = True |
4904 | 332 | 319 | ||
4905 | 333 | 320 | ||
4906 | === modified file 'tests/vmtests/test_bcache_basic.py' | |||
4907 | --- tests/vmtests/test_bcache_basic.py 2017-06-12 20:39:06 +0000 | |||
4908 | +++ tests/vmtests/test_bcache_basic.py 2017-10-06 16:35:22 +0000 | |||
4909 | @@ -59,10 +59,6 @@ | |||
4910 | 59 | __test__ = True | 59 | __test__ = True |
4911 | 60 | 60 | ||
4912 | 61 | 61 | ||
4913 | 62 | class YakketyBcacheBasic(relbase.yakkety, TestBcacheBasic): | ||
4914 | 63 | __test__ = True | ||
4915 | 64 | |||
4916 | 65 | |||
4917 | 66 | class ZestyBcacheBasic(relbase.zesty, TestBcacheBasic): | 62 | class ZestyBcacheBasic(relbase.zesty, TestBcacheBasic): |
4918 | 67 | __test__ = True | 63 | __test__ = True |
4919 | 68 | 64 | ||
4920 | 69 | 65 | ||
4921 | === modified file 'tests/vmtests/test_centos_basic.py' | |||
4922 | --- tests/vmtests/test_centos_basic.py 2017-01-18 16:01:35 +0000 | |||
4923 | +++ tests/vmtests/test_centos_basic.py 2017-10-06 16:35:22 +0000 | |||
4924 | @@ -1,5 +1,6 @@ | |||
4925 | 1 | from . import VMBaseClass | 1 | from . import VMBaseClass |
4926 | 2 | from .releases import centos_base_vm_classes as relbase | 2 | from .releases import centos_base_vm_classes as relbase |
4927 | 3 | from .test_network import TestNetworkBaseTestsAbs | ||
4928 | 3 | 4 | ||
4929 | 4 | import textwrap | 5 | import textwrap |
4930 | 5 | 6 | ||
4931 | @@ -9,10 +10,20 @@ | |||
4932 | 9 | __test__ = False | 10 | __test__ = False |
4933 | 10 | conf_file = "examples/tests/centos_basic.yaml" | 11 | conf_file = "examples/tests/centos_basic.yaml" |
4934 | 11 | extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00" | 12 | extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00" |
4935 | 13 | # XXX: command | tee output is required for Centos under SELinux | ||
4936 | 14 | # http://danwalsh.livejournal.com/22860.html | ||
4937 | 12 | collect_scripts = [textwrap.dedent( | 15 | collect_scripts = [textwrap.dedent( |
4938 | 13 | """ | 16 | """ |
4939 | 14 | cd OUTPUT_COLLECT_D | 17 | cd OUTPUT_COLLECT_D |
4940 | 15 | cat /etc/fstab > fstab | 18 | cat /etc/fstab > fstab |
4941 | 19 | rpm -qa | cat >rpm_qa | ||
4942 | 20 | ifconfig -a | cat >ifconfig_a | ||
4943 | 21 | ip a | cat >ip_a | ||
4944 | 22 | cp -a /etc/sysconfig/network-scripts . | ||
4945 | 23 | cp -a /var/log/messages . | ||
4946 | 24 | cp -a /var/log/cloud-init* . | ||
4947 | 25 | cp -a /var/lib/cloud ./var_lib_cloud | ||
4948 | 26 | cp -a /run/cloud-init ./run_cloud-init | ||
4949 | 16 | """)] | 27 | """)] |
4950 | 17 | fstab_expected = { | 28 | fstab_expected = { |
4951 | 18 | 'LABEL=cloudimg-rootfs': '/', | 29 | 'LABEL=cloudimg-rootfs': '/', |
4952 | @@ -40,3 +51,27 @@ | |||
4953 | 40 | # FIXME: test is disabled because the grub config script in target | 51 | # FIXME: test is disabled because the grub config script in target |
4954 | 41 | # specifies drive using hd(1,0) syntax, which breaks when the | 52 | # specifies drive using hd(1,0) syntax, which breaks when the |
4955 | 42 | # installation medium is removed. other than this, the install works | 53 | # installation medium is removed. other than this, the install works |
4956 | 54 | |||
4957 | 55 | |||
4958 | 56 | class CentosTestBasicNetworkAbs(TestNetworkBaseTestsAbs): | ||
4959 | 57 | conf_file = "examples/tests/centos_basic.yaml" | ||
4960 | 58 | extra_kern_args = "BOOTIF=eth0-52:54:00:12:34:00" | ||
4961 | 59 | collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [ | ||
4962 | 60 | textwrap.dedent(""" | ||
4963 | 61 | cd OUTPUT_COLLECT_D | ||
4964 | 62 | cp -a /etc/sysconfig/network-scripts . | ||
4965 | 63 | cp -a /var/log/cloud-init* . | ||
4966 | 64 | cp -a /var/lib/cloud ./var_lib_cloud | ||
4967 | 65 | cp -a /run/cloud-init ./run_cloud-init | ||
4968 | 66 | """)] | ||
4969 | 67 | |||
4970 | 68 | def test_etc_network_interfaces(self): | ||
4971 | 69 | pass | ||
4972 | 70 | |||
4973 | 71 | def test_etc_resolvconf(self): | ||
4974 | 72 | pass | ||
4975 | 73 | |||
4976 | 74 | |||
4977 | 75 | class Centos70BasicNetworkFromXenialTestBasic(relbase.centos70fromxenial, | ||
4978 | 76 | CentosTestBasicNetworkAbs): | ||
4979 | 77 | __test__ = True | ||
4980 | 43 | 78 | ||
4981 | === modified file 'tests/vmtests/test_iscsi.py' | |||
4982 | --- tests/vmtests/test_iscsi.py 2017-06-12 20:39:06 +0000 | |||
4983 | +++ tests/vmtests/test_iscsi.py 2017-10-06 16:35:22 +0000 | |||
4984 | @@ -59,10 +59,6 @@ | |||
4985 | 59 | __test__ = True | 59 | __test__ = True |
4986 | 60 | 60 | ||
4987 | 61 | 61 | ||
4988 | 62 | class YakketyTestIscsiBasic(relbase.yakkety, TestBasicIscsiAbs): | ||
4989 | 63 | __test__ = True | ||
4990 | 64 | |||
4991 | 65 | |||
4992 | 66 | class ZestyTestIscsiBasic(relbase.zesty, TestBasicIscsiAbs): | 62 | class ZestyTestIscsiBasic(relbase.zesty, TestBasicIscsiAbs): |
4993 | 67 | __test__ = True | 63 | __test__ = True |
4994 | 68 | 64 | ||
4995 | 69 | 65 | ||
4996 | === added file 'tests/vmtests/test_journald_reporter.py' | |||
4997 | --- tests/vmtests/test_journald_reporter.py 1970-01-01 00:00:00 +0000 | |||
4998 | +++ tests/vmtests/test_journald_reporter.py 2017-10-06 16:35:22 +0000 | |||
4999 | @@ -0,0 +1,52 @@ | |||
5000 | 1 | from . import VMBaseClass |
The diff has been truncated for viewing.