1
from unittest import TestCase
4
from curtin.block import clear_holders
9
class TestClearHolders(TestCase):
10
test_blockdev = '/dev/null'
11
test_syspath = '/sys/class/block/null'
12
example_holders_trees = [
13
[{'device': '/sys/class/block/sda', 'name': 'sda', 'holders':
14
[{'device': '/sys/class/block/sda/sda1', 'name': 'sda1',
15
'holders': [], 'dev_type': 'partition'},
16
{'device': '/sys/class/block/sda/sda2', 'name': 'sda2',
17
'holders': [], 'dev_type': 'partition'},
18
{'device': '/sys/class/block/sda/sda5', 'name': 'sda5', 'holders':
19
[{'device': '/sys/class/block/dm-0', 'name': 'dm-0', 'holders':
20
[{'device': '/sys/class/block/dm-1', 'name': 'dm-1',
21
'holders': [], 'dev_type': 'lvm'},
22
{'device': '/sys/class/block/dm-2', 'name': 'dm-2', 'holders':
23
[{'device': '/sys/class/block/dm-3', 'name': 'dm-3',
24
'holders': [], 'dev_type': 'crypt'}],
26
'dev_type': 'crypt'}],
27
'dev_type': 'partition'}],
29
[{"device": "/sys/class/block/vdb", 'name': 'vdb', "holders":
30
[{"device": "/sys/class/block/vdb/vdb1", 'name': 'vdb1',
31
"holders": [], "dev_type": "partition"},
32
{"device": "/sys/class/block/vdb/vdb2", 'name': 'vdb2',
33
"holders": [], "dev_type": "partition"},
34
{"device": "/sys/class/block/vdb/vdb3", 'name': 'vdb3', "holders":
35
[{"device": "/sys/class/block/md0", 'name': 'md0', "holders":
36
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
37
"holders": [], "dev_type": "bcache"}],
39
"dev_type": "partition"},
40
{"device": "/sys/class/block/vdb/vdb4", 'name': 'vdb4', "holders":
41
[{"device": "/sys/class/block/md0", 'name': 'md0', "holders":
42
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
43
"holders": [], "dev_type": "bcache"}],
45
"dev_type": "partition"},
46
{"device": "/sys/class/block/vdb/vdb5", 'name': 'vdb5', "holders":
47
[{"device": "/sys/class/block/md0", 'name': 'md0', "holders":
48
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
49
"holders": [], "dev_type": "bcache"}],
51
"dev_type": "partition"},
52
{"device": "/sys/class/block/vdb/vdb6", 'name': 'vdb6', "holders":
53
[{"device": "/sys/class/block/bcache1", 'name': 'bcache1',
54
"holders": [], "dev_type": "bcache"},
55
{"device": "/sys/class/block/bcache2", 'name': 'bcache2',
56
"holders": [], "dev_type": "bcache"}],
57
"dev_type": "partition"},
58
{"device": "/sys/class/block/vdb/vdb7", 'name': 'vdb7', "holders":
59
[{"device": "/sys/class/block/bcache2", 'name': 'bcache2',
60
"holders": [], "dev_type": "bcache"}],
61
"dev_type": "partition"},
62
{"device": "/sys/class/block/vdb/vdb8", 'name': 'vdb8',
63
"holders": [], "dev_type": "partition"}],
65
{"device": "/sys/class/block/vdc", 'name': 'vdc', "holders": [],
67
{"device": "/sys/class/block/vdd", 'name': 'vdd', "holders":
68
[{"device": "/sys/class/block/vdd/vdd1", 'name': 'vdd1',
69
"holders": [], "dev_type": "partition"}],
73
@mock.patch('curtin.block.clear_holders.block')
74
@mock.patch('curtin.block.clear_holders.util')
75
def test_get_dmsetup_uuid(self, mock_util, mock_block):
76
"""ensure that clear_holders.get_dmsetup_uuid works as expected"""
77
uuid = "CRYPT-LUKS1-fe335a74374e4649af9776c1699676f8-sdb5_crypt"
78
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
79
mock_util.subp.return_value = (' ' + uuid + '\n', None)
80
res = clear_holders.get_dmsetup_uuid(self.test_syspath)
81
mock_util.subp.assert_called_with(
82
['dmsetup', 'info', self.test_blockdev, '-C', '-o',
83
'uuid', '--noheadings'], capture=True)
84
self.assertEqual(res, uuid)
85
mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
87
@mock.patch('curtin.block.clear_holders.block')
88
@mock.patch('curtin.block.clear_holders.os')
89
def test_get_bcache_using_dev(self, mock_os, mock_block):
90
"""Ensure that get_bcache_using_dev works"""
91
fake_bcache = '/sys/fs/bcache/fake'
92
mock_os.path.realpath.return_value = fake_bcache
93
mock_os.path.exists.side_effect = lambda x: x == fake_bcache
94
mock_block.sys_block_path.return_value = self.test_blockdev
95
bcache_dir = clear_holders.get_bcache_using_dev(self.test_blockdev)
96
self.assertEqual(bcache_dir, fake_bcache)
98
@mock.patch('curtin.block.clear_holders.get_dmsetup_uuid')
99
@mock.patch('curtin.block.clear_holders.block')
100
def test_differentiate_lvm_and_crypt(
101
self, mock_block, mock_get_dmsetup_uuid):
102
"""test clear_holders.identify_lvm and clear_holders.identify_crypt"""
103
for (kname, dm_uuid, is_lvm, is_crypt) in [
104
('dm-0', 'LVM-abcdefg', True, False),
105
('sda', 'LVM-abcdefg', False, False),
106
('sda', 'CRYPT-abcdefg', False, False),
107
('dm-0', 'CRYPT-abcdefg', False, True),
108
('dm-1', 'invalid', False, False)]:
109
mock_block.path_to_kname.return_value = kname
110
mock_get_dmsetup_uuid.return_value = dm_uuid
112
is_lvm, clear_holders.identify_lvm(self.test_syspath))
114
is_crypt, clear_holders.identify_crypt(self.test_syspath))
115
mock_block.path_to_kname.assert_called_with(self.test_syspath)
116
mock_get_dmsetup_uuid.assert_called_with(self.test_syspath)
118
@mock.patch('curtin.block.clear_holders.util')
119
@mock.patch('curtin.block.clear_holders.os')
120
@mock.patch('curtin.block.clear_holders.LOG')
121
@mock.patch('curtin.block.clear_holders.get_bcache_using_dev')
122
def test_shutdown_bcache(self, mock_get_bcache, mock_log, mock_os,
124
"""test clear_holders.shutdown_bcache"""
125
mock_os.path.exists.return_value = True
126
mock_os.path.join.side_effect = os.path.join
127
mock_get_bcache.return_value = self.test_blockdev
128
clear_holders.shutdown_bcache(self.test_syspath)
129
mock_get_bcache.assert_called_with(self.test_syspath)
130
self.assertTrue(mock_log.debug.called)
131
self.assertFalse(mock_log.warn.called)
132
mock_util.write_file.assert_called_with(self.test_blockdev + '/stop',
135
@mock.patch('curtin.block.clear_holders.LOG')
136
@mock.patch('curtin.block.clear_holders.block.sys_block_path')
137
@mock.patch('curtin.block.clear_holders.lvm')
138
@mock.patch('curtin.block.clear_holders.util')
139
def test_shutdown_lvm(self, mock_util, mock_lvm, mock_syspath, mock_log):
140
"""test clear_holders.shutdown_lvm"""
141
vg_name = 'volgroup1'
143
mock_syspath.return_value = self.test_blockdev
144
mock_util.load_file.return_value = '-'.join((vg_name, lv_name))
145
mock_lvm.split_lvm_name.return_value = (vg_name, lv_name)
146
mock_lvm.get_lvols_in_volgroup.return_value = ['lvol2']
147
clear_holders.shutdown_lvm(self.test_blockdev)
148
mock_syspath.assert_called_with(self.test_blockdev)
149
mock_util.load_file.assert_called_with(self.test_blockdev + '/dm/name')
150
mock_lvm.split_lvm_name.assert_called_with(
151
'-'.join((vg_name, lv_name)))
152
self.assertTrue(mock_log.debug.called)
153
mock_util.subp.assert_called_with(
154
['lvremove', '--force', '--force', '/'.join((vg_name, lv_name))],
156
mock_lvm.get_lvols_in_volgroup.assert_called_with(vg_name)
157
self.assertEqual(len(mock_util.subp.call_args_list), 1)
158
self.assertTrue(mock_lvm.lvm_scan.called)
159
mock_lvm.get_lvols_in_volgroup.return_value = []
160
clear_holders.shutdown_lvm(self.test_blockdev)
161
mock_util.subp.assert_called_with(
162
['vgremove', '--force', '--force', vg_name], rcs=[0, 5])
164
@mock.patch('curtin.block.clear_holders.block')
165
@mock.patch('curtin.block.clear_holders.util')
166
def test_shutdown_crypt(self, mock_util, mock_block):
167
"""test clear_holders.shutdown_crypt"""
168
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
169
clear_holders.shutdown_crypt(self.test_syspath)
170
mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
171
mock_util.subp.assert_called_with(
172
['cryptsetup', 'remove', self.test_blockdev], capture=True)
174
@mock.patch('curtin.block.clear_holders.LOG')
175
@mock.patch('curtin.block.clear_holders.block')
176
def test_shutdown_mdadm(self, mock_block, mock_log):
177
"""test clear_holders.shutdown_mdadm"""
178
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
179
clear_holders.shutdown_mdadm(self.test_syspath)
180
mock_block.mdadm.mdadm_stop.assert_called_with(self.test_blockdev)
181
mock_block.mdadm.mdadm_remove.assert_called_with(self.test_blockdev)
182
self.assertTrue(mock_log.debug.called)
184
@mock.patch('curtin.block.clear_holders.LOG')
185
@mock.patch('curtin.block.clear_holders.block')
186
def test_clear_holders_wipe_superblock(self, mock_block, mock_log):
187
"""test clear_holders.wipe_superblock handles errors right"""
188
mock_block.sysfs_to_devpath.return_value = self.test_blockdev
189
mock_block.is_extended_partition.return_value = True
190
clear_holders.wipe_superblock(self.test_syspath)
191
self.assertFalse(mock_block.wipe_volume.called)
192
mock_block.is_extended_partition.return_value = False
193
clear_holders.wipe_superblock(self.test_syspath)
194
mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
195
mock_block.wipe_volume.assert_called_with(
196
self.test_blockdev, mode='superblock')
198
@mock.patch('curtin.block.clear_holders.LOG')
199
@mock.patch('curtin.block.clear_holders.block')
200
@mock.patch('curtin.block.clear_holders.os')
201
def test_get_holders(self, mock_os, mock_block, mock_log):
202
"""test clear_holders.get_holders"""
203
mock_block.sys_block_path.return_value = self.test_syspath
204
mock_os.path.join.side_effect = os.path.join
205
clear_holders.get_holders(self.test_blockdev)
206
mock_block.sys_block_path.assert_called_with(self.test_blockdev)
207
mock_os.path.join.assert_called_with(self.test_syspath, 'holders')
208
self.assertTrue(mock_log.debug.called)
209
mock_os.listdir.assert_called_with(
210
os.path.join(self.test_syspath, 'holders'))
212
def test_plan_shutdown_holders_trees(self):
214
make sure clear_holdrs.plan_shutdown_holders_tree orders shutdown
215
functions correctly and uses the appropriate shutdown function for each
218
# trees that have been generated, checked for correctness,
219
# and the order that they should be shut down in (by level)
220
test_trees_and_orders = [
221
(self.example_holders_trees[0][0],
222
({'dm-3'}, {'dm-1', 'dm-2'}, {'dm-0'}, {'sda5', 'sda2', 'sda1'},
224
(self.example_holders_trees[1],
225
({'bcache1'}, {'bcache2', 'md0'},
226
{'vdb1', 'vdb2', 'vdb3', 'vdb4', 'vdb5', 'vdb6', 'vdb7', 'vdb8',
228
{'vdb', 'vdc', 'vdd'}))
230
for tree, correct_order in test_trees_and_orders:
231
res = clear_holders.plan_shutdown_holder_trees(tree)
232
for level in correct_order:
233
self.assertEqual({os.path.basename(e['device'])
234
for e in res[:len(level)]}, level)
235
res = res[len(level):]
237
def test_format_holders_tree(self):
238
"""test output of clear_holders.format_holders_tree"""
239
test_trees_and_results = [
240
(self.example_holders_trees[0][0],
251
(self.example_holders_trees[1][0],
272
(self.example_holders_trees[1][1], 'vdc'),
273
(self.example_holders_trees[1][2],
279
for tree, result in test_trees_and_results:
280
self.assertEqual(clear_holders.format_holders_tree(tree), result)
282
def test_get_holder_types(self):
283
"""test clear_holders.get_holder_types"""
284
test_trees_and_results = [
285
(self.example_holders_trees[0][0],
286
{('disk', '/sys/class/block/sda'),
287
('partition', '/sys/class/block/sda/sda1'),
288
('partition', '/sys/class/block/sda/sda2'),
289
('partition', '/sys/class/block/sda/sda5'),
290
('crypt', '/sys/class/block/dm-0'),
291
('lvm', '/sys/class/block/dm-1'),
292
('lvm', '/sys/class/block/dm-2'),
293
('crypt', '/sys/class/block/dm-3')}),
294
(self.example_holders_trees[1][0],
295
{('disk', '/sys/class/block/vdb'),
296
('partition', '/sys/class/block/vdb/vdb1'),
297
('partition', '/sys/class/block/vdb/vdb2'),
298
('partition', '/sys/class/block/vdb/vdb3'),
299
('partition', '/sys/class/block/vdb/vdb4'),
300
('partition', '/sys/class/block/vdb/vdb5'),
301
('partition', '/sys/class/block/vdb/vdb6'),
302
('partition', '/sys/class/block/vdb/vdb7'),
303
('partition', '/sys/class/block/vdb/vdb8'),
304
('raid', '/sys/class/block/md0'),
305
('bcache', '/sys/class/block/bcache1'),
306
('bcache', '/sys/class/block/bcache2')})
308
for tree, result in test_trees_and_results:
309
self.assertEqual(clear_holders.get_holder_types(tree), result)
311
@mock.patch('curtin.block.clear_holders.block.sys_block_path')
312
@mock.patch('curtin.block.clear_holders.gen_holders_tree')
313
def test_assert_clear(self, mock_gen_holders_tree, mock_syspath):
314
mock_gen_holders_tree.return_value = self.example_holders_trees[0][0]
315
mock_syspath.side_effect = lambda x: x
317
with self.assertRaises(OSError):
318
clear_holders.assert_clear(device)
319
mock_gen_holders_tree.assert_called_with(device)
320
mock_gen_holders_tree.return_value = self.example_holders_trees[1][1]
321
clear_holders.assert_clear(device)
323
@mock.patch('curtin.block.clear_holders.block.mdadm')
324
@mock.patch('curtin.block.clear_holders.util')
325
def test_start_clear_holders_deps(self, mock_util, mock_mdadm):
326
clear_holders.start_clear_holders_deps()
327
mock_mdadm.mdadm_assemble.assert_called_with(
328
scan=True, ignore_errors=True)
329
mock_util.subp.assert_called_with(['modprobe', 'bcache'], rcs=[0, 1])