6
from curtin.block import clear_holders
7
from .helpers import CiTestCase
10
class TestClearHolders(CiTestCase):
11
test_blockdev = '/dev/null'
12
test_syspath = '/sys/class/block/null'
13
remove_retries = [0.2] * 150 # clear_holders defaults to 30 seconds
14
example_holders_trees = [
15
[{'device': '/sys/class/block/sda', 'name': 'sda', 'holders':
16
[{'device': '/sys/class/block/sda/sda1', 'name': 'sda1',
17
'holders': [], 'dev_type': 'partition'},
18
{'device': '/sys/class/block/sda/sda2', 'name': 'sda2',
19
'holders': [], 'dev_type': 'partition'},
20
{'device': '/sys/class/block/sda/sda5', 'name': 'sda5', 'holders':
21
[{'device': '/sys/class/block/dm-0', 'name': 'dm-0', 'holders':
22
[{'device': '/sys/class/block/dm-1', 'name': 'dm-1',
23
'holders': [], 'dev_type': 'lvm'},
24
{'device': '/sys/class/block/dm-2', 'name': 'dm-2', 'holders':
25
[{'device': '/sys/class/block/dm-3', 'name': 'dm-3',
26
'holders': [], 'dev_type': 'crypt'}],
28
'dev_type': 'crypt'}],
29
'dev_type': 'partition'}],
31
[{"device": "/sys/class/block/vdb", 'name': 'vdb', "holders":
32
[{"device": "/sys/class/block/vdb/vdb1", 'name': 'vdb1',
33
"holders": [], "dev_type": "partition"},
34
{"device": "/sys/class/block/vdb/vdb2", 'name': 'vdb2',
35
"holders": [], "dev_type": "partition"},
36
{"device": "/sys/class/block/vdb/vdb3", 'name': 'vdb3', "holders":
37
[{"device": "/sys/class/block/md0", 'name': 'md0', "holders":
38
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
39
"holders": [], "dev_type": "bcache"}],
41
"dev_type": "partition"},
42
{"device": "/sys/class/block/vdb/vdb4", 'name': 'vdb4', "holders":
43
[{"device": "/sys/class/block/md0", 'name': 'md0', "holders":
44
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
45
"holders": [], "dev_type": "bcache"}],
47
"dev_type": "partition"},
48
{"device": "/sys/class/block/vdb/vdb5", 'name': 'vdb5', "holders":
49
[{"device": "/sys/class/block/md0", 'name': 'md0', "holders":
50
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
51
"holders": [], "dev_type": "bcache"}],
53
"dev_type": "partition"},
54
{"device": "/sys/class/block/vdb/vdb6", 'name': 'vdb6', "holders":
55
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
56
"holders": [], "dev_type": "bcache"},
57
{"device": "/sys/class/block/bcache2", 'name': 'bcache2',
58
"holders": [], "dev_type": "bcache"}],
59
"dev_type": "partition"},
60
{"device": "/sys/class/block/vdb/vdb7", 'name': 'vdb7', "holders":
61
[{"device": "/sys/class/block/bcache2", 'name': 'bcache2',
62
"holders": [], "dev_type": "bcache"}],
63
"dev_type": "partition"},
64
{"device": "/sys/class/block/vdb/vdb8", 'name': 'vdb8',
65
"holders": [], "dev_type": "partition"}],
67
{"device": "/sys/class/block/vdc", 'name': 'vdc', "holders": [],
69
{"device": "/sys/class/block/vdd", 'name': 'vdd', "holders":
70
[{"device": "/sys/class/block/vdd/vdd1", 'name': 'vdd1',
71
"holders": [], "dev_type": "partition"}],
75
@mock.patch('curtin.block.clear_holders.block')
76
@mock.patch('curtin.block.clear_holders.util')
77
def test_get_dmsetup_uuid(self, mock_util, mock_block):
78
"""ensure that clear_holders.get_dmsetup_uuid works as expected"""
79
uuid = "CRYPT-LUKS1-fe335a74374e4649af9776c1699676f8-sdb5_crypt"
80
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
81
mock_util.subp.return_value = (' ' + uuid + '\n', None)
82
res = clear_holders.get_dmsetup_uuid(self.test_syspath)
83
mock_util.subp.assert_called_with(
84
['dmsetup', 'info', self.test_blockdev, '-C', '-o',
85
'uuid', '--noheadings'], capture=True)
86
self.assertEqual(res, uuid)
87
mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
89
@mock.patch('curtin.block.clear_holders.block')
90
@mock.patch('curtin.block.clear_holders.os')
91
def test_get_bcache_using_dev(self, mock_os, mock_block):
92
"""Ensure that get_bcache_using_dev works"""
93
fake_bcache = '/sys/fs/bcache/fake'
94
mock_os.path.join.side_effect = os.path.join
95
mock_block.sys_block_path.return_value = self.test_syspath
96
mock_os.path.realpath.return_value = fake_bcache
98
bcache_dir = clear_holders.get_bcache_using_dev(self.test_blockdev)
99
mock_os.path.realpath.assert_called_with(self.test_syspath +
101
self.assertEqual(bcache_dir, fake_bcache)
103
@mock.patch('curtin.block.clear_holders.os')
104
@mock.patch('curtin.block.clear_holders.block')
105
def test_get_bcache_sys_path(self, mock_block, mock_os):
106
fake_backing = '/sys/class/block/fake'
107
mock_block.sys_block_path.return_value = fake_backing
108
mock_os.path.join.side_effect = os.path.join
109
mock_os.path.exists.return_value = True
110
bcache_dir = clear_holders.get_bcache_sys_path("/dev/fake")
111
self.assertEqual(bcache_dir, fake_backing + "/bcache")
113
@mock.patch('curtin.block.clear_holders.get_dmsetup_uuid')
114
@mock.patch('curtin.block.clear_holders.block')
115
def test_differentiate_lvm_and_crypt(
116
self, mock_block, mock_get_dmsetup_uuid):
117
"""test clear_holders.identify_lvm and clear_holders.identify_crypt"""
118
for (kname, dm_uuid, is_lvm, is_crypt) in [
119
('dm-0', 'LVM-abcdefg', True, False),
120
('sda', 'LVM-abcdefg', False, False),
121
('sda', 'CRYPT-abcdefg', False, False),
122
('dm-0', 'CRYPT-abcdefg', False, True),
123
('dm-1', 'invalid', False, False)]:
124
mock_block.path_to_kname.return_value = kname
125
mock_get_dmsetup_uuid.return_value = dm_uuid
127
is_lvm, clear_holders.identify_lvm(self.test_syspath))
129
is_crypt, clear_holders.identify_crypt(self.test_syspath))
130
mock_block.path_to_kname.assert_called_with(self.test_syspath)
131
mock_get_dmsetup_uuid.assert_called_with(self.test_syspath)
133
@mock.patch('curtin.block.clear_holders.udev.udevadm_settle')
134
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
135
@mock.patch('curtin.block.clear_holders.util')
136
@mock.patch('curtin.block.clear_holders.os')
137
@mock.patch('curtin.block.clear_holders.LOG')
138
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
139
def test_shutdown_bcache(self, mock_get_bcache, mock_log, mock_os,
140
mock_util, mock_get_bcache_block,
141
mock_udevadm_settle):
142
"""test clear_holders.shutdown_bcache"""
144
# pass in a sysfs path to a bcache block device,
145
# determine the bcache cset it is part of (or not)
146
# 1) stop the cset device (if it's enabled)
147
# 2) wait on cset to be removed if it was present
148
# 3) stop the block device (if it's still present after stopping cset)
149
# 4) wait on bcache block device to be removed
152
device = self.test_syspath
153
bcache_cset_uuid = 'c08ae789-a964-46fb-a66e-650f0ae78f94'
155
mock_os.path.exists.return_value = True
156
mock_os.path.join.side_effect = os.path.join
157
# os.path.realpath on symlink of /sys/class/block/null/bcache/cache ->
158
# to /sys/fs/bcache/cset_UUID
159
mock_get_bcache.return_value = '/sys/fs/bcache/' + bcache_cset_uuid
160
mock_get_bcache_block.return_value = device + '/bcache'
162
clear_holders.shutdown_bcache(device)
164
mock_get_bcache.assert_called_with(device, strict=False)
165
mock_get_bcache_block.assert_called_with(device, strict=False)
167
self.assertTrue(mock_log.info.called)
168
self.assertFalse(mock_log.warn.called)
169
mock_util.wait_for_removal.assert_has_calls([
170
mock.call('/sys/fs/bcache/' + bcache_cset_uuid,
171
retries=self.remove_retries),
172
mock.call(device, retries=self.remove_retries)])
174
mock_util.write_file.assert_has_calls([
175
mock.call('/sys/fs/bcache/%s/stop' % bcache_cset_uuid,
177
mock.call(device + '/bcache/stop',
180
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
181
@mock.patch('curtin.block.clear_holders.util')
182
@mock.patch('curtin.block.clear_holders.os')
183
@mock.patch('curtin.block.clear_holders.LOG')
184
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
185
def test_shutdown_bcache_non_sysfs_device(self, mock_get_bcache, mock_log,
187
mock_get_bcache_block):
188
device = "/dev/fakenull"
189
with self.assertRaises(ValueError):
190
clear_holders.shutdown_bcache(device)
192
self.assertEqual(0, len(mock_get_bcache.call_args_list))
193
self.assertEqual(0, len(mock_log.call_args_list))
194
self.assertEqual(0, len(mock_os.call_args_list))
195
self.assertEqual(0, len(mock_util.call_args_list))
196
self.assertEqual(0, len(mock_get_bcache_block.call_args_list))
198
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
199
@mock.patch('curtin.block.clear_holders.util')
200
@mock.patch('curtin.block.clear_holders.os')
201
@mock.patch('curtin.block.clear_holders.LOG')
202
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
203
def test_shutdown_bcache_no_device(self, mock_get_bcache, mock_log,
205
mock_get_bcache_block):
206
device = "/sys/class/block/null"
207
mock_os.path.exists.return_value = False
209
clear_holders.shutdown_bcache(device)
211
self.assertEqual(1, len(mock_log.info.call_args_list))
212
self.assertEqual(1, len(mock_os.path.exists.call_args_list))
213
self.assertEqual(0, len(mock_get_bcache.call_args_list))
214
self.assertEqual(0, len(mock_util.call_args_list))
215
self.assertEqual(0, len(mock_get_bcache_block.call_args_list))
217
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
218
@mock.patch('curtin.block.clear_holders.util')
219
@mock.patch('curtin.block.clear_holders.os')
220
@mock.patch('curtin.block.clear_holders.LOG')
221
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
222
def test_shutdown_bcache_no_cset(self, mock_get_bcache, mock_log,
224
mock_get_bcache_block):
225
device = "/sys/class/block/null"
226
mock_os.path.exists.side_effect = iter([
227
True, # backing device exists
228
False, # cset device not present (already removed)
229
True, # backing device (still) exists
231
mock_get_bcache.return_value = '/sys/fs/bcache/fake'
232
mock_get_bcache_block.return_value = device + '/bcache'
233
mock_os.path.join.side_effect = os.path.join
235
clear_holders.shutdown_bcache(device)
237
self.assertEqual(2, len(mock_log.info.call_args_list))
238
self.assertEqual(3, len(mock_os.path.exists.call_args_list))
239
self.assertEqual(1, len(mock_get_bcache.call_args_list))
240
self.assertEqual(1, len(mock_get_bcache_block.call_args_list))
241
self.assertEqual(1, len(mock_util.write_file.call_args_list))
242
self.assertEqual(2, len(mock_util.wait_for_removal.call_args_list))
244
mock_get_bcache.assert_called_with(device, strict=False)
245
mock_get_bcache_block.assert_called_with(device, strict=False)
246
mock_util.write_file.assert_called_with(device + '/bcache/stop',
248
retries = self.remove_retries
249
mock_util.wait_for_removal.assert_has_calls([
250
mock.call(device, retries=retries),
251
mock.call(device + '/bcache', retries=retries)])
253
@mock.patch('curtin.block.clear_holders.udev.udevadm_settle')
254
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
255
@mock.patch('curtin.block.clear_holders.util')
256
@mock.patch('curtin.block.clear_holders.os')
257
@mock.patch('curtin.block.clear_holders.LOG')
258
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
259
def test_shutdown_bcache_delete_cset_and_backing(self, mock_get_bcache,
262
mock_get_bcache_block,
263
mock_udevadm_settle):
264
device = "/sys/class/block/null"
265
mock_os.path.exists.side_effect = iter([
266
True, # backing device exists
267
True, # cset device not present (already removed)
268
True, # backing device (still) exists
270
cset = '/sys/fs/bcache/fake'
271
mock_get_bcache.return_value = cset
272
mock_get_bcache_block.return_value = device + '/bcache'
273
mock_os.path.join.side_effect = os.path.join
275
clear_holders.shutdown_bcache(device)
277
self.assertEqual(2, len(mock_log.info.call_args_list))
278
self.assertEqual(3, len(mock_os.path.exists.call_args_list))
279
self.assertEqual(1, len(mock_get_bcache.call_args_list))
280
self.assertEqual(1, len(mock_get_bcache_block.call_args_list))
281
self.assertEqual(2, len(mock_util.write_file.call_args_list))
282
self.assertEqual(3, len(mock_util.wait_for_removal.call_args_list))
284
mock_get_bcache.assert_called_with(device, strict=False)
285
mock_get_bcache_block.assert_called_with(device, strict=False)
286
mock_util.write_file.assert_has_calls([
287
mock.call(cset + '/stop', '1', mode=None),
288
mock.call(device + '/bcache/stop', '1', mode=None)])
289
mock_util.wait_for_removal.assert_has_calls([
290
mock.call(cset, retries=self.remove_retries),
291
mock.call(device, retries=self.remove_retries)
294
@mock.patch('curtin.block.clear_holders.udev.udevadm_settle')
295
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
296
@mock.patch('curtin.block.clear_holders.util')
297
@mock.patch('curtin.block.clear_holders.os')
298
@mock.patch('curtin.block.clear_holders.LOG')
299
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
300
def test_shutdown_bcache_delete_cset_no_backing(self, mock_get_bcache,
303
mock_get_bcache_block,
304
mock_udevadm_settle):
305
device = "/sys/class/block/null"
306
mock_os.path.exists.side_effect = iter([
307
True, # backing device exists
308
True, # cset device not present (already removed)
309
False, # backing device is removed with cset
311
cset = '/sys/fs/bcache/fake'
312
mock_get_bcache.return_value = cset
313
mock_get_bcache_block.return_value = device + '/bcache'
314
mock_os.path.join.side_effect = os.path.join
316
clear_holders.shutdown_bcache(device)
318
self.assertEqual(2, len(mock_log.info.call_args_list))
319
self.assertEqual(3, len(mock_os.path.exists.call_args_list))
320
self.assertEqual(1, len(mock_get_bcache.call_args_list))
321
self.assertEqual(1, len(mock_get_bcache_block.call_args_list))
322
self.assertEqual(1, len(mock_util.write_file.call_args_list))
323
self.assertEqual(1, len(mock_util.wait_for_removal.call_args_list))
325
mock_get_bcache.assert_called_with(device, strict=False)
326
mock_util.write_file.assert_has_calls([
327
mock.call(cset + '/stop', '1', mode=None),
329
mock_util.wait_for_removal.assert_has_calls([
330
mock.call(cset, retries=self.remove_retries)
333
# test bcache shutdown with 'stop' sysfs write failure
334
@mock.patch('curtin.block.clear_holders.udev.udevadm_settle')
335
@mock.patch('curtin.block.clear_holders.get_bcache_sys_path')
336
@mock.patch('curtin.block.clear_holders.util')
337
@mock.patch('curtin.block.clear_holders.os')
338
@mock.patch('curtin.block.clear_holders.LOG')
339
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
340
def test_shutdown_bcache_stop_sysfs_write_fails(self, mock_get_bcache,
343
mock_get_bcache_block,
344
mock_udevadm_settle):
345
"""Test writes sysfs write failures pass if file not present"""
346
device = "/sys/class/block/null"
347
mock_os.path.exists.side_effect = iter([
348
True, # backing device exists
349
True, # cset device not present (already removed)
350
False, # backing device is removed with cset
351
False, # bcache/stop sysfs is missing (already removed)
353
cset = '/sys/fs/bcache/fake'
354
mock_get_bcache.return_value = cset
355
mock_get_bcache_block.return_value = device + '/bcache'
356
mock_os.path.join.side_effect = os.path.join
358
# make writes to sysfs fail
359
mock_util.write_file.side_effect = IOError(errno.ENOENT,
362
clear_holders.shutdown_bcache(device)
364
self.assertEqual(2, len(mock_log.info.call_args_list))
365
self.assertEqual(3, len(mock_os.path.exists.call_args_list))
366
self.assertEqual(1, len(mock_get_bcache.call_args_list))
367
self.assertEqual(1, len(mock_get_bcache_block.call_args_list))
368
self.assertEqual(1, len(mock_util.write_file.call_args_list))
369
self.assertEqual(1, len(mock_util.wait_for_removal.call_args_list))
371
mock_get_bcache.assert_called_with(device, strict=False)
372
mock_util.write_file.assert_has_calls([
373
mock.call(cset + '/stop', '1', mode=None),
375
mock_util.wait_for_removal.assert_has_calls([
376
mock.call(cset, retries=self.remove_retries)
379
@mock.patch('curtin.block.clear_holders.LOG')
380
@mock.patch('curtin.block.clear_holders.block.sys_block_path')
381
@mock.patch('curtin.block.clear_holders.lvm')
382
@mock.patch('curtin.block.clear_holders.util')
383
def test_shutdown_lvm(self, mock_util, mock_lvm, mock_syspath, mock_log):
384
"""test clear_holders.shutdown_lvm"""
385
vg_name = 'volgroup1'
387
mock_syspath.return_value = self.test_blockdev
388
mock_util.load_file.return_value = '-'.join((vg_name, lv_name))
389
mock_lvm.split_lvm_name.return_value = (vg_name, lv_name)
390
mock_lvm.get_lvols_in_volgroup.return_value = ['lvol2']
391
clear_holders.shutdown_lvm(self.test_blockdev)
392
mock_syspath.assert_called_with(self.test_blockdev)
393
mock_util.load_file.assert_called_with(self.test_blockdev + '/dm/name')
394
mock_lvm.split_lvm_name.assert_called_with(
395
'-'.join((vg_name, lv_name)))
396
self.assertTrue(mock_log.debug.called)
397
mock_util.subp.assert_called_with(
398
['lvremove', '--force', '--force', '/'.join((vg_name, lv_name))],
400
mock_lvm.get_lvols_in_volgroup.assert_called_with(vg_name)
401
self.assertEqual(len(mock_util.subp.call_args_list), 1)
402
self.assertTrue(mock_lvm.lvm_scan.called)
403
mock_lvm.get_lvols_in_volgroup.return_value = []
404
clear_holders.shutdown_lvm(self.test_blockdev)
405
mock_util.subp.assert_called_with(
406
['vgremove', '--force', '--force', vg_name], rcs=[0, 5])
408
@mock.patch('curtin.block.clear_holders.block')
409
@mock.patch('curtin.block.clear_holders.util')
410
def test_shutdown_crypt(self, mock_util, mock_block):
411
"""test clear_holders.shutdown_crypt"""
412
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
413
clear_holders.shutdown_crypt(self.test_syspath)
414
mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
415
mock_util.subp.assert_called_with(
416
['cryptsetup', 'remove', self.test_blockdev], capture=True)
418
@mock.patch('curtin.block.clear_holders.time')
419
@mock.patch('curtin.block.clear_holders.util')
420
@mock.patch('curtin.block.clear_holders.LOG')
421
@mock.patch('curtin.block.clear_holders.mdadm')
422
@mock.patch('curtin.block.clear_holders.block')
423
def test_shutdown_mdadm(self, mock_block, mock_mdadm, mock_log, mock_util,
425
"""test clear_holders.shutdown_mdadm"""
426
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
427
mock_block.path_to_kname.return_value = self.test_blockdev
428
mock_mdadm.md_present.return_value = False
429
clear_holders.shutdown_mdadm(self.test_syspath)
430
mock_mdadm.mdadm_stop.assert_called_with(self.test_blockdev)
431
mock_mdadm.md_present.assert_called_with(self.test_blockdev)
432
self.assertTrue(mock_log.debug.called)
434
@mock.patch('curtin.block.clear_holders.os')
435
@mock.patch('curtin.block.clear_holders.time')
436
@mock.patch('curtin.block.clear_holders.util')
437
@mock.patch('curtin.block.clear_holders.LOG')
438
@mock.patch('curtin.block.clear_holders.mdadm')
439
@mock.patch('curtin.block.clear_holders.block')
440
def test_shutdown_mdadm_fail_raises_oserror(self, mock_block, mock_mdadm,
441
mock_log, mock_util, mock_time,
443
"""test clear_holders.shutdown_mdadm raises OSError on failure"""
444
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
445
mock_block.path_to_kname.return_value = self.test_blockdev
446
mock_mdadm.md_present.return_value = True
447
mock_util.subp.return_value = ("", "")
448
mock_os.path.exists.return_value = True
450
with self.assertRaises(OSError):
451
clear_holders.shutdown_mdadm(self.test_syspath)
453
mock_mdadm.mdadm_stop.assert_called_with(self.test_blockdev)
454
mock_mdadm.md_present.assert_called_with(self.test_blockdev)
455
mock_util.load_file.assert_called_with('/proc/mdstat')
456
self.assertTrue(mock_log.debug.called)
457
self.assertTrue(mock_log.critical.called)
459
@mock.patch('curtin.block.clear_holders.os')
460
@mock.patch('curtin.block.clear_holders.time')
461
@mock.patch('curtin.block.clear_holders.util')
462
@mock.patch('curtin.block.clear_holders.LOG')
463
@mock.patch('curtin.block.clear_holders.mdadm')
464
@mock.patch('curtin.block.clear_holders.block')
465
def test_shutdown_mdadm_fails_no_proc_mdstat(self, mock_block, mock_mdadm,
468
"""test clear_holders.shutdown_mdadm handles no /proc/mdstat"""
469
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
470
mock_block.path_to_kname.return_value = self.test_blockdev
471
mock_mdadm.md_present.return_value = True
472
mock_os.path.exists.return_value = False
474
with self.assertRaises(OSError):
475
clear_holders.shutdown_mdadm(self.test_syspath)
477
mock_mdadm.mdadm_stop.assert_called_with(self.test_blockdev)
478
mock_mdadm.md_present.assert_called_with(self.test_blockdev)
479
self.assertEqual([], mock_util.subp.call_args_list)
480
self.assertTrue(mock_log.debug.called)
481
self.assertTrue(mock_log.critical.called)
483
@mock.patch('curtin.block.clear_holders.LOG')
484
@mock.patch('curtin.block.clear_holders.block')
485
def test_clear_holders_wipe_superblock(self, mock_block, mock_log):
486
"""test clear_holders.wipe_superblock handles errors right"""
487
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
488
mock_block.is_extended_partition.return_value = True
489
clear_holders.wipe_superblock(self.test_syspath)
490
self.assertFalse(mock_block.wipe_volume.called)
491
mock_block.is_extended_partition.return_value = False
492
clear_holders.wipe_superblock(self.test_syspath)
493
mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
494
mock_block.wipe_volume.assert_called_with(
495
self.test_blockdev, mode='superblock')
497
@mock.patch('curtin.block.clear_holders.LOG')
498
@mock.patch('curtin.block.clear_holders.block')
499
@mock.patch('curtin.block.clear_holders.os')
500
def test_get_holders(self, mock_os, mock_block, mock_log):
501
"""test clear_holders.get_holders"""
502
mock_block.sys_block_path.return_value = self.test_syspath
503
mock_os.path.join.side_effect = os.path.join
504
clear_holders.get_holders(self.test_blockdev)
505
mock_block.sys_block_path.assert_called_with(self.test_blockdev)
506
mock_os.path.join.assert_called_with(self.test_syspath, 'holders')
507
self.assertTrue(mock_log.debug.called)
508
mock_os.listdir.assert_called_with(
509
os.path.join(self.test_syspath, 'holders'))
511
def test_plan_shutdown_holders_trees(self):
513
make sure clear_holdrs.plan_shutdown_holders_tree orders shutdown
514
functions correctly and uses the appropriate shutdown function for each
517
# trees that have been generated, checked for correctness,
518
# and the order that they should be shut down in (by level)
519
test_trees_and_orders = [
520
(self.example_holders_trees[0][0],
521
({'dm-3'}, {'dm-1', 'dm-2'}, {'dm-0'}, {'sda5', 'sda2', 'sda1'},
523
(self.example_holders_trees[1],
524
({'bcache1'}, {'bcache2', 'md0'},
525
{'vdb1', 'vdb2', 'vdb3', 'vdb4', 'vdb5', 'vdb6', 'vdb7', 'vdb8',
527
{'vdb', 'vdc', 'vdd'}))
529
for tree, correct_order in test_trees_and_orders:
530
res = clear_holders.plan_shutdown_holder_trees(tree)
531
for level in correct_order:
532
self.assertEqual({os.path.basename(e['device'])
533
for e in res[:len(level)]}, level)
534
res = res[len(level):]
536
def test_format_holders_tree(self):
537
"""test output of clear_holders.format_holders_tree"""
538
test_trees_and_results = [
539
(self.example_holders_trees[0][0],
550
(self.example_holders_trees[1][0],
571
(self.example_holders_trees[1][1], 'vdc'),
572
(self.example_holders_trees[1][2],
578
for tree, result in test_trees_and_results:
579
self.assertEqual(clear_holders.format_holders_tree(tree), result)
581
@mock.patch('curtin.block.clear_holders.util.write_file')
582
def test_maybe_stop_bcache_device_raises_errors(self, m_write_file):
583
"""Non-IO/OS exceptions are raised by maybe_stop_bcache_device."""
584
m_write_file.side_effect = ValueError('Crazy Value Error')
585
with self.assertRaises(ValueError) as cm:
586
clear_holders.maybe_stop_bcache_device('does/not/matter')
587
self.assertEqual('Crazy Value Error', str(cm.exception))
589
mock.call('does/not/matter/stop', '1', mode=None),
590
m_write_file.call_args)
592
@mock.patch('curtin.block.clear_holders.LOG')
593
@mock.patch('curtin.block.clear_holders.util.write_file')
594
def test_maybe_stop_bcache_device_handles_oserror(self, m_write_file,
596
"""When OSError.NOENT is raised, log the condition and move on."""
597
m_write_file.side_effect = OSError(errno.ENOENT, 'Expected oserror')
598
clear_holders.maybe_stop_bcache_device('does/not/matter')
600
'Error writing to bcache stop file %s, device removed: %s',
601
m_log.debug.call_args[0][0])
602
self.assertEqual('does/not/matter/stop', m_log.debug.call_args[0][1])
604
@mock.patch('curtin.block.clear_holders.LOG')
605
@mock.patch('curtin.block.clear_holders.util.write_file')
606
def test_maybe_stop_bcache_device_handles_ioerror(self, m_write_file,
608
"""When IOError.NOENT is raised, log the condition and move on."""
609
m_write_file.side_effect = IOError(errno.ENOENT, 'Expected ioerror')
610
clear_holders.maybe_stop_bcache_device('does/not/matter')
612
'Error writing to bcache stop file %s, device removed: %s',
613
m_log.debug.call_args[0][0])
614
self.assertEqual('does/not/matter/stop', m_log.debug.call_args[0][1])
616
def test_get_holder_types(self):
617
"""test clear_holders.get_holder_types"""
618
test_trees_and_results = [
619
(self.example_holders_trees[0][0],
620
{('disk', '/sys/class/block/sda'),
621
('partition', '/sys/class/block/sda/sda1'),
622
('partition', '/sys/class/block/sda/sda2'),
623
('partition', '/sys/class/block/sda/sda5'),
624
('crypt', '/sys/class/block/dm-0'),
625
('lvm', '/sys/class/block/dm-1'),
626
('lvm', '/sys/class/block/dm-2'),
627
('crypt', '/sys/class/block/dm-3')}),
628
(self.example_holders_trees[1][0],
629
{('disk', '/sys/class/block/vdb'),
630
('partition', '/sys/class/block/vdb/vdb1'),
631
('partition', '/sys/class/block/vdb/vdb2'),
632
('partition', '/sys/class/block/vdb/vdb3'),
633
('partition', '/sys/class/block/vdb/vdb4'),
634
('partition', '/sys/class/block/vdb/vdb5'),
635
('partition', '/sys/class/block/vdb/vdb6'),
636
('partition', '/sys/class/block/vdb/vdb7'),
637
('partition', '/sys/class/block/vdb/vdb8'),
638
('raid', '/sys/class/block/md0'),
639
('bcache', '/sys/class/block/bcache1'),
640
('bcache', '/sys/class/block/bcache2')})
642
for tree, result in test_trees_and_results:
643
self.assertEqual(clear_holders.get_holder_types(tree), result)
645
@mock.patch('curtin.block.clear_holders.block.sys_block_path')
646
@mock.patch('curtin.block.clear_holders.gen_holders_tree')
647
def test_assert_clear(self, mock_gen_holders_tree, mock_syspath):
648
mock_gen_holders_tree.return_value = self.example_holders_trees[0][0]
649
mock_syspath.side_effect = lambda x: x
651
with self.assertRaises(OSError):
652
clear_holders.assert_clear(device)
653
mock_gen_holders_tree.assert_called_with(device)
654
mock_gen_holders_tree.return_value = self.example_holders_trees[1][1]
655
clear_holders.assert_clear(device)
657
@mock.patch('curtin.block.clear_holders.mdadm')
658
@mock.patch('curtin.block.clear_holders.util')
659
def test_start_clear_holders_deps(self, mock_util, mock_mdadm):
660
clear_holders.start_clear_holders_deps()
661
mock_mdadm.mdadm_assemble.assert_called_with(
662
scan=True, ignore_errors=True)
663
mock_util.subp.assert_called_with(['modprobe', 'bcache'], rcs=[0, 1])