~curtin-dev/curtin/trunk

« back to all changes in this revision

Viewing changes to tests/unittests/test_clear_holders.py

  • Committer: Scott Moser
  • Date: 2017-12-20 17:33:03 UTC
  • Revision ID: smoser@ubuntu.com-20171220173303-29gha5qb8wpqrd40
README: Mention move of revision control to git.

curtin development has moved its revision control to git.
It is available at
  https://code.launchpad.net/curtin

Clone with
  git clone https://git.launchpad.net/curtin
or
  git clone git+ssh://git.launchpad.net/curtin

For more information see
  http://curtin.readthedocs.io/en/latest/topics/development.html

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import errno
2
 
import mock
3
 
import os
4
 
import textwrap
5
 
 
6
 
from curtin.block import clear_holders
7
 
from .helpers import CiTestCase
8
 
 
9
 
 
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'}],
27
 
                'dev_type': 'lvm'}],
28
 
              'dev_type': 'crypt'}],
29
 
            'dev_type': 'partition'}],
30
 
          'dev_type': 'disk'}],
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"}],
40
 
              "dev_type": "raid"}],
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"}],
46
 
              "dev_type": "raid"}],
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"}],
52
 
              "dev_type": "raid"}],
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"}],
66
 
          "dev_type": "disk"},
67
 
         {"device": "/sys/class/block/vdc", 'name': 'vdc', "holders": [],
68
 
          "dev_type": "disk"},
69
 
         {"device": "/sys/class/block/vdd", 'name': 'vdd', "holders":
70
 
          [{"device": "/sys/class/block/vdd/vdd1", 'name': 'vdd1',
71
 
            "holders": [], "dev_type": "partition"}],
72
 
          "dev_type": "disk"}],
73
 
    ]
74
 
 
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)
88
 
 
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
97
 
 
98
 
        bcache_dir = clear_holders.get_bcache_using_dev(self.test_blockdev)
99
 
        mock_os.path.realpath.assert_called_with(self.test_syspath +
100
 
                                                 '/bcache/cache')
101
 
        self.assertEqual(bcache_dir, fake_bcache)
102
 
 
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")
112
 
 
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
126
 
            self.assertEqual(
127
 
                is_lvm, clear_holders.identify_lvm(self.test_syspath))
128
 
            self.assertEqual(
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)
132
 
 
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"""
143
 
        #
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
150
 
        #
151
 
 
152
 
        device = self.test_syspath
153
 
        bcache_cset_uuid = 'c08ae789-a964-46fb-a66e-650f0ae78f94'
154
 
 
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'
161
 
 
162
 
        clear_holders.shutdown_bcache(device)
163
 
 
164
 
        mock_get_bcache.assert_called_with(device, strict=False)
165
 
        mock_get_bcache_block.assert_called_with(device, strict=False)
166
 
 
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)])
173
 
 
174
 
        mock_util.write_file.assert_has_calls([
175
 
                mock.call('/sys/fs/bcache/%s/stop' % bcache_cset_uuid,
176
 
                          '1', mode=None),
177
 
                mock.call(device + '/bcache/stop',
178
 
                          '1', mode=None)])
179
 
 
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,
186
 
                                              mock_os, mock_util,
187
 
                                              mock_get_bcache_block):
188
 
        device = "/dev/fakenull"
189
 
        with self.assertRaises(ValueError):
190
 
            clear_holders.shutdown_bcache(device)
191
 
 
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))
197
 
 
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,
204
 
                                       mock_os, mock_util,
205
 
                                       mock_get_bcache_block):
206
 
        device = "/sys/class/block/null"
207
 
        mock_os.path.exists.return_value = False
208
 
 
209
 
        clear_holders.shutdown_bcache(device)
210
 
 
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))
216
 
 
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,
223
 
                                     mock_os, mock_util,
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
230
 
        ])
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
234
 
 
235
 
        clear_holders.shutdown_bcache(device)
236
 
 
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))
243
 
 
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',
247
 
                                                '1', mode=None)
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)])
252
 
 
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,
260
 
                                                     mock_log, mock_os,
261
 
                                                     mock_util,
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
269
 
        ])
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
274
 
 
275
 
        clear_holders.shutdown_bcache(device)
276
 
 
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))
283
 
 
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)
292
 
        ])
293
 
 
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,
301
 
                                                    mock_log, mock_os,
302
 
                                                    mock_util,
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
310
 
        ])
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
315
 
 
316
 
        clear_holders.shutdown_bcache(device)
317
 
 
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))
324
 
 
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),
328
 
        ])
329
 
        mock_util.wait_for_removal.assert_has_calls([
330
 
            mock.call(cset, retries=self.remove_retries)
331
 
        ])
332
 
 
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,
341
 
                                                    mock_log, mock_os,
342
 
                                                    mock_util,
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)
352
 
        ])
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
357
 
 
358
 
        # make writes to sysfs fail
359
 
        mock_util.write_file.side_effect = IOError(errno.ENOENT,
360
 
                                                   "File not found")
361
 
 
362
 
        clear_holders.shutdown_bcache(device)
363
 
 
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))
370
 
 
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),
374
 
        ])
375
 
        mock_util.wait_for_removal.assert_has_calls([
376
 
            mock.call(cset, retries=self.remove_retries)
377
 
        ])
378
 
 
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'
386
 
        lv_name = 'lvol1'
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))],
399
 
            rcs=[0, 5])
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])
407
 
 
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)
417
 
 
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,
424
 
                            mock_time):
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)
433
 
 
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,
442
 
                                                mock_os):
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
449
 
 
450
 
        with self.assertRaises(OSError):
451
 
            clear_holders.shutdown_mdadm(self.test_syspath)
452
 
 
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)
458
 
 
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,
466
 
                                                 mock_log, mock_util,
467
 
                                                 mock_time, mock_os):
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
473
 
 
474
 
        with self.assertRaises(OSError):
475
 
            clear_holders.shutdown_mdadm(self.test_syspath)
476
 
 
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)
482
 
 
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')
496
 
 
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'))
510
 
 
511
 
    def test_plan_shutdown_holders_trees(self):
512
 
        """
513
 
        make sure clear_holdrs.plan_shutdown_holders_tree orders shutdown
514
 
        functions correctly and uses the appropriate shutdown function for each
515
 
        dev type
516
 
        """
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'},
522
 
              {'sda'})),
523
 
            (self.example_holders_trees[1],
524
 
             ({'bcache1'}, {'bcache2', 'md0'},
525
 
              {'vdb1', 'vdb2', 'vdb3', 'vdb4', 'vdb5', 'vdb6', 'vdb7', 'vdb8',
526
 
               'vdd1'},
527
 
              {'vdb', 'vdc', 'vdd'}))
528
 
        ]
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):]
535
 
 
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],
540
 
             textwrap.dedent("""
541
 
                 sda
542
 
                 |-- sda1
543
 
                 |-- sda2
544
 
                 `-- sda5
545
 
                     `-- dm-0
546
 
                         |-- dm-1
547
 
                         `-- dm-2
548
 
                             `-- dm-3
549
 
                 """).strip()),
550
 
            (self.example_holders_trees[1][0],
551
 
             textwrap.dedent("""
552
 
                 vdb
553
 
                 |-- vdb1
554
 
                 |-- vdb2
555
 
                 |-- vdb3
556
 
                 |   `-- md0
557
 
                 |       `-- bcache1
558
 
                 |-- vdb4
559
 
                 |   `-- md0
560
 
                 |       `-- bcache1
561
 
                 |-- vdb5
562
 
                 |   `-- md0
563
 
                 |       `-- bcache1
564
 
                 |-- vdb6
565
 
                 |   |-- bcache1
566
 
                 |   `-- bcache2
567
 
                 |-- vdb7
568
 
                 |   `-- bcache2
569
 
                 `-- vdb8
570
 
                 """).strip()),
571
 
            (self.example_holders_trees[1][1], 'vdc'),
572
 
            (self.example_holders_trees[1][2],
573
 
             textwrap.dedent("""
574
 
                 vdd
575
 
                 `-- vdd1
576
 
                 """).strip())
577
 
        ]
578
 
        for tree, result in test_trees_and_results:
579
 
            self.assertEqual(clear_holders.format_holders_tree(tree), result)
580
 
 
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))
588
 
        self.assertEqual(
589
 
            mock.call('does/not/matter/stop', '1', mode=None),
590
 
            m_write_file.call_args)
591
 
 
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,
595
 
                                                      m_log):
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')
599
 
        self.assertEqual(
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])
603
 
 
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,
607
 
                                                      m_log):
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')
611
 
        self.assertEqual(
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])
615
 
 
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')})
641
 
        ]
642
 
        for tree, result in test_trees_and_results:
643
 
            self.assertEqual(clear_holders.get_holder_types(tree), result)
644
 
 
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
650
 
        device = '/dev/null'
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)
656
 
 
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])