Merge lp:~raharper/curtin/trunk.iscsi-stacked into lp:~curtin-dev/curtin/trunk
- trunk.iscsi-stacked
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 499 |
Proposed branch: | lp:~raharper/curtin/trunk.iscsi-stacked |
Merge into: | lp:~curtin-dev/curtin/trunk |
Diff against target: |
926 lines (+692/-57) 13 files modified
curtin/block/__init__.py (+28/-0) curtin/block/iscsi.py (+20/-0) curtin/commands/block_meta.py (+15/-23) curtin/commands/install.py (+2/-2) examples/tests/lvm_iscsi.yaml (+175/-0) examples/tests/mdadm_iscsi.yaml (+87/-0) tests/unittests/test_block.py (+118/-0) tests/unittests/test_block_iscsi.py (+96/-1) tests/vmtests/__init__.py (+10/-6) tests/vmtests/test_iscsi.py (+23/-7) tests/vmtests/test_lvm.py (+9/-18) tests/vmtests/test_lvm_iscsi.py (+67/-0) tests/vmtests/test_mdadm_iscsi.py (+42/-0) |
To merge this branch: | bzr merge lp:~raharper/curtin/trunk.iscsi-stacked |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser (community) | Approve | ||
Review via email: mp+324005@code.launchpad.net |
Commit message
Description of the change
Fix stacked storage configurations with iSCSI
Currently, we fail to correctly handle more complex storage configurations where one or more members of a LVM or MD configuration are on iSCSI. We only check for disks and partitions if they are on iSCSI -- instead, in the mount_handler, check if the to-be-mounted volume (i.e., in the case of a disk or partition) is itself on iSCSI, or if any of the "slaves" of the kname for the to-be-mounted volume (i.e, in the case of LVM or MD) are on iSCSI.
LP: #1683910
Ryan Harper (raharper) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:498
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 499. By Ryan Harper
-
merge from trunk
- 500. By Ryan Harper
-
Add some verbose debugging for kname slaves, restructure stacked iscsi tests
- 501. By Ryan Harper
-
Unmount the devices before unplugging iscsi devices
- 502. By Ryan Harper
-
Check for _netdev within each of the fstab lines
- 503. By Ryan Harper
-
block: slaves_kname: Handle present but empty slaves dir
- 504. By Ryan Harper
-
vmtests: update stacked iscsi testfile configuration, add tunable
- 505. By Ryan Harper
-
Fix lint
- 506. By Ryan Harper
-
Try appending collect_scripts instead of clobbering
- 507. By Ryan Harper
-
Must manually build scripts from parent classes
- 508. By Ryan Harper
-
Rework syntax
- 509. By Ryan Harper
-
Fix up collect scripts for iscsi subclassed tests
- 510. By Ryan Harper
-
Refactor lvm_iscsi, use fs uuids so we can match fstab, skiptest for precise,trusty dname
- 511. By Ryan Harper
-
Fix vg name in lvm_iscsi test
- 512. By Ryan Harper
-
Drop verbose slave kname debugging
Ryan Harper (raharper) wrote : | # |
After failing VMTest run, fix up a few issues and refactor the vmtests.
1) Make sure we run iscsi disconnect *after* install unmounts the target
This is the other critical fix for using iscsi devices.
2) Fix an issue with obtaining slave knames when a device has a 'slaves' sysfs entry but it is empty.
3) Update the iscsi vmtests to check for '_netdev' in fstab
4) Derive the lvm and mdadm iscsi tests from respective parent classes so we confirm that those devices are built as expected as well as ensuring the use of iscsi block devices at the bottom layer works.
Started new vmtest run of iscsi tests:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:512
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
All tests look good, the one failure is ArtfulIscsi which won't pass till jenkins does it's nightly sync-images.
Scott Moser (smoser) wrote : | # |
This looks really good. I'll approve, you are welcome to fix what you want and not what you dont.
I do think the unit tests are more implementation dependent than they really should be.
Ryan Harper (raharper) wrote : | # |
Thanks for the review, will push an update and retest on jenkins.
- 513. By Ryan Harper
-
Switch to append
- 514. By Ryan Harper
-
Change Exception message on invalid input
- 515. By Ryan Harper
-
vmtests: modify output_
files_{ exist,dont_ exist} to check all of the input
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:515
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Preview Diff
1 | === modified file 'curtin/block/__init__.py' |
2 | --- curtin/block/__init__.py 2017-05-04 17:23:42 +0000 |
3 | +++ curtin/block/__init__.py 2017-05-19 00:57:33 +0000 |
4 | @@ -176,6 +176,34 @@ |
5 | return holders |
6 | |
7 | |
8 | +def get_device_slave_knames(device): |
9 | + """ |
10 | + Find the underlying knames of a given device by walking sysfs |
11 | + recursively. |
12 | + |
13 | + Returns a list of knames |
14 | + """ |
15 | + slave_knames = [] |
16 | + slaves_dir_path = os.path.join(sys_block_path(device), 'slaves') |
17 | + |
18 | + # if we find a 'slaves' dir, recurse and check |
19 | + # the underlying devices |
20 | + if os.path.exists(slaves_dir_path): |
21 | + slaves = os.listdir(slaves_dir_path) |
22 | + if len(slaves) > 0: |
23 | + for slave_kname in slaves: |
24 | + slave_knames.extend(get_device_slave_knames(slave_kname)) |
25 | + else: |
26 | + slave_knames.append(path_to_kname(device)) |
27 | + |
28 | + return slave_knames |
29 | + else: |
30 | + # if a device has no 'slaves' attribute then |
31 | + # we've found the underlying device, return |
32 | + # the kname of the device |
33 | + return [path_to_kname(device)] |
34 | + |
35 | + |
36 | def _shlex_split(str_in): |
37 | # shlex.split takes a string |
38 | # but in python2 if input here is a unicode, encode it to a string. |
39 | |
40 | === modified file 'curtin/block/iscsi.py' |
41 | --- curtin/block/iscsi.py 2017-04-04 17:58:52 +0000 |
42 | +++ curtin/block/iscsi.py 2017-05-19 00:57:33 +0000 |
43 | @@ -27,6 +27,9 @@ |
44 | import shutil |
45 | |
46 | from curtin import (util, udev) |
47 | +from curtin.block import (get_device_slave_knames, |
48 | + path_to_kname) |
49 | + |
50 | from curtin.log import LOG |
51 | |
52 | _ISCSI_DISKS = {} |
53 | @@ -272,6 +275,23 @@ |
54 | return False |
55 | |
56 | |
57 | +def volpath_is_iscsi(volume_path): |
58 | + """ Determine if the volume_path's kname is backed by iSCSI. |
59 | + Recursively check volume_path's slave devices as well in |
60 | + case volume_path is a stacked block device (like LVM/MD) |
61 | + |
62 | + returns a boolean |
63 | + """ |
64 | + if not volume_path: |
65 | + raise ValueError("Invalid input for volume_path: '%s'", volume_path) |
66 | + |
67 | + volume_path_slaves = get_device_slave_knames(volume_path) |
68 | + LOG.debug('volume_path=%s found slaves: %s', volume_path, |
69 | + volume_path_slaves) |
70 | + knames = [path_to_kname(volume_path)] + volume_path_slaves |
71 | + return any([kname_is_iscsi(kname) for kname in knames]) |
72 | + |
73 | + |
74 | class IscsiDisk(object): |
75 | # Per Debian bug 804162, the iscsi specifier looks like |
76 | # TARGETSPEC=host:proto:port:lun:targetname |
77 | |
78 | === modified file 'curtin/commands/block_meta.py' |
79 | --- curtin/commands/block_meta.py 2017-04-12 01:38:04 +0000 |
80 | +++ curtin/commands/block_meta.py 2017-05-19 00:57:33 +0000 |
81 | @@ -640,6 +640,19 @@ |
82 | # Mount volume |
83 | util.subp(['mount', volume_path, mount_point]) |
84 | |
85 | + path = "/%s" % path |
86 | + |
87 | + options = ["defaults"] |
88 | + # If the volume_path's kname is backed by iSCSI or (in the case of |
89 | + # LVM/DM) if any of its slaves are backed by iSCSI, then we need to |
90 | + # append _netdev to the fstab line |
91 | + if iscsi.volpath_is_iscsi(volume_path): |
92 | + LOG.debug("Marking volume_path:%s as '_netdev'", volume_path) |
93 | + options.append("_netdev") |
94 | + else: |
95 | + path = "none" |
96 | + options = ["sw"] |
97 | + |
98 | # Add volume to fstab |
99 | if state['fstab']: |
100 | with open(state['fstab'], "a") as fp: |
101 | @@ -649,34 +662,13 @@ |
102 | if len(uuid) > 0: |
103 | location = "UUID=%s" % uuid |
104 | |
105 | - if filesystem.get('fstype') == "swap": |
106 | - path = "none" |
107 | - options = "sw" |
108 | - else: |
109 | - path = "/%s" % path |
110 | - if volume.get('type') == "partition": |
111 | - disk_block_path = get_path_to_storage_volume( |
112 | - volume.get('device'), storage_config) |
113 | - disk_kname = block.path_to_kname(disk_block_path) |
114 | - if iscsi.kname_is_iscsi(disk_kname): |
115 | - options = "_netdev" |
116 | - else: |
117 | - options = "defaults" |
118 | - elif volume.get('type') == "disk": |
119 | - disk_kname = block.path_to_kname(location) |
120 | - if iscsi.kname_is_iscsi(disk_kname): |
121 | - options = "_netdev" |
122 | - else: |
123 | - options = "defaults" |
124 | - else: |
125 | - options = "defaults" |
126 | - |
127 | if filesystem.get('fstype') in ["fat", "fat12", "fat16", "fat32", |
128 | "fat64"]: |
129 | fstype = "vfat" |
130 | else: |
131 | fstype = filesystem.get('fstype') |
132 | - fp.write("%s %s %s %s 0 0\n" % (location, path, fstype, options)) |
133 | + fp.write("%s %s %s %s 0 0\n" % (location, path, fstype, |
134 | + ",".join(options))) |
135 | else: |
136 | LOG.info("fstab not in environment, so not writing") |
137 | |
138 | |
139 | === modified file 'curtin/commands/install.py' |
140 | --- curtin/commands/install.py 2017-04-20 15:57:21 +0000 |
141 | +++ curtin/commands/install.py 2017-05-19 00:57:33 +0000 |
142 | @@ -452,11 +452,11 @@ |
143 | finally: |
144 | log_target_path = instcfg.get('save_install_log', |
145 | '/root/curtin-install.log') |
146 | - # need to do some processing on iscsi disks to disconnect? |
147 | - iscsi.disconnect_target_disks(workingd.target) |
148 | if log_target_path: |
149 | copy_install_log(logfile, workingd.target, log_target_path) |
150 | util.do_umount(workingd.target, recursive=True) |
151 | + # need to do some processing on iscsi disks to disconnect? |
152 | + iscsi.disconnect_target_disks(workingd.target) |
153 | shutil.rmtree(workingd.top) |
154 | |
155 | apply_power_state(cfg.get('power_state')) |
156 | |
157 | === added file 'examples/tests/lvm_iscsi.yaml' |
158 | --- examples/tests/lvm_iscsi.yaml 1970-01-01 00:00:00 +0000 |
159 | +++ examples/tests/lvm_iscsi.yaml 2017-05-19 00:57:33 +0000 |
160 | @@ -0,0 +1,175 @@ |
161 | +storage: |
162 | + version: 1 |
163 | + config: |
164 | + - id: vdb |
165 | + type: disk |
166 | + ptable: msdos |
167 | + model: QEMU HARDDISK |
168 | + path: /dev/vdb |
169 | + name: main_disk |
170 | + wipe: superblock |
171 | + grub_device: true |
172 | + - id: vdb1 |
173 | + type: partition |
174 | + number: 1 |
175 | + size: 3GB |
176 | + device: vdb |
177 | + flag: boot |
178 | + - id: vdb2 |
179 | + type: partition |
180 | + number: 2 |
181 | + size: 1GB |
182 | + device: vdb |
183 | + - id: vdb1_root |
184 | + type: format |
185 | + fstype: ext4 |
186 | + volume: vdb1 |
187 | + - id: vdb2_home |
188 | + type: format |
189 | + fstype: ext4 |
190 | + volume: vdb2 |
191 | + - id: vdb1_mount |
192 | + type: mount |
193 | + path: / |
194 | + device: vdb1_root |
195 | + - id: vdb2_mount |
196 | + type: mount |
197 | + path: /home |
198 | + device: vdb2_home |
199 | + - id: sda |
200 | + type: disk |
201 | + path: iscsi:__RFC4173__ |
202 | + name: iscsi_disk1 |
203 | + ptable: msdos |
204 | + wipe: superblock |
205 | + - id: sda_extended |
206 | + type: partition |
207 | + size: 5G |
208 | + flag: extended |
209 | + device: sda |
210 | + - id: sda1 |
211 | + type: partition |
212 | + size: 2G |
213 | + flag: logical |
214 | + device: sda |
215 | + - id: sda2 |
216 | + type: partition |
217 | + size: 3G |
218 | + flag: logical |
219 | + device: sda |
220 | + - id: volgroup1 |
221 | + name: vg1 |
222 | + type: lvm_volgroup |
223 | + devices: |
224 | + - sda1 |
225 | + - sda2 |
226 | + - id: lvmpart1 |
227 | + name: lv1 |
228 | + size: 1G |
229 | + type: lvm_partition |
230 | + volgroup: volgroup1 |
231 | + - id: lvmpart2 |
232 | + name: lv2 |
233 | + type: lvm_partition |
234 | + volgroup: volgroup1 |
235 | + - id: lv1_fs |
236 | + name: storage |
237 | + type: format |
238 | + fstype: ext4 |
239 | + volume: lvmpart1 |
240 | + uuid: 6de56115-9500-424b-8151-221b270ec708 |
241 | + - id: lv2_fs |
242 | + name: storage |
243 | + type: format |
244 | + fstype: ext3 |
245 | + volume: lvmpart2 |
246 | + uuid: 9604e4c4-e5ae-40dd-ab1f-940de6b59047 |
247 | + - id: lv1_mount |
248 | + type: mount |
249 | + path: /mnt/iscsi1 |
250 | + device: lv1_fs |
251 | + - id: lv2_mount |
252 | + type: mount |
253 | + path: /mnt/iscsi2 |
254 | + device: lv2_fs |
255 | + - id: sdb |
256 | + type: disk |
257 | + path: iscsi:__RFC4173__ |
258 | + name: iscsi_disk2 |
259 | + ptable: msdos |
260 | + wipe: superblock |
261 | + - id: sdb_extended |
262 | + type: partition |
263 | + size: 4G |
264 | + flag: extended |
265 | + device: sdb |
266 | + - id: sdb1 |
267 | + type: partition |
268 | + size: 2G |
269 | + flag: logical |
270 | + device: sdb |
271 | + - id: sdb2 |
272 | + type: partition |
273 | + size: 2G |
274 | + flag: logical |
275 | + device: sdb |
276 | + - id: volgroup2 |
277 | + name: vg2 |
278 | + type: lvm_volgroup |
279 | + devices: |
280 | + - sdb1 |
281 | + - sdb2 |
282 | + - id: lvmpart3 |
283 | + name: lv3 |
284 | + size: 1G |
285 | + type: lvm_partition |
286 | + volgroup: volgroup2 |
287 | + - id: lvmpart4 |
288 | + name: lv4 |
289 | + type: lvm_partition |
290 | + volgroup: volgroup2 |
291 | + - id: lv3_fs |
292 | + name: storage |
293 | + type: format |
294 | + fstype: ext4 |
295 | + volume: lvmpart3 |
296 | + uuid: 18bec31c-09a8-4a02-91c6-e9bf6efb6fad |
297 | + - id: lv4_fs |
298 | + name: storage |
299 | + type: format |
300 | + fstype: ext3 |
301 | + volume: lvmpart4 |
302 | + uuid: a98f706b-b064-4682-8eb2-6c2c1284060c |
303 | + - id: lv3_mount |
304 | + type: mount |
305 | + path: /mnt/iscsi3 |
306 | + device: lv3_fs |
307 | + - id: lv4_mount |
308 | + type: mount |
309 | + path: /mnt/iscsi4 |
310 | + device: lv4_fs |
311 | +network: |
312 | + version: 1 |
313 | + config: |
314 | + - type: physical |
315 | + name: interface0 |
316 | + mac_address: "52:54:00:12:34:00" |
317 | + subnets: |
318 | + - type: dhcp |
319 | +write_files: |
320 | + f1: |
321 | + path: /mnt/iscsi1/testfile |
322 | + content: "test1" |
323 | + permissions: 0777 |
324 | + f2: |
325 | + path: /mnt/iscsi2/testfile |
326 | + content: "test2" |
327 | + permissions: 0777 |
328 | + f3: |
329 | + path: /mnt/iscsi3/testfile |
330 | + content: "test3" |
331 | + permissions: 0777 |
332 | + f4: |
333 | + path: /mnt/iscsi4/testfile |
334 | + content: "test4" |
335 | + permissions: 0777 |
336 | |
337 | === added file 'examples/tests/mdadm_iscsi.yaml' |
338 | --- examples/tests/mdadm_iscsi.yaml 1970-01-01 00:00:00 +0000 |
339 | +++ examples/tests/mdadm_iscsi.yaml 2017-05-19 00:57:33 +0000 |
340 | @@ -0,0 +1,87 @@ |
341 | +storage: |
342 | + version: 1 |
343 | + config: |
344 | + - id: vdb |
345 | + type: disk |
346 | + ptable: msdos |
347 | + model: QEMU HARDDISK |
348 | + path: /dev/vdb |
349 | + name: main_disk |
350 | + wipe: superblock |
351 | + grub_device: true |
352 | + - id: vdb1 |
353 | + type: partition |
354 | + number: 1 |
355 | + size: 3GB |
356 | + device: vdb |
357 | + flag: boot |
358 | + - id: vdb2 |
359 | + type: partition |
360 | + number: 2 |
361 | + size: 1GB |
362 | + device: vdb |
363 | + - id: vdb1_root |
364 | + type: format |
365 | + fstype: ext4 |
366 | + volume: vdb1 |
367 | + - id: vdb2_home |
368 | + type: format |
369 | + fstype: ext4 |
370 | + volume: vdb2 |
371 | + - id: vdb1_mount |
372 | + type: mount |
373 | + path: / |
374 | + device: vdb1_root |
375 | + - id: vdb2_mount |
376 | + type: mount |
377 | + path: /home |
378 | + device: vdb2_home |
379 | + - id: sda |
380 | + type: disk |
381 | + path: iscsi:__RFC4173__ |
382 | + name: iscsi_disk1 |
383 | + ptable: msdos |
384 | + wipe: superblock |
385 | + - id: sdb |
386 | + type: disk |
387 | + path: iscsi:__RFC4173__ |
388 | + name: iscsi_disk2 |
389 | + ptable: msdos |
390 | + wipe: superblock |
391 | + - id: sdc |
392 | + type: disk |
393 | + path: iscsi:__RFC4173__ |
394 | + name: iscsi_disk3 |
395 | + ptable: msdos |
396 | + wipe: superblock |
397 | + - devices: |
398 | + - sda |
399 | + - sdb |
400 | + - sdc |
401 | + id: md0 |
402 | + name: md0 |
403 | + raidlevel: 5 |
404 | + spare_devices: [] |
405 | + type: raid |
406 | + - fstype: ext4 |
407 | + id: md0_format |
408 | + label: '' |
409 | + type: format |
410 | + volume: md0 |
411 | + - device: md0_format |
412 | + id: md0_mount |
413 | + path: /mnt/iscsi1 |
414 | + type: mount |
415 | +network: |
416 | + version: 1 |
417 | + config: |
418 | + - type: physical |
419 | + name: interface0 |
420 | + mac_address: "52:54:00:12:34:00" |
421 | + subnets: |
422 | + - type: dhcp |
423 | +write_files: |
424 | + f1: |
425 | + path: /mnt/iscsi1/testfile |
426 | + content: "test1" |
427 | + permissions: 0777 |
428 | |
429 | === modified file 'tests/unittests/test_block.py' |
430 | --- tests/unittests/test_block.py 2017-02-08 15:19:42 +0000 |
431 | +++ tests/unittests/test_block.py 2017-05-19 00:57:33 +0000 |
432 | @@ -515,4 +515,122 @@ |
433 | mock_subp.return_value = (out, err) |
434 | block.blkid() |
435 | |
436 | + |
437 | +class TestSlaveKnames(TestCase): |
438 | + def add_patch(self, target, attr, autospec=True): |
439 | + """Patches specified target object and sets it as attr on test |
440 | + instance also schedules cleanup""" |
441 | + m = mock.patch(target, autospec=autospec) |
442 | + p = m.start() |
443 | + self.addCleanup(m.stop) |
444 | + setattr(self, attr, p) |
445 | + |
446 | + def setUp(self): |
447 | + super(TestSlaveKnames, self).setUp() |
448 | + self.add_patch('curtin.block.get_blockdev_for_partition', |
449 | + 'm_blockdev_for_partition') |
450 | + self.add_patch('curtin.block.os.path.exists', |
451 | + 'm_os_path_exists') |
452 | + # trusty-p3 does not like autospec=True for os.listdir |
453 | + self.add_patch('curtin.block.os.listdir', |
454 | + 'm_os_listdir', autospec=False) |
455 | + |
456 | + def _prepare_mocks(self, device, cfg): |
457 | + """ |
458 | + Construct the correct sequence of mocks |
459 | + give a mapping of device and slaves |
460 | + |
461 | + cfg = { |
462 | + 'wark': ['foo', 'bar'], |
463 | + 'foo': [], |
464 | + 'bar': [], |
465 | + } |
466 | + device = 'wark', slaves = ['foo, 'bar'] |
467 | + |
468 | + cfg = { |
469 | + 'wark': ['foo', 'bar'], |
470 | + 'foo': ['zip'], |
471 | + 'bar': [], |
472 | + 'zip': [] |
473 | + } |
474 | + device = 'wark', slaves = ['zip', 'bar'] |
475 | + """ |
476 | + # kname side-effect mapping |
477 | + parts = [(k, None) for k in cfg.keys()] |
478 | + self.m_blockdev_for_partition.side_effect = iter(parts) |
479 | + |
480 | + # construct side effects to os.path.exists |
481 | + # and os.listdir based on mapping. |
482 | + dirs = [] |
483 | + exists = [] |
484 | + for (dev, slvs) in cfg.items(): |
485 | + # sys_block_dev checks if dev exists |
486 | + exists.append(True) |
487 | + if slvs: |
488 | + # os.path.exists on slaves dir |
489 | + exists.append(True) |
490 | + # result of os.listdir |
491 | + dirs.append(slvs) |
492 | + else: |
493 | + # os.path.exists on slaves dir |
494 | + exists.append(False) |
495 | + |
496 | + self.m_os_path_exists.side_effect = iter(exists) |
497 | + self.m_os_listdir.side_effect = iter(dirs) |
498 | + |
499 | + def test_get_device_slave_knames(self): |
500 | + # |
501 | + # /sys/class/block/wark/slaves/foo -> ../../foo |
502 | + # /sys/class/block/foo # |
503 | + # should return 'bar' |
504 | + cfg = OrderedDict([ |
505 | + ('wark', ['foo']), |
506 | + ('foo', []), |
507 | + ]) |
508 | + device = "/dev/wark" |
509 | + slaves = ["foo"] |
510 | + self._prepare_mocks(device, cfg) |
511 | + knames = block.get_device_slave_knames(device) |
512 | + self.assertEqual(slaves, knames) |
513 | + |
514 | + def test_get_device_slave_knames_stacked(self): |
515 | + # |
516 | + # /sys/class/block/wark/slaves/foo -> ../../foo |
517 | + # /sys/class/block/wark/slaves/bar -> ../../bar |
518 | + # /sys/class/block/foo |
519 | + # /sys/class/block/bar |
520 | + # |
521 | + # should return ['foo', 'bar'] |
522 | + cfg = OrderedDict([ |
523 | + ('wark', ['foo', 'bar']), |
524 | + ('foo', []), |
525 | + ('bar', []), |
526 | + ]) |
527 | + device = 'wark' |
528 | + slaves = ['foo', 'bar'] |
529 | + self._prepare_mocks(device, cfg) |
530 | + knames = block.get_device_slave_knames(device) |
531 | + self.assertEqual(slaves, knames) |
532 | + |
533 | + def test_get_device_slave_knames_double_stacked(self): |
534 | + # /sys/class/block/wark/slaves/foo -> ../../foo |
535 | + # /sys/class/block/wark/slaves/bar -> ../../bar |
536 | + # /sys/class/block/foo |
537 | + # /sys/class/block/bar/slaves/zip -> ../../zip |
538 | + # /sys/class/block/zip |
539 | + # |
540 | + # mapping of device: |
541 | + cfg = OrderedDict([ |
542 | + ('wark', ['foo', 'bar']), |
543 | + ('foo', []), |
544 | + ('bar', ['zip']), |
545 | + ('zip', []), |
546 | + ]) |
547 | + device = 'wark' |
548 | + slaves = ['foo', 'zip'] |
549 | + self._prepare_mocks(device, cfg) |
550 | + knames = block.get_device_slave_knames(device) |
551 | + self.assertEqual(slaves, knames) |
552 | + |
553 | + |
554 | # vi: ts=4 expandtab syntax=python |
555 | |
556 | === modified file 'tests/unittests/test_block_iscsi.py' |
557 | --- tests/unittests/test_block_iscsi.py 2017-04-04 17:58:52 +0000 |
558 | +++ tests/unittests/test_block_iscsi.py 2017-05-19 00:57:33 +0000 |
559 | @@ -1,8 +1,23 @@ |
560 | +import mock |
561 | + |
562 | from unittest import TestCase |
563 | from curtin.block import iscsi |
564 | |
565 | |
566 | -class TestBlockIscsiPortalParsing(TestCase): |
567 | +class IscsiTestBase(TestCase): |
568 | + def setUp(self): |
569 | + super(IscsiTestBase, self).setUp() |
570 | + |
571 | + def add_patch(self, target, attr): |
572 | + """Patches specified target object and sets it as attr on test |
573 | + instance also schedules cleanup""" |
574 | + m = mock.patch(target, autospec=True) |
575 | + p = m.start() |
576 | + self.addCleanup(m.stop) |
577 | + setattr(self, attr, p) |
578 | + |
579 | + |
580 | +class TestBlockIscsiPortalParsing(IscsiTestBase): |
581 | def test_iscsi_portal_parsing_string(self): |
582 | with self.assertRaisesRegexp(ValueError, 'not a string'): |
583 | iscsi.assert_valid_iscsi_portal(1234) |
584 | @@ -474,4 +489,84 @@ |
585 | self.assertEquals(i.lun, 0) |
586 | self.assertEquals(i.target, 'iqn.2017-04.com.example.test:target-name') |
587 | |
588 | + |
589 | +class TestBlockIscsiVolPath(IscsiTestBase): |
590 | + # non-iscsi backed disk returns false |
591 | + # regular iscsi-backed disk returns true |
592 | + # layered setup without an iscsi member returns false |
593 | + # layered setup with an iscsi member returns true |
594 | + |
595 | + def setUp(self): |
596 | + super(TestBlockIscsiVolPath, self).setUp() |
597 | + self.add_patch('curtin.block.iscsi.get_device_slave_knames', |
598 | + 'mock_get_device_slave_knames') |
599 | + self.add_patch('curtin.block.iscsi.path_to_kname', |
600 | + 'mock_path_to_kname') |
601 | + self.add_patch('curtin.block.iscsi.kname_is_iscsi', |
602 | + 'mock_kname_is_iscsi') |
603 | + |
604 | + def test_volpath_is_iscsi_false(self): |
605 | + volume_path = '/dev/wark' |
606 | + kname = 'wark' |
607 | + slaves = [] |
608 | + self.mock_get_device_slave_knames.return_value = slaves |
609 | + self.mock_path_to_kname.return_value = kname |
610 | + self.mock_kname_is_iscsi.return_value = 'iscsi' in kname |
611 | + |
612 | + is_iscsi = iscsi.volpath_is_iscsi(volume_path) |
613 | + |
614 | + self.assertFalse(is_iscsi) |
615 | + self.mock_get_device_slave_knames.assert_called_with(volume_path) |
616 | + self.mock_path_to_kname.assert_called_with(volume_path) |
617 | + self.mock_kname_is_iscsi.assert_called_with(kname) |
618 | + |
619 | + def test_volpath_is_iscsi_true(self): |
620 | + volume_path = '/dev/wark' |
621 | + kname = 'wark-iscsi-lun-2' |
622 | + slaves = [] |
623 | + self.mock_get_device_slave_knames.return_value = slaves |
624 | + self.mock_path_to_kname.return_value = kname |
625 | + self.mock_kname_is_iscsi.return_value = 'iscsi' in kname |
626 | + |
627 | + is_iscsi = iscsi.volpath_is_iscsi(volume_path) |
628 | + |
629 | + self.assertTrue(is_iscsi) |
630 | + self.mock_get_device_slave_knames.assert_called_with(volume_path) |
631 | + self.mock_path_to_kname.assert_called_with(volume_path) |
632 | + self.mock_kname_is_iscsi.assert_called_with(kname) |
633 | + |
634 | + def test_volpath_is_iscsi_layered_true(self): |
635 | + volume_path = '/dev/wark' |
636 | + slaves = ['wark', 'bzr', 'super-iscsi-lun-27'] |
637 | + self.mock_get_device_slave_knames.return_value = slaves |
638 | + self.mock_path_to_kname.side_effect = lambda x: x |
639 | + self.mock_kname_is_iscsi.side_effect = lambda x: 'iscsi' in x |
640 | + |
641 | + is_iscsi = iscsi.volpath_is_iscsi(volume_path) |
642 | + |
643 | + self.assertTrue(is_iscsi) |
644 | + self.mock_get_device_slave_knames.assert_called_with(volume_path) |
645 | + self.mock_path_to_kname.assert_called_with(volume_path) |
646 | + self.mock_kname_is_iscsi.assert_has_calls([ |
647 | + mock.call(x) for x in slaves]) |
648 | + |
649 | + def test_volpath_is_iscsi_layered_false(self): |
650 | + volume_path = '/dev/wark' |
651 | + slaves = ['wark', 'bzr', 'nvmen27p47'] |
652 | + self.mock_get_device_slave_knames.return_value = slaves |
653 | + self.mock_path_to_kname.side_effect = lambda x: x |
654 | + self.mock_kname_is_iscsi.side_effect = lambda x: 'iscsi' in x |
655 | + |
656 | + is_iscsi = iscsi.volpath_is_iscsi(volume_path) |
657 | + |
658 | + self.assertFalse(is_iscsi) |
659 | + self.mock_get_device_slave_knames.assert_called_with(volume_path) |
660 | + self.mock_path_to_kname.assert_called_with(volume_path) |
661 | + self.mock_kname_is_iscsi.assert_has_calls([ |
662 | + mock.call(x) for x in slaves]) |
663 | + |
664 | + def test_volpath_is_iscsi_missing_param(self): |
665 | + with self.assertRaises(ValueError): |
666 | + iscsi.volpath_is_iscsi(None) |
667 | + |
668 | # vi: ts=4 expandtab syntax=python |
669 | |
670 | === modified file 'tests/vmtests/__init__.py' |
671 | --- tests/vmtests/__init__.py 2017-04-25 20:30:31 +0000 |
672 | +++ tests/vmtests/__init__.py 2017-05-19 00:57:33 +0000 |
673 | @@ -973,14 +973,18 @@ |
674 | |
675 | # Misc functions that are useful for many tests |
676 | def output_files_exist(self, files): |
677 | - for f in files: |
678 | - logger.debug('checking file %s', f) |
679 | - self.assertTrue(os.path.exists(os.path.join(self.td.collect, f))) |
680 | + logger.debug('checking files: %s', files) |
681 | + results = {f: os.path.exists(os.path.join(self.td.collect, f)) |
682 | + for f in files} |
683 | + logger.debug('results: %s', results) |
684 | + self.assertTrue(False not in results.values()) |
685 | |
686 | def output_files_dont_exist(self, files): |
687 | - for f in files: |
688 | - logger.debug('checking file %s', f) |
689 | - self.assertFalse(os.path.exists(os.path.join(self.td.collect, f))) |
690 | + logger.debug('checking files: %s', files) |
691 | + results = {f: os.path.exists(os.path.join(self.td.collect, f)) |
692 | + for f in files} |
693 | + logger.debug('results: %s', results) |
694 | + self.assertTrue(True not in results.values()) |
695 | |
696 | def load_collect_file(self, filename, mode="r"): |
697 | with open(os.path.join(self.td.collect, filename), mode) as fp: |
698 | |
699 | === modified file 'tests/vmtests/test_iscsi.py' |
700 | --- tests/vmtests/test_iscsi.py 2017-04-26 16:14:04 +0000 |
701 | +++ tests/vmtests/test_iscsi.py 2017-05-19 00:57:33 +0000 |
702 | @@ -12,6 +12,7 @@ |
703 | {'size': '5G', 'auth': 'user:passw0rd', 'iauth': 'iuser:ipassw0rd'}, |
704 | {'size': '6G', 'iauth': 'iuser:ipassw0rd'}] |
705 | conf_file = "examples/tests/basic_iscsi.yaml" |
706 | + nr_testfiles = 4 |
707 | |
708 | collect_scripts = [textwrap.dedent( |
709 | """ |
710 | @@ -19,16 +20,31 @@ |
711 | cat /etc/fstab > fstab |
712 | ls /dev/disk/by-dname/ > ls_dname |
713 | find /etc/network/interfaces.d > find_interfacesd |
714 | - cat /mnt/iscsi1/testfile > testfile1 |
715 | - cat /mnt/iscsi2/testfile > testfile2 |
716 | - cat /mnt/iscsi3/testfile > testfile3 |
717 | - cat /mnt/iscsi4/testfile > testfile4 |
718 | + bash -c \ |
719 | + 'for f in /mnt/iscsi*; do cat $f/testfile > testfile${f: -1}; done' |
720 | """)] |
721 | |
722 | - def test_output_files_exist(self): |
723 | + def test_fstab_has_netdev_option(self): |
724 | + self.output_files_exist(["fstab"]) |
725 | + fstab = self.load_collect_file("fstab").strip() |
726 | + self.assertTrue(any(["_netdev" in line |
727 | + for line in fstab.splitlines()])) |
728 | + |
729 | + def test_iscsi_testfiles(self): |
730 | # add check by SN or UUID that the iSCSI disks are attached? |
731 | - self.output_files_exist(["fstab", "testfile1", "testfile2", |
732 | - "testfile3", "testfile4"]) |
733 | + testfiles = ["testfile%s" % t for t in range(1, self.nr_testfiles + 1)] |
734 | + |
735 | + # make sure all required files are present: |
736 | + print('Expecting testfiles: %s' % testfiles) |
737 | + self.output_files_exist(testfiles) |
738 | + |
739 | + for testfile in testfiles: |
740 | + print('checking file content: %s' % testfile) |
741 | + expected_content = "test%s" % testfile[-1] |
742 | + content = self.load_collect_file(testfile).strip() |
743 | + self.assertEqual(expected_content, content, |
744 | + "Checking %s, expected:\n%s\nfound:\n%s" % |
745 | + (testfile, expected_content, content)) |
746 | |
747 | |
748 | class PreciseTestIscsiBasic(relbase.precise, TestBasicIscsiAbs): |
749 | |
750 | === modified file 'tests/vmtests/test_lvm.py' |
751 | --- tests/vmtests/test_lvm.py 2017-04-26 16:14:04 +0000 |
752 | +++ tests/vmtests/test_lvm.py 2017-05-19 00:57:33 +0000 |
753 | @@ -1,5 +1,6 @@ |
754 | from . import VMBaseClass |
755 | from .releases import base_vm_classes as relbase |
756 | +from unittest import SkipTest |
757 | |
758 | import textwrap |
759 | |
760 | @@ -38,16 +39,18 @@ |
761 | self.output_files_exist( |
762 | ["fstab", "ls_dname"]) |
763 | |
764 | + # FIXME(LP: #1523037): dname does not work on precise|trusty, so we cannot |
765 | + # expect sda-part2 to exist in /dev/disk/by-dname as we can on other |
766 | + # releases when dname works on trusty, then we need to re-enable by |
767 | + # removing line. |
768 | + def test_dname(self): |
769 | + if self.release in ['precise', 'trusty']: |
770 | + raise SkipTest("test_dname does not work for %s" % self.release) |
771 | + |
772 | |
773 | class PreciseTestLvm(relbase.precise, TestLvmAbs): |
774 | __test__ = True |
775 | |
776 | - # FIXME(LP: #1523037): dname does not work on trusty, so we cannot expect |
777 | - # sda-part2 to exist in /dev/disk/by-dname as we can on other releases |
778 | - # when dname works on trusty, then we need to re-enable by removing line. |
779 | - def test_dname(self): |
780 | - print("test_dname does not work for Trusty") |
781 | - |
782 | |
783 | class PreciseHWETTestLvm(relbase.precise_hwe_t, PreciseTestLvm): |
784 | __test__ = True |
785 | @@ -56,22 +59,10 @@ |
786 | class TrustyTestLvm(relbase.trusty, TestLvmAbs): |
787 | __test__ = True |
788 | |
789 | - # FIXME(LP: #1523037): dname does not work on trusty, so we cannot expect |
790 | - # sda-part2 to exist in /dev/disk/by-dname as we can on other releases |
791 | - # when dname works on trusty, then we need to re-enable by removing line. |
792 | - def test_dname(self): |
793 | - print("test_dname does not work for Trusty") |
794 | - |
795 | |
796 | class TrustyHWEXTestLvm(relbase.trusty_hwe_x, TestLvmAbs): |
797 | __test__ = True |
798 | |
799 | - # FIXME(LP: #1523037): dname does not work on trusty, so we cannot expect |
800 | - # sda-part2 to exist in /dev/disk/by-dname as we can on other releases |
801 | - # when dname works on trusty, then we need to re-enable by removing line. |
802 | - def test_dname(self): |
803 | - print("test_dname does not work for Trusty") |
804 | - |
805 | |
806 | class WilyTestLvm(relbase.wily, TestLvmAbs): |
807 | # EOL - 2016-07-28 |
808 | |
809 | === added file 'tests/vmtests/test_lvm_iscsi.py' |
810 | --- tests/vmtests/test_lvm_iscsi.py 1970-01-01 00:00:00 +0000 |
811 | +++ tests/vmtests/test_lvm_iscsi.py 2017-05-19 00:57:33 +0000 |
812 | @@ -0,0 +1,67 @@ |
813 | +from .releases import base_vm_classes as relbase |
814 | +from .test_lvm import TestLvmAbs |
815 | +from .test_iscsi import TestBasicIscsiAbs |
816 | + |
817 | +import textwrap |
818 | + |
819 | + |
820 | +class TestLvmIscsiAbs(TestLvmAbs, TestBasicIscsiAbs): |
821 | + interactive = False |
822 | + iscsi_disks = [ |
823 | + {'size': '6G'}, |
824 | + {'size': '5G', 'auth': 'user:passw0rd', 'iauth': 'iuser:ipassw0rd'}] |
825 | + conf_file = "examples/tests/lvm_iscsi.yaml" |
826 | + nr_testfiles = 4 |
827 | + |
828 | + collect_scripts = TestLvmAbs.collect_scripts |
829 | + collect_scripts += TestBasicIscsiAbs.collect_scripts + [textwrap.dedent( |
830 | + """ |
831 | + cd OUTPUT_COLLECT_D |
832 | + ls -al /sys/class/block/dm*/slaves/ > dm_slaves |
833 | + """)] |
834 | + |
835 | + fstab_expected = { |
836 | + 'UUID=6de56115-9500-424b-8151-221b270ec708': '/mnt/iscsi1', |
837 | + 'UUID=9604e4c4-e5ae-40dd-ab1f-940de6b59047': '/mnt/iscsi2', |
838 | + 'UUID=18bec31c-09a8-4a02-91c6-e9bf6efb6fad': '/mnt/iscsi3', |
839 | + 'UUID=a98f706b-b064-4682-8eb2-6c2c1284060c': '/mnt/iscsi4', |
840 | + } |
841 | + disk_to_check = [('main_disk', 1), |
842 | + ('main_disk', 5), |
843 | + ('main_disk', 6), |
844 | + ('vg1-lv1', 0), |
845 | + ('vg1-lv2', 0), |
846 | + ('vg2-lv3', 0), |
847 | + ('vg2-lv4', 0)] |
848 | + |
849 | + def test_lvs(self): |
850 | + self.check_file_strippedline("lvs", "lv1=vg1") |
851 | + self.check_file_strippedline("lvs", "lv2=vg1") |
852 | + self.check_file_strippedline("lvs", "lv3=vg2") |
853 | + self.check_file_strippedline("lvs", "lv4=vg2") |
854 | + |
855 | + def test_pvs(self): |
856 | + self.check_file_strippedline("pvs", "vg1=/dev/sda5") |
857 | + self.check_file_strippedline("pvs", "vg1=/dev/sda6") |
858 | + self.check_file_strippedline("pvs", "vg2=/dev/sdb5") |
859 | + self.check_file_strippedline("pvs", "vg2=/dev/sdb6") |
860 | + |
861 | + |
862 | +class PreciseTestIscsiLvm(relbase.precise, TestLvmIscsiAbs): |
863 | + __test__ = True |
864 | + |
865 | + |
866 | +class TrustyTestIscsiLvm(relbase.trusty, TestLvmIscsiAbs): |
867 | + __test__ = True |
868 | + |
869 | + |
870 | +class XenialTestIscsiLvm(relbase.xenial, TestLvmIscsiAbs): |
871 | + __test__ = True |
872 | + |
873 | + |
874 | +class YakketyTestIscsiLvm(relbase.yakkety, TestLvmIscsiAbs): |
875 | + __test__ = True |
876 | + |
877 | + |
878 | +class ZestyTestIscsiLvm(relbase.zesty, TestLvmIscsiAbs): |
879 | + __test__ = True |
880 | |
881 | === added file 'tests/vmtests/test_mdadm_iscsi.py' |
882 | --- tests/vmtests/test_mdadm_iscsi.py 1970-01-01 00:00:00 +0000 |
883 | +++ tests/vmtests/test_mdadm_iscsi.py 2017-05-19 00:57:33 +0000 |
884 | @@ -0,0 +1,42 @@ |
885 | +from .releases import base_vm_classes as relbase |
886 | +from .test_mdadm_bcache import TestMdadmAbs |
887 | +from .test_iscsi import TestBasicIscsiAbs |
888 | + |
889 | +import textwrap |
890 | + |
891 | + |
892 | +class TestMdadmIscsiAbs(TestMdadmAbs, TestBasicIscsiAbs): |
893 | + interactive = False |
894 | + iscsi_disks = [ |
895 | + {'size': '5G', 'auth': 'user:passw0rd'}, |
896 | + {'size': '5G', 'auth': 'user:passw0rd', 'iauth': 'iuser:ipassw0rd'}, |
897 | + {'size': '5G', 'iauth': 'iuser:ipassw0rd'}] |
898 | + conf_file = "examples/tests/mdadm_iscsi.yaml" |
899 | + nr_testfiles = 1 |
900 | + |
901 | + collect_scripts = TestMdadmAbs.collect_scripts |
902 | + collect_scripts += TestBasicIscsiAbs.collect_scripts + [textwrap.dedent( |
903 | + """ |
904 | + cd OUTPUT_COLLECT_D |
905 | + ls -al /sys/class/block/md*/slaves/ > md_slaves |
906 | + """)] |
907 | + |
908 | + |
909 | +class PreciseTestIscsiMdadm(relbase.precise, TestMdadmIscsiAbs): |
910 | + __test__ = True |
911 | + |
912 | + |
913 | +class TrustyTestIscsiMdadm(relbase.trusty, TestMdadmIscsiAbs): |
914 | + __test__ = True |
915 | + |
916 | + |
917 | +class XenialTestIscsiMdadm(relbase.xenial, TestMdadmIscsiAbs): |
918 | + __test__ = True |
919 | + |
920 | + |
921 | +class YakketyTestIscsiMdadm(relbase.yakkety, TestMdadmIscsiAbs): |
922 | + __test__ = True |
923 | + |
924 | + |
925 | +class ZestyTestIscsiMdadm(relbase.zesty, TestMdadmIscsiAbs): |
926 | + __test__ = True |
I've pulled Nish's branch, refactored how we determine slave knames, in particular adding support for arbitrary level of nested block devices (vs. just one level deep) which allows us to find an iscsi device under a bcache used in a raid device with lvm's on top, etc...