~curtin-dev/curtin/trunk

« back to all changes in this revision

Viewing changes to tests/unittests/test_block_mdadm.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
 
from mock import call, patch
2
 
from curtin.block import dev_short
3
 
from curtin.block import mdadm
4
 
from curtin import util
5
 
from .helpers import CiTestCase
6
 
import os
7
 
import subprocess
8
 
import textwrap
9
 
 
10
 
 
11
 
class TestBlockMdadmAssemble(CiTestCase):
12
 
 
13
 
    def setUp(self):
14
 
        super(TestBlockMdadmAssemble, self).setUp()
15
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
16
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
17
 
        self.add_patch('curtin.block.mdadm.udev', 'mock_udev')
18
 
 
19
 
        # Common mock settings
20
 
        self.mock_valid.return_value = True
21
 
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
22
 
        self.mock_util.subp.return_value = ('', '')
23
 
 
24
 
    def test_mdadm_assemble_scan(self):
25
 
        mdadm.mdadm_assemble(scan=True)
26
 
        assemble_calls = [
27
 
            call(["mdadm", "--assemble", "--scan", "-v"], capture=True,
28
 
                 rcs=[0, 1, 2]),
29
 
            call(["mdadm", "--detail", "--scan", "-v"], capture=True,
30
 
                 rcs=[0, 1]),
31
 
        ]
32
 
        self.mock_util.subp.assert_has_calls(assemble_calls)
33
 
        self.assertTrue(self.mock_udev.udevadm_settle.called)
34
 
 
35
 
    def test_mdadm_assemble_md_devname(self):
36
 
        md_devname = "/dev/md0"
37
 
        mdadm.mdadm_assemble(md_devname=md_devname)
38
 
 
39
 
        assemble_calls = [
40
 
            call(["mdadm", "--assemble", md_devname, "--run"],
41
 
                 capture=True, rcs=[0, 1, 2]),
42
 
            call(["mdadm", "--detail", "--scan", "-v"], capture=True,
43
 
                 rcs=[0, 1]),
44
 
        ]
45
 
        self.mock_util.subp.assert_has_calls(assemble_calls)
46
 
        self.assertTrue(self.mock_udev.udevadm_settle.called)
47
 
 
48
 
    def test_mdadm_assemble_md_devname_short(self):
49
 
        with self.assertRaises(ValueError):
50
 
            md_devname = "md0"
51
 
            mdadm.mdadm_assemble(md_devname=md_devname)
52
 
 
53
 
    def test_mdadm_assemble_md_devname_none(self):
54
 
        with self.assertRaises(ValueError):
55
 
            md_devname = None
56
 
            mdadm.mdadm_assemble(md_devname=md_devname)
57
 
 
58
 
    def test_mdadm_assemble_md_devname_devices(self):
59
 
        md_devname = "/dev/md0"
60
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
61
 
        mdadm.mdadm_assemble(md_devname=md_devname, devices=devices)
62
 
        assemble_calls = [
63
 
            call(["mdadm", "--assemble", md_devname, "--run"] + devices,
64
 
                 capture=True, rcs=[0, 1, 2]),
65
 
            call(["mdadm", "--detail", "--scan", "-v"], capture=True,
66
 
                 rcs=[0, 1]),
67
 
        ]
68
 
        self.mock_util.subp.assert_has_calls(assemble_calls)
69
 
        self.assertTrue(self.mock_udev.udevadm_settle.called)
70
 
 
71
 
    def test_mdadm_assemble_exec_error(self):
72
 
 
73
 
        def _raise_pexec_error(*args, **kwargs):
74
 
            raise util.ProcessExecutionError()
75
 
 
76
 
        self.mock_util.ProcessExecutionError = util.ProcessExecutionError
77
 
        self.mock_util.subp.side_effect = _raise_pexec_error
78
 
        with self.assertRaises(util.ProcessExecutionError):
79
 
            mdadm.mdadm_assemble(scan=True, ignore_errors=False)
80
 
        self.mock_util.subp.assert_called_with(
81
 
            ['mdadm', '--assemble', '--scan', '-v'], capture=True,
82
 
            rcs=[0, 1, 2])
83
 
 
84
 
 
85
 
class TestBlockMdadmCreate(CiTestCase):
86
 
    def setUp(self):
87
 
        super(TestBlockMdadmCreate, self).setUp()
88
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
89
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
90
 
        self.add_patch('curtin.block.mdadm.get_holders', 'mock_holders')
91
 
 
92
 
        # Common mock settings
93
 
        self.mock_valid.return_value = True
94
 
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
95
 
        self.mock_holders.return_value = []
96
 
 
97
 
    def prepare_mock(self, md_devname, raidlevel, devices, spares):
98
 
        side_effects = []
99
 
        expected_calls = []
100
 
        hostname = 'ubuntu'
101
 
 
102
 
        # don't mock anything if raidlevel and spares mismatch
103
 
        if spares and raidlevel not in mdadm.SPARE_RAID_LEVELS:
104
 
            return (side_effects, expected_calls)
105
 
 
106
 
        side_effects.append((hostname, ""))  # hostname -s
107
 
        expected_calls.append(call(["hostname", "-s"],
108
 
                                   capture=True, rcs=[0]))
109
 
 
110
 
        # prepare side-effects
111
 
        for d in devices + spares:
112
 
            side_effects.append(("", ""))  # mdadm --zero-superblock
113
 
            expected_calls.append(
114
 
                call(["mdadm", "--zero-superblock", d], capture=True))
115
 
 
116
 
        side_effects.append(("", ""))  # udevadm settle
117
 
        expected_calls.append(call(["udevadm", "settle"]))
118
 
        side_effects.append(("", ""))  # udevadm control --stop-exec-queue
119
 
        expected_calls.append(call(["udevadm", "control",
120
 
                                    "--stop-exec-queue"]))
121
 
 
122
 
        side_effects.append(("", ""))  # mdadm create
123
 
        # build command how mdadm_create does
124
 
        cmd = (["mdadm", "--create", md_devname, "--run",
125
 
                "--homehost=%s" % hostname, "--level=%s" % raidlevel,
126
 
                "--raid-devices=%s" % len(devices)] +
127
 
               devices)
128
 
        if spares:
129
 
            cmd += ["--spare-devices=%s" % len(spares)] + spares
130
 
 
131
 
        expected_calls.append(call(cmd, capture=True))
132
 
        side_effects.append(("", ""))  # udevadm control --start-exec-queue
133
 
        expected_calls.append(call(["udevadm", "control",
134
 
                                    "--start-exec-queue"]))
135
 
        side_effects.append(("", ""))  # udevadm settle
136
 
        expected_calls.append(call(["udevadm", "settle",
137
 
                                    "--exit-if-exists=%s" % md_devname]))
138
 
 
139
 
        return (side_effects, expected_calls)
140
 
 
141
 
    def test_mdadm_create_raid0(self):
142
 
        md_devname = "/dev/md0"
143
 
        raidlevel = 0
144
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
145
 
        spares = []
146
 
        (side_effects, expected_calls) = self.prepare_mock(md_devname,
147
 
                                                           raidlevel,
148
 
                                                           devices,
149
 
                                                           spares)
150
 
 
151
 
        self.mock_util.subp.side_effect = side_effects
152
 
        mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
153
 
                           devices=devices, spares=spares)
154
 
        self.mock_util.subp.assert_has_calls(expected_calls)
155
 
 
156
 
    def test_mdadm_create_raid0_devshort(self):
157
 
        md_devname = "md0"
158
 
        raidlevel = 0
159
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
160
 
        spares = []
161
 
        with self.assertRaises(ValueError):
162
 
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
163
 
                               devices=devices, spares=spares)
164
 
 
165
 
    def test_mdadm_create_raid0_with_spares(self):
166
 
        md_devname = "/dev/md0"
167
 
        raidlevel = 0
168
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
169
 
        spares = ["/dev/vde1"]
170
 
        (side_effects, expected_calls) = self.prepare_mock(md_devname,
171
 
                                                           raidlevel,
172
 
                                                           devices,
173
 
                                                           spares)
174
 
 
175
 
        self.mock_util.subp.side_effect = side_effects
176
 
        with self.assertRaises(ValueError):
177
 
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
178
 
                               devices=devices, spares=spares)
179
 
        self.mock_util.subp.assert_has_calls(expected_calls)
180
 
 
181
 
    def test_mdadm_create_md_devname_none(self):
182
 
        md_devname = None
183
 
        raidlevel = 0
184
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
185
 
        spares = ["/dev/vde1"]
186
 
        with self.assertRaises(ValueError):
187
 
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
188
 
                               devices=devices, spares=spares)
189
 
 
190
 
    def test_mdadm_create_md_devname_missing(self):
191
 
        self.mock_valid.return_value = False
192
 
        md_devname = "/dev/wark"
193
 
        raidlevel = 0
194
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
195
 
        spares = ["/dev/vde1"]
196
 
        with self.assertRaises(ValueError):
197
 
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
198
 
                               devices=devices, spares=spares)
199
 
 
200
 
    def test_mdadm_create_invalid_raidlevel(self):
201
 
        md_devname = "/dev/md0"
202
 
        raidlevel = 27
203
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
204
 
        spares = ["/dev/vde1"]
205
 
        with self.assertRaises(ValueError):
206
 
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
207
 
                               devices=devices, spares=spares)
208
 
 
209
 
    def test_mdadm_create_check_min_devices(self):
210
 
        md_devname = "/dev/md0"
211
 
        raidlevel = 5
212
 
        devices = ["/dev/vdc1", "/dev/vdd1"]
213
 
        spares = ["/dev/vde1"]
214
 
        with self.assertRaises(ValueError):
215
 
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
216
 
                               devices=devices, spares=spares)
217
 
 
218
 
    def test_mdadm_create_raid5(self):
219
 
        md_devname = "/dev/md0"
220
 
        raidlevel = 5
221
 
        devices = ['/dev/vdc1', '/dev/vdd1', '/dev/vde1']
222
 
        spares = ['/dev/vdg1']
223
 
        (side_effects, expected_calls) = self.prepare_mock(md_devname,
224
 
                                                           raidlevel,
225
 
                                                           devices,
226
 
                                                           spares)
227
 
 
228
 
        self.mock_util.subp.side_effect = side_effects
229
 
        mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
230
 
                           devices=devices, spares=spares)
231
 
        self.mock_util.subp.assert_has_calls(expected_calls)
232
 
 
233
 
 
234
 
class TestBlockMdadmExamine(CiTestCase):
235
 
    def setUp(self):
236
 
        super(TestBlockMdadmExamine, self).setUp()
237
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
238
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
239
 
 
240
 
        # Common mock settings
241
 
        self.mock_valid.return_value = True
242
 
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
243
 
 
244
 
    def test_mdadm_examine_export(self):
245
 
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
246
 
        self.mock_util.subp.return_value = (
247
 
            """
248
 
            MD_LEVEL=raid0
249
 
            MD_DEVICES=2
250
 
            MD_METADATA=0.90
251
 
            MD_UUID=93a73e10:427f280b:b7076c02:204b8f7a
252
 
            """, "")
253
 
 
254
 
        device = "/dev/vde"
255
 
        data = mdadm.mdadm_examine(device, export=True)
256
 
 
257
 
        expected_calls = [
258
 
            call(["mdadm", "--examine", "--export", device], capture=True),
259
 
        ]
260
 
        self.mock_util.subp.assert_has_calls(expected_calls)
261
 
        self.assertEqual(data['MD_UUID'],
262
 
                         '93a73e10:427f280b:b7076c02:204b8f7a')
263
 
 
264
 
    def test_mdadm_examine_no_export(self):
265
 
        self.mock_util.subp.return_value = ("""/dev/vde:
266
 
              Magic : a92b4efc
267
 
            Version : 1.2
268
 
        Feature Map : 0x0
269
 
         Array UUID : 93a73e10:427f280b:b7076c02:204b8f7a
270
 
               Name : wily-foobar:0  (local to host wily-foobar)
271
 
      Creation Time : Sat Dec 12 16:06:05 2015
272
 
         Raid Level : raid1
273
 
       Raid Devices : 2
274
 
 
275
 
     Avail Dev Size : 20955136 (9.99 GiB 10.73 GB)
276
 
      Used Dev Size : 20955136 (9.99 GiB 10.73 GB)
277
 
         Array Size : 10477568 (9.99 GiB 10.73 GB)
278
 
        Data Offset : 16384 sectors
279
 
       Super Offset : 8 sectors
280
 
       Unused Space : before=16296 sectors, after=0 sectors
281
 
              State : clean
282
 
        Device UUID : 8fcd62e6:991acc6e:6cb71ee3:7c956919
283
 
 
284
 
        Update Time : Sat Dec 12 16:09:09 2015
285
 
      Bad Block Log : 512 entries available at offset 72 sectors
286
 
           Checksum : 65b57c2e - correct
287
 
             Events : 17
288
 
 
289
 
 
290
 
       Device Role : spare
291
 
       Array State : AA ('A' == active, '.' == missing, 'R' == replacing)
292
 
        """, "")   # mdadm --examine /dev/vde
293
 
 
294
 
        device = "/dev/vde"
295
 
        data = mdadm.mdadm_examine(device, export=False)
296
 
 
297
 
        expected_calls = [
298
 
            call(["mdadm", "--examine", device], capture=True),
299
 
        ]
300
 
        self.mock_util.subp.assert_has_calls(expected_calls)
301
 
        self.assertEqual(data['MD_UUID'],
302
 
                         '93a73e10:427f280b:b7076c02:204b8f7a')
303
 
 
304
 
    def test_mdadm_examine_no_raid(self):
305
 
        self.mock_util.subp.side_effect = subprocess.CalledProcessError("", "")
306
 
 
307
 
        device = "/dev/sda"
308
 
        data = mdadm.mdadm_examine(device, export=False)
309
 
 
310
 
        expected_calls = [
311
 
            call(["mdadm", "--examine", device], capture=True),
312
 
        ]
313
 
 
314
 
        # don't mock anything if raidlevel and spares mismatch
315
 
        self.mock_util.subp.assert_has_calls(expected_calls)
316
 
        self.assertEqual(data, {})
317
 
 
318
 
 
319
 
class TestBlockMdadmStop(CiTestCase):
320
 
    def setUp(self):
321
 
        super(TestBlockMdadmStop, self).setUp()
322
 
        self.add_patch('curtin.block.mdadm.util.lsb_release', 'mock_util_lsb')
323
 
        self.add_patch('curtin.block.mdadm.util.subp', 'mock_util_subp')
324
 
        self.add_patch('curtin.block.mdadm.util.write_file',
325
 
                       'mock_util_write_file')
326
 
        self.add_patch('curtin.block.mdadm.util.load_file',
327
 
                       'mock_util_load_file')
328
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
329
 
        self.add_patch('curtin.block.mdadm.sys_block_path',
330
 
                       'mock_sys_block_path')
331
 
        self.add_patch('curtin.block.mdadm.os.path.isfile', 'mock_path_isfile')
332
 
 
333
 
        # Common mock settings
334
 
        self.mock_valid.return_value = True
335
 
        self.mock_util_lsb.return_value = {'codename': 'xenial'}
336
 
        self.mock_util_subp.side_effect = iter([
337
 
            ("", ""),  # mdadm stop device
338
 
        ])
339
 
        self.mock_path_isfile.return_value = True
340
 
        self.mock_util_load_file.side_effect = iter([
341
 
            "idle", "max",
342
 
        ])
343
 
 
344
 
    def _set_sys_path(self, md_device):
345
 
        self.sys_path = '/sys/class/block/%s/md' % md_device.split("/")[-1]
346
 
        self.mock_sys_block_path.return_value = self.sys_path
347
 
 
348
 
    def test_mdadm_stop_no_devpath(self):
349
 
        with self.assertRaises(ValueError):
350
 
            mdadm.mdadm_stop(None)
351
 
 
352
 
    def test_mdadm_stop(self):
353
 
        device = "/dev/md0"
354
 
        self._set_sys_path(device)
355
 
 
356
 
        mdadm.mdadm_stop(device)
357
 
 
358
 
        expected_calls = [
359
 
            call(["mdadm", "--manage", "--stop", device], capture=True)
360
 
        ]
361
 
        self.mock_util_subp.assert_has_calls(expected_calls)
362
 
 
363
 
        expected_reads = [
364
 
            call(self.sys_path + '/sync_action'),
365
 
            call(self.sys_path + '/sync_max'),
366
 
        ]
367
 
        self.mock_util_load_file.assert_has_calls(expected_reads)
368
 
 
369
 
    @patch('curtin.block.mdadm.time.sleep')
370
 
    def test_mdadm_stop_retry(self, mock_sleep):
371
 
        device = "/dev/md10"
372
 
        self._set_sys_path(device)
373
 
        self.mock_util_load_file.side_effect = iter([
374
 
            "resync", "max",
375
 
            "proc/mdstat output",
376
 
            "idle", "0",
377
 
        ])
378
 
        self.mock_util_subp.side_effect = iter([
379
 
            util.ProcessExecutionError(),
380
 
            ("mdadm stopped %s" % device, ''),
381
 
        ])
382
 
 
383
 
        mdadm.mdadm_stop(device)
384
 
 
385
 
        expected_calls = [
386
 
            call(["mdadm", "--manage", "--stop", device], capture=True),
387
 
            call(["mdadm", "--manage", "--stop", device], capture=True)
388
 
        ]
389
 
        self.mock_util_subp.assert_has_calls(expected_calls)
390
 
 
391
 
        expected_reads = [
392
 
            call(self.sys_path + '/sync_action'),
393
 
            call(self.sys_path + '/sync_max'),
394
 
            call('/proc/mdstat'),
395
 
            call(self.sys_path + '/sync_action'),
396
 
            call(self.sys_path + '/sync_max'),
397
 
        ]
398
 
        self.mock_util_load_file.assert_has_calls(expected_reads)
399
 
 
400
 
        expected_writes = [
401
 
            call(self.sys_path + '/sync_action', content='idle'),
402
 
            call(self.sys_path + '/sync_max', content='0'),
403
 
            call(self.sys_path + '/sync_min', content='0'),
404
 
        ]
405
 
        self.mock_util_write_file.assert_has_calls(expected_writes)
406
 
 
407
 
    @patch('curtin.block.mdadm.time.sleep')
408
 
    def test_mdadm_stop_retry_sysfs_write_fail(self, mock_sleep):
409
 
        device = "/dev/md126"
410
 
        self._set_sys_path(device)
411
 
        self.mock_util_load_file.side_effect = iter([
412
 
            "resync", "max",
413
 
            "proc/mdstat output",
414
 
            "idle", "0",
415
 
        ])
416
 
        self.mock_util_subp.side_effect = iter([
417
 
            util.ProcessExecutionError(),
418
 
            ("mdadm stopped %s" % device, ''),
419
 
        ])
420
 
        # sometimes we fail to modify sysfs attrs
421
 
        self.mock_util_write_file.side_effect = iter([
422
 
            "",         # write to sync_action OK
423
 
            IOError(),  # write to sync_max FAIL
424
 
        ])
425
 
 
426
 
        mdadm.mdadm_stop(device)
427
 
 
428
 
        expected_calls = [
429
 
            call(["mdadm", "--manage", "--stop", device], capture=True),
430
 
            call(["mdadm", "--manage", "--stop", device], capture=True)
431
 
        ]
432
 
        self.mock_util_subp.assert_has_calls(expected_calls)
433
 
 
434
 
        expected_reads = [
435
 
            call(self.sys_path + '/sync_action'),
436
 
            call(self.sys_path + '/sync_max'),
437
 
            call('/proc/mdstat'),
438
 
            call(self.sys_path + '/sync_action'),
439
 
            call(self.sys_path + '/sync_max'),
440
 
        ]
441
 
        self.mock_util_load_file.assert_has_calls(expected_reads)
442
 
 
443
 
        expected_writes = [
444
 
            call(self.sys_path + '/sync_action', content='idle'),
445
 
        ]
446
 
        self.mock_util_write_file.assert_has_calls(expected_writes)
447
 
 
448
 
    @patch('curtin.block.mdadm.time.sleep')
449
 
    def test_mdadm_stop_retry_exhausted(self, mock_sleep):
450
 
        device = "/dev/md/37"
451
 
        retries = 60
452
 
        self._set_sys_path(device)
453
 
        self.mock_util_load_file.side_effect = iter([
454
 
            "resync", "max",
455
 
            "proc/mdstat output",
456
 
        ] * retries)
457
 
        self.mock_util_subp.side_effect = iter([
458
 
            util.ProcessExecutionError(),
459
 
        ] * retries)
460
 
        # sometimes we fail to modify sysfs attrs
461
 
        self.mock_util_write_file.side_effect = iter([
462
 
            "", IOError()] * retries)
463
 
 
464
 
        with self.assertRaises(OSError):
465
 
            mdadm.mdadm_stop(device)
466
 
 
467
 
        expected_calls = [
468
 
            call(["mdadm", "--manage", "--stop", device], capture=True),
469
 
        ] * retries
470
 
        self.mock_util_subp.assert_has_calls(expected_calls)
471
 
 
472
 
        expected_reads = [
473
 
            call(self.sys_path + '/sync_action'),
474
 
            call(self.sys_path + '/sync_max'),
475
 
            call('/proc/mdstat'),
476
 
        ] * retries
477
 
        self.mock_util_load_file.assert_has_calls(expected_reads)
478
 
 
479
 
        expected_writes = [
480
 
            call(self.sys_path + '/sync_action', content='idle'),
481
 
            call(self.sys_path + '/sync_max', content='0'),
482
 
        ] * retries
483
 
        self.mock_util_write_file.assert_has_calls(expected_writes)
484
 
 
485
 
 
486
 
class TestBlockMdadmRemove(CiTestCase):
487
 
    def setUp(self):
488
 
        super(TestBlockMdadmRemove, self).setUp()
489
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
490
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
491
 
 
492
 
        # Common mock settings
493
 
        self.mock_valid.return_value = True
494
 
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
495
 
        self.mock_util.subp.side_effect = [
496
 
            ("", ""),  # mdadm remove device
497
 
        ]
498
 
 
499
 
    def test_mdadm_remove_no_devpath(self):
500
 
        with self.assertRaises(ValueError):
501
 
            mdadm.mdadm_remove(None)
502
 
 
503
 
    def test_mdadm_remove(self):
504
 
        device = "/dev/vdc"
505
 
        mdadm.mdadm_remove(device)
506
 
        expected_calls = [
507
 
            call(["mdadm", "--remove", device], rcs=[0], capture=True),
508
 
        ]
509
 
        self.mock_util.subp.assert_has_calls(expected_calls)
510
 
 
511
 
 
512
 
class TestBlockMdadmQueryDetail(CiTestCase):
513
 
    def setUp(self):
514
 
        super(TestBlockMdadmQueryDetail, self).setUp()
515
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
516
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
517
 
 
518
 
        # Common mock settings
519
 
        self.mock_valid.return_value = True
520
 
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
521
 
 
522
 
    def test_mdadm_query_detail_export(self):
523
 
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
524
 
        self.mock_util.subp.return_value = (
525
 
            """
526
 
            MD_LEVEL=raid1
527
 
            MD_DEVICES=2
528
 
            MD_METADATA=1.2
529
 
            MD_UUID=93a73e10:427f280b:b7076c02:204b8f7a
530
 
            MD_NAME=wily-foobar:0
531
 
            MD_DEVICE_vdc_ROLE=0
532
 
            MD_DEVICE_vdc_DEV=/dev/vdc
533
 
            MD_DEVICE_vdd_ROLE=1
534
 
            MD_DEVICE_vdd_DEV=/dev/vdd
535
 
            MD_DEVICE_vde_ROLE=spare
536
 
            MD_DEVICE_vde_DEV=/dev/vde
537
 
            """, "")
538
 
 
539
 
        device = "/dev/md0"
540
 
        self.mock_valid.return_value = True
541
 
        data = mdadm.mdadm_query_detail(device, export=True)
542
 
 
543
 
        expected_calls = [
544
 
            call(["mdadm", "--query", "--detail", "--export", device],
545
 
                 capture=True),
546
 
        ]
547
 
        self.mock_util.subp.assert_has_calls(expected_calls)
548
 
        self.assertEqual(data['MD_UUID'],
549
 
                         '93a73e10:427f280b:b7076c02:204b8f7a')
550
 
 
551
 
    def test_mdadm_query_detail_no_export(self):
552
 
        self.mock_util.subp.return_value = ("""/dev/md0:
553
 
        Version : 1.2
554
 
  Creation Time : Sat Dec 12 16:06:05 2015
555
 
     Raid Level : raid1
556
 
     Array Size : 10477568 (9.99 GiB 10.73 GB)
557
 
  Used Dev Size : 10477568 (9.99 GiB 10.73 GB)
558
 
   Raid Devices : 2
559
 
  Total Devices : 3
560
 
    Persistence : Superblock is persistent
561
 
 
562
 
    Update Time : Sat Dec 12 16:09:09 2015
563
 
          State : clean
564
 
 Active Devices : 2
565
 
Working Devices : 3
566
 
 Failed Devices : 0
567
 
  Spare Devices : 1
568
 
 
569
 
           Name : wily-foobar:0  (local to host wily-foobar)
570
 
           UUID : 93a73e10:427f280b:b7076c02:204b8f7a
571
 
         Events : 17
572
 
 
573
 
    Number   Major   Minor   RaidDevice State
574
 
       0     253       32        0      active sync   /dev/vdc
575
 
       1     253       48        1      active sync   /dev/vdd
576
 
 
577
 
       2     253       64        -      spare   /dev/vde
578
 
        """, "")   # mdadm --query --detail /dev/md0
579
 
 
580
 
        device = "/dev/md0"
581
 
        data = mdadm.mdadm_query_detail(device, export=False)
582
 
        expected_calls = [
583
 
            call(["mdadm", "--query", "--detail", device], capture=True),
584
 
        ]
585
 
        self.mock_util.subp.assert_has_calls(expected_calls)
586
 
        self.assertEqual(data['MD_UUID'],
587
 
                         '93a73e10:427f280b:b7076c02:204b8f7a')
588
 
 
589
 
 
590
 
class TestBlockMdadmDetailScan(CiTestCase):
591
 
    def setUp(self):
592
 
        super(TestBlockMdadmDetailScan, self).setUp()
593
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
594
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
595
 
 
596
 
        # Common mock settings
597
 
        self.scan_output = ("ARRAY /dev/md0 metadata=1.2 spares=2 name=0 " +
598
 
                            "UUID=b1eae2ff:69b6b02e:1d63bb53:ddfa6e4a")
599
 
        self.mock_valid.return_value = True
600
 
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
601
 
        self.mock_util.subp.side_effect = [
602
 
            (self.scan_output, ""),  # mdadm --detail --scan
603
 
        ]
604
 
 
605
 
    def test_mdadm_remove(self):
606
 
        data = mdadm.mdadm_detail_scan()
607
 
        expected_calls = [
608
 
            call(["mdadm", "--detail", "--scan"], capture=True),
609
 
        ]
610
 
        self.mock_util.subp.assert_has_calls(expected_calls)
611
 
        self.assertEqual(self.scan_output, data)
612
 
 
613
 
    def test_mdadm_remove_error(self):
614
 
        self.mock_util.subp.side_effect = [
615
 
            ("wark", "error"),  # mdadm --detail --scan
616
 
        ]
617
 
        data = mdadm.mdadm_detail_scan()
618
 
        expected_calls = [
619
 
            call(["mdadm", "--detail", "--scan"], capture=True),
620
 
        ]
621
 
        self.mock_util.subp.assert_has_calls(expected_calls)
622
 
        self.assertEqual(None, data)
623
 
 
624
 
 
625
 
class TestBlockMdadmMdHelpers(CiTestCase):
626
 
    def setUp(self):
627
 
        super(TestBlockMdadmMdHelpers, self).setUp()
628
 
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
629
 
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
630
 
 
631
 
        self.mock_valid.return_value = True
632
 
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
633
 
 
634
 
    def test_valid_mdname(self):
635
 
        mdname = "/dev/md0"
636
 
        result = mdadm.valid_mdname(mdname)
637
 
        expected_calls = [
638
 
            call(mdname)
639
 
        ]
640
 
        self.mock_valid.assert_has_calls(expected_calls)
641
 
        self.assertTrue(result)
642
 
 
643
 
    def test_valid_mdname_short(self):
644
 
        mdname = "md0"
645
 
        with self.assertRaises(ValueError):
646
 
            mdadm.valid_mdname(mdname)
647
 
 
648
 
    def test_valid_mdname_none(self):
649
 
        mdname = None
650
 
        with self.assertRaises(ValueError):
651
 
            mdadm.valid_mdname(mdname)
652
 
 
653
 
    def test_valid_mdname_not_valid_device(self):
654
 
        self.mock_valid.return_value = False
655
 
        mdname = "/dev/md0"
656
 
        with self.assertRaises(ValueError):
657
 
            mdadm.valid_mdname(mdname)
658
 
 
659
 
    @patch('curtin.block.mdadm.sys_block_path')
660
 
    @patch('curtin.block.mdadm.os.path.isfile')
661
 
    def test_md_sysfs_attr(self, mock_isfile, mock_sysblock):
662
 
        mdname = "/dev/md0"
663
 
        attr_name = 'array_state'
664
 
        sysfs_path = '/sys/class/block/{}/md/{}'.format(dev_short(mdname),
665
 
                                                        attr_name)
666
 
        mock_sysblock.side_effect = ['/sys/class/block/md0/md']
667
 
        mock_isfile.side_effect = [True]
668
 
        mdadm.md_sysfs_attr(mdname, attr_name)
669
 
        self.mock_util.load_file.assert_called_with(sysfs_path)
670
 
        mock_sysblock.assert_called_with(mdname, 'md')
671
 
        mock_isfile.assert_called_with(sysfs_path)
672
 
 
673
 
    def test_md_sysfs_attr_devname_none(self):
674
 
        mdname = None
675
 
        attr_name = 'array_state'
676
 
        with self.assertRaises(ValueError):
677
 
            mdadm.md_sysfs_attr(mdname, attr_name)
678
 
 
679
 
    def test_md_raidlevel_short(self):
680
 
        for rl in [0, 1, 5, 6, 10, 'linear', 'stripe']:
681
 
            self.assertEqual(rl, mdadm.md_raidlevel_short(rl))
682
 
            if isinstance(rl, int):
683
 
                long_rl = 'raid%d' % rl
684
 
                self.assertEqual(rl, mdadm.md_raidlevel_short(long_rl))
685
 
 
686
 
    def test_md_minimum_devices(self):
687
 
        min_to_rl = {
688
 
            2: [0, 1, 'linear', 'stripe'],
689
 
            3: [5],
690
 
            4: [6, 10],
691
 
        }
692
 
 
693
 
        for rl in [0, 1, 5, 6, 10, 'linear', 'stripe']:
694
 
            min_devs = mdadm.md_minimum_devices(rl)
695
 
            self.assertTrue(rl in min_to_rl[min_devs])
696
 
 
697
 
    def test_md_minimum_devices_invalid_rl(self):
698
 
        min_devs = mdadm.md_minimum_devices(27)
699
 
        self.assertEqual(min_devs, -1)
700
 
 
701
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
702
 
    def test_md_check_array_state_rw(self, mock_attr):
703
 
        mdname = '/dev/md0'
704
 
        mock_attr.return_value = 'clean'
705
 
        self.assertTrue(mdadm.md_check_array_state_rw(mdname))
706
 
 
707
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
708
 
    def test_md_check_array_state_rw_false(self, mock_attr):
709
 
        mdname = '/dev/md0'
710
 
        mock_attr.return_value = 'inactive'
711
 
        self.assertFalse(mdadm.md_check_array_state_rw(mdname))
712
 
 
713
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
714
 
    def test_md_check_array_state_ro(self, mock_attr):
715
 
        mdname = '/dev/md0'
716
 
        mock_attr.return_value = 'readonly'
717
 
        self.assertTrue(mdadm.md_check_array_state_ro(mdname))
718
 
 
719
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
720
 
    def test_md_check_array_state_ro_false(self, mock_attr):
721
 
        mdname = '/dev/md0'
722
 
        mock_attr.return_value = 'inactive'
723
 
        self.assertFalse(mdadm.md_check_array_state_ro(mdname))
724
 
 
725
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
726
 
    def test_md_check_array_state_error(self, mock_attr):
727
 
        mdname = '/dev/md0'
728
 
        mock_attr.return_value = 'inactive'
729
 
        self.assertTrue(mdadm.md_check_array_state_error(mdname))
730
 
 
731
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
732
 
    def test_md_check_array_state_error_false(self, mock_attr):
733
 
        mdname = '/dev/md0'
734
 
        mock_attr.return_value = 'active'
735
 
        self.assertFalse(mdadm.md_check_array_state_error(mdname))
736
 
 
737
 
    def test_md_device_key_role(self):
738
 
        devname = '/dev/vda'
739
 
        rolekey = mdadm.md_device_key_role(devname)
740
 
        self.assertEqual('MD_DEVICE_vda_ROLE', rolekey)
741
 
 
742
 
    def test_md_device_key_role_no_dev(self):
743
 
        devname = None
744
 
        with self.assertRaises(ValueError):
745
 
            mdadm.md_device_key_role(devname)
746
 
 
747
 
    def test_md_device_key_dev(self):
748
 
        devname = '/dev/vda'
749
 
        devkey = mdadm.md_device_key_dev(devname)
750
 
        self.assertEqual('MD_DEVICE_vda_DEV', devkey)
751
 
 
752
 
    def test_md_device_key_dev_no_dev(self):
753
 
        devname = None
754
 
        with self.assertRaises(ValueError):
755
 
            mdadm.md_device_key_dev(devname)
756
 
 
757
 
    @patch('curtin.block.get_blockdev_for_partition')
758
 
    @patch('curtin.block.mdadm.os.path.exists')
759
 
    @patch('curtin.block.mdadm.os.listdir')
760
 
    def tests_md_get_spares_list(self, mock_listdir, mock_exists,
761
 
                                 mock_getbdev):
762
 
        mdname = '/dev/md0'
763
 
        devices = ['dev-vda', 'dev-vdb', 'dev-vdc']
764
 
        states = ['in-sync', 'in-sync', 'spare']
765
 
 
766
 
        mock_exists.return_value = True
767
 
        mock_listdir.return_value = devices
768
 
        self.mock_util.load_file.side_effect = states
769
 
        mock_getbdev.return_value = ('md0', None)
770
 
 
771
 
        sysfs_path = '/sys/class/block/md0/md/'
772
 
 
773
 
        expected_calls = []
774
 
        for d in devices:
775
 
            expected_calls.append(call(os.path.join(sysfs_path, d, 'state')))
776
 
 
777
 
        spares = mdadm.md_get_spares_list(mdname)
778
 
        self.mock_util.load_file.assert_has_calls(expected_calls)
779
 
        self.assertEqual(['/dev/vdc'], spares)
780
 
 
781
 
    @patch('curtin.block.get_blockdev_for_partition')
782
 
    @patch('curtin.block.mdadm.os.path.exists')
783
 
    def tests_md_get_spares_list_nomd(self, mock_exists, mock_getbdev):
784
 
        mdname = '/dev/md0'
785
 
        mock_exists.return_value = False
786
 
        mock_getbdev.return_value = ('md0', None)
787
 
        with self.assertRaises(OSError):
788
 
            mdadm.md_get_spares_list(mdname)
789
 
 
790
 
    @patch('curtin.block.get_blockdev_for_partition')
791
 
    @patch('curtin.block.mdadm.os.path.exists')
792
 
    @patch('curtin.block.mdadm.os.listdir')
793
 
    def tests_md_get_devices_list(self, mock_listdir, mock_exists,
794
 
                                  mock_getbdev):
795
 
        mdname = '/dev/md0'
796
 
        devices = ['dev-vda', 'dev-vdb', 'dev-vdc']
797
 
        states = ['in-sync', 'in-sync', 'spare']
798
 
 
799
 
        mock_exists.return_value = True
800
 
        mock_listdir.return_value = devices
801
 
        self.mock_util.load_file.side_effect = states
802
 
        mock_getbdev.return_value = ('md0', None)
803
 
 
804
 
        sysfs_path = '/sys/class/block/md0/md/'
805
 
 
806
 
        expected_calls = []
807
 
        for d in devices:
808
 
            expected_calls.append(call(os.path.join(sysfs_path, d, 'state')))
809
 
 
810
 
        devs = mdadm.md_get_devices_list(mdname)
811
 
        self.mock_util.load_file.assert_has_calls(expected_calls)
812
 
        self.assertEqual(sorted(['/dev/vda', '/dev/vdb']), sorted(devs))
813
 
 
814
 
    @patch('curtin.block.get_blockdev_for_partition')
815
 
    @patch('curtin.block.mdadm.os.path.exists')
816
 
    def tests_md_get_devices_list_nomd(self, mock_exists, mock_getbdev):
817
 
        mdname = '/dev/md0'
818
 
        mock_exists.return_value = False
819
 
        mock_getbdev.return_value = ('md0', None)
820
 
        with self.assertRaises(OSError):
821
 
            mdadm.md_get_devices_list(mdname)
822
 
 
823
 
    @patch('curtin.block.mdadm.os')
824
 
    def test_md_check_array_uuid(self, mock_os):
825
 
        devname = '/dev/md0'
826
 
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
827
 
        mock_os.path.realpath.return_value = devname
828
 
        rv = mdadm.md_check_array_uuid(devname, md_uuid)
829
 
        self.assertTrue(rv)
830
 
 
831
 
    @patch('curtin.block.mdadm.os')
832
 
    def test_md_check_array_uuid_mismatch(self, mock_os):
833
 
        devname = '/dev/md0'
834
 
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
835
 
        mock_os.path.realpath.return_value = '/dev/md1'
836
 
 
837
 
        with self.assertRaises(ValueError):
838
 
            mdadm.md_check_array_uuid(devname, md_uuid)
839
 
 
840
 
    @patch('curtin.block.mdadm.mdadm_query_detail')
841
 
    def test_md_get_uuid(self, mock_query):
842
 
        mdname = '/dev/md0'
843
 
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
844
 
        mock_query.return_value = {'MD_UUID': md_uuid}
845
 
        uuid = mdadm.md_get_uuid(mdname)
846
 
        self.assertEqual(md_uuid, uuid)
847
 
 
848
 
    @patch('curtin.block.mdadm.mdadm_query_detail')
849
 
    def test_md_get_uuid_dev_none(self, mock_query):
850
 
        mdname = None
851
 
        with self.assertRaises(ValueError):
852
 
            mdadm.md_get_uuid(mdname)
853
 
 
854
 
    def test_md_check_raid_level(self):
855
 
        for rl in mdadm.VALID_RAID_LEVELS:
856
 
            self.assertTrue(mdadm.md_check_raidlevel(rl))
857
 
 
858
 
    def test_md_check_raid_level_bad(self):
859
 
        bogus = '27'
860
 
        self.assertTrue(bogus not in mdadm.VALID_RAID_LEVELS)
861
 
        with self.assertRaises(ValueError):
862
 
            mdadm.md_check_raidlevel(bogus)
863
 
 
864
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
865
 
    def test_md_check_array_state(self, mock_attr):
866
 
        mdname = '/dev/md0'
867
 
        mock_attr.side_effect = [
868
 
            'clean',  # array_state
869
 
            '0',  # degraded
870
 
            'idle',  # sync_action
871
 
        ]
872
 
        self.assertTrue(mdadm.md_check_array_state(mdname))
873
 
 
874
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
875
 
    def test_md_check_array_state_norw(self, mock_attr):
876
 
        mdname = '/dev/md0'
877
 
        mock_attr.side_effect = [
878
 
            'suspended',  # array_state
879
 
            '0',  # degraded
880
 
            'idle',  # sync_action
881
 
        ]
882
 
        with self.assertRaises(ValueError):
883
 
            mdadm.md_check_array_state(mdname)
884
 
 
885
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
886
 
    def test_md_check_array_state_degraded(self, mock_attr):
887
 
        mdname = '/dev/md0'
888
 
        mock_attr.side_effect = [
889
 
            'clean',  # array_state
890
 
            '1',  # degraded
891
 
            'idle',  # sync_action
892
 
        ]
893
 
        with self.assertRaises(ValueError):
894
 
            mdadm.md_check_array_state(mdname)
895
 
 
896
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
897
 
    def test_md_check_array_state_degraded_empty(self, mock_attr):
898
 
        mdname = '/dev/md0'
899
 
        mock_attr.side_effect = [
900
 
            'clean',  # array_state
901
 
            '',  # unknown
902
 
            'idle',  # sync_action
903
 
        ]
904
 
        with self.assertRaises(ValueError):
905
 
            mdadm.md_check_array_state(mdname)
906
 
 
907
 
    @patch('curtin.block.mdadm.md_sysfs_attr')
908
 
    def test_md_check_array_state_sync(self, mock_attr):
909
 
        mdname = '/dev/md0'
910
 
        mock_attr.side_effect = [
911
 
            'clean',  # array_state
912
 
            '0',  # degraded
913
 
            'recovery',  # sync_action
914
 
        ]
915
 
        with self.assertRaises(ValueError):
916
 
            mdadm.md_check_array_state(mdname)
917
 
 
918
 
    @patch('curtin.block.mdadm.md_check_array_uuid')
919
 
    @patch('curtin.block.mdadm.md_get_uuid')
920
 
    def test_md_check_uuid(self, mock_guuid, mock_ckuuid):
921
 
        mdname = '/dev/md0'
922
 
        mock_guuid.return_value = '93a73e10:427f280b:b7076c02:204b8f7a'
923
 
        mock_ckuuid.return_value = True
924
 
 
925
 
        rv = mdadm.md_check_uuid(mdname)
926
 
        self.assertTrue(rv)
927
 
 
928
 
    @patch('curtin.block.mdadm.md_check_array_uuid')
929
 
    @patch('curtin.block.mdadm.md_get_uuid')
930
 
    def test_md_check_uuid_nouuid(self, mock_guuid, mock_ckuuid):
931
 
        mdname = '/dev/md0'
932
 
        mock_guuid.return_value = None
933
 
        with self.assertRaises(ValueError):
934
 
            mdadm.md_check_uuid(mdname)
935
 
 
936
 
    @patch('curtin.block.mdadm.md_get_devices_list')
937
 
    def test_md_check_devices(self, mock_devlist):
938
 
        mdname = '/dev/md0'
939
 
        devices = ['/dev/vdc', '/dev/vdd']
940
 
 
941
 
        mock_devlist.return_value = devices
942
 
        rv = mdadm.md_check_devices(mdname, devices)
943
 
        self.assertEqual(rv, None)
944
 
 
945
 
    @patch('curtin.block.mdadm.md_get_devices_list')
946
 
    def test_md_check_devices_wrong_devs(self, mock_devlist):
947
 
        mdname = '/dev/md0'
948
 
        devices = ['/dev/vdc', '/dev/vdd']
949
 
 
950
 
        mock_devlist.return_value = ['/dev/sda']
951
 
        with self.assertRaises(ValueError):
952
 
            mdadm.md_check_devices(mdname, devices)
953
 
 
954
 
    def test_md_check_devices_no_devs(self):
955
 
        mdname = '/dev/md0'
956
 
        devices = []
957
 
 
958
 
        with self.assertRaises(ValueError):
959
 
            mdadm.md_check_devices(mdname, devices)
960
 
 
961
 
    @patch('curtin.block.mdadm.md_get_spares_list')
962
 
    def test_md_check_spares(self, mock_devlist):
963
 
        mdname = '/dev/md0'
964
 
        spares = ['/dev/vdc', '/dev/vdd']
965
 
 
966
 
        mock_devlist.return_value = spares
967
 
        rv = mdadm.md_check_spares(mdname, spares)
968
 
        self.assertEqual(rv, None)
969
 
 
970
 
    @patch('curtin.block.mdadm.md_get_spares_list')
971
 
    def test_md_check_spares_wrong_devs(self, mock_devlist):
972
 
        mdname = '/dev/md0'
973
 
        spares = ['/dev/vdc', '/dev/vdd']
974
 
 
975
 
        mock_devlist.return_value = ['/dev/sda']
976
 
        with self.assertRaises(ValueError):
977
 
            mdadm.md_check_spares(mdname, spares)
978
 
 
979
 
    @patch('curtin.block.mdadm.mdadm_examine')
980
 
    @patch('curtin.block.mdadm.mdadm_query_detail')
981
 
    @patch('curtin.block.mdadm.md_get_uuid')
982
 
    def test_md_check_array_membership(self, mock_uuid, mock_query,
983
 
                                       mock_examine):
984
 
        mdname = '/dev/md0'
985
 
        devices = ['/dev/vda', '/dev/vdb', '/dev/vdc', '/dev/vdd']
986
 
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
987
 
        md_dict = {'MD_UUID': md_uuid}
988
 
        mock_query.return_value = md_dict
989
 
        mock_uuid.return_value = md_uuid
990
 
        mock_examine.side_effect = [md_dict] * len(devices)
991
 
        expected_calls = []
992
 
        for dev in devices:
993
 
            expected_calls.append(call(dev, export=False))
994
 
 
995
 
        rv = mdadm.md_check_array_membership(mdname, devices)
996
 
 
997
 
        self.assertEqual(rv, None)
998
 
        mock_uuid.assert_has_calls([call(mdname)])
999
 
        mock_examine.assert_has_calls(expected_calls)
1000
 
 
1001
 
    @patch('curtin.block.mdadm.mdadm_examine')
1002
 
    @patch('curtin.block.mdadm.mdadm_query_detail')
1003
 
    @patch('curtin.block.mdadm.md_get_uuid')
1004
 
    def test_md_check_array_membership_bad_dev(self, mock_uuid, mock_query,
1005
 
                                               mock_examine):
1006
 
        mdname = '/dev/md0'
1007
 
        devices = ['/dev/vda', '/dev/vdb', '/dev/vdc', '/dev/vdd']
1008
 
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
1009
 
        md_dict = {'MD_UUID': md_uuid}
1010
 
        mock_query.return_value = md_dict
1011
 
        mock_uuid.return_value = md_uuid
1012
 
        mock_examine.side_effect = [
1013
 
            md_dict,
1014
 
            {},
1015
 
            md_dict,
1016
 
            md_dict,
1017
 
        ]  # one device isn't a member
1018
 
 
1019
 
        with self.assertRaises(ValueError):
1020
 
            mdadm.md_check_array_membership(mdname, devices)
1021
 
 
1022
 
    @patch('curtin.block.mdadm.mdadm_examine')
1023
 
    @patch('curtin.block.mdadm.mdadm_query_detail')
1024
 
    @patch('curtin.block.mdadm.md_get_uuid')
1025
 
    def test_md_check_array_membership_wrong_array(self, mock_uuid, mock_query,
1026
 
                                                   mock_examine):
1027
 
        mdname = '/dev/md0'
1028
 
        devices = ['/dev/vda', '/dev/vdb', '/dev/vdc', '/dev/vdd']
1029
 
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
1030
 
        md_dict = {'MD_UUID': '11111111:427f280b:b7076c02:204b8f7a'}
1031
 
        mock_query.return_value = md_dict
1032
 
        mock_uuid.return_value = md_uuid
1033
 
        mock_examine.side_effect = [md_dict] * len(devices)
1034
 
 
1035
 
        with self.assertRaises(ValueError):
1036
 
            mdadm.md_check_array_membership(mdname, devices)
1037
 
 
1038
 
    @patch('curtin.block.mdadm.md_check_array_membership')
1039
 
    @patch('curtin.block.mdadm.md_check_spares')
1040
 
    @patch('curtin.block.mdadm.md_check_devices')
1041
 
    @patch('curtin.block.mdadm.md_check_uuid')
1042
 
    @patch('curtin.block.mdadm.md_check_raidlevel')
1043
 
    @patch('curtin.block.mdadm.md_check_array_state')
1044
 
    def test_md_check_all_good(self, mock_array, mock_raid, mock_uuid,
1045
 
                               mock_dev, mock_spare, mock_member):
1046
 
        md_devname = '/dev/md0'
1047
 
        raidlevel = 1
1048
 
        devices = ['/dev/vda', '/dev/vdb']
1049
 
        spares = ['/dev/vdc']
1050
 
 
1051
 
        mock_array.return_value = None
1052
 
        mock_raid.return_value = None
1053
 
        mock_uuid.return_value = None
1054
 
        mock_dev.return_value = None
1055
 
        mock_spare.return_value = None
1056
 
        mock_member.return_value = None
1057
 
 
1058
 
        mdadm.md_check(md_devname, raidlevel, devices=devices, spares=spares)
1059
 
 
1060
 
        mock_array.assert_has_calls([call(md_devname)])
1061
 
        mock_raid.assert_has_calls([call(raidlevel)])
1062
 
        mock_uuid.assert_has_calls([call(md_devname)])
1063
 
        mock_dev.assert_has_calls([call(md_devname, devices)])
1064
 
        mock_spare.assert_has_calls([call(md_devname, spares)])
1065
 
        mock_member.assert_has_calls([call(md_devname, devices + spares)])
1066
 
 
1067
 
    def test_md_check_all_good_devshort(self):
1068
 
        md_devname = 'md0'
1069
 
        raidlevel = 1
1070
 
        devices = ['/dev/vda', '/dev/vdb']
1071
 
        spares = ['/dev/vdc']
1072
 
 
1073
 
        with self.assertRaises(ValueError):
1074
 
            mdadm.md_check(md_devname, raidlevel, devices=devices,
1075
 
                           spares=spares)
1076
 
 
1077
 
    def test_md_present(self):
1078
 
        mdname = 'md0'
1079
 
        self.mock_util.load_file.return_value = textwrap.dedent("""
1080
 
        Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5]
1081
 
        [raid4] [raid10]
1082
 
        md0 : active raid1 vdc1[1] vda2[0]
1083
 
              3143680 blocks super 1.2 [2/2] [UU]
1084
 
 
1085
 
        unused devices: <none>
1086
 
        """)
1087
 
 
1088
 
        md_is_present = mdadm.md_present(mdname)
1089
 
 
1090
 
        self.assertTrue(md_is_present)
1091
 
        self.mock_util.load_file.assert_called_with('/proc/mdstat')
1092
 
 
1093
 
    def test_md_present_not_found(self):
1094
 
        mdname = 'md1'
1095
 
        self.mock_util.load_file.return_value = textwrap.dedent("""
1096
 
        Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5]
1097
 
        [raid4] [raid10]
1098
 
        md0 : active raid1 vdc1[1] vda2[0]
1099
 
              3143680 blocks super 1.2 [2/2] [UU]
1100
 
 
1101
 
        unused devices: <none>
1102
 
        """)
1103
 
 
1104
 
        md_is_present = mdadm.md_present(mdname)
1105
 
 
1106
 
        self.assertFalse(md_is_present)
1107
 
        self.mock_util.load_file.assert_called_with('/proc/mdstat')
1108
 
 
1109
 
    def test_md_present_not_found_check_matching(self):
1110
 
        mdname = 'md1'
1111
 
        found_mdname = 'md10'
1112
 
        self.mock_util.load_file.return_value = textwrap.dedent("""
1113
 
        Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5]
1114
 
        [raid4] [raid10]
1115
 
        md10 : active raid1 vdc1[1] vda2[0]
1116
 
               3143680 blocks super 1.2 [2/2] [UU]
1117
 
 
1118
 
        unused devices: <none>
1119
 
        """)
1120
 
 
1121
 
        md_is_present = mdadm.md_present(mdname)
1122
 
 
1123
 
        self.assertFalse(md_is_present,
1124
 
                         "%s mistakenly matched %s" % (mdname, found_mdname))
1125
 
        self.mock_util.load_file.assert_called_with('/proc/mdstat')
1126
 
 
1127
 
    def test_md_present_with_dev_path(self):
1128
 
        mdname = '/dev/md0'
1129
 
        self.mock_util.load_file.return_value = textwrap.dedent("""
1130
 
        Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5]
1131
 
        [raid4] [raid10]
1132
 
        md0 : active raid1 vdc1[1] vda2[0]
1133
 
              3143680 blocks super 1.2 [2/2] [UU]
1134
 
 
1135
 
        unused devices: <none>
1136
 
        """)
1137
 
 
1138
 
        md_is_present = mdadm.md_present(mdname)
1139
 
 
1140
 
        self.assertTrue(md_is_present)
1141
 
        self.mock_util.load_file.assert_called_with('/proc/mdstat')
1142
 
 
1143
 
    def test_md_present_none(self):
1144
 
        mdname = ''
1145
 
        self.mock_util.load_file.return_value = textwrap.dedent("""
1146
 
        Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5]
1147
 
        [raid4] [raid10]
1148
 
        md0 : active raid1 vdc1[1] vda2[0]
1149
 
              3143680 blocks super 1.2 [2/2] [UU]
1150
 
 
1151
 
        unused devices: <none>
1152
 
        """)
1153
 
 
1154
 
        with self.assertRaises(ValueError):
1155
 
            mdadm.md_present(mdname)
1156
 
 
1157
 
        # util.load_file should NOT have been called
1158
 
        self.assertEqual([], self.mock_util.call_args_list)
1159
 
 
1160
 
    def test_md_present_no_proc_mdstat(self):
1161
 
        mdname = 'md0'
1162
 
        self.mock_util.side_effect = IOError
1163
 
 
1164
 
        md_is_present = mdadm.md_present(mdname)
1165
 
        self.assertFalse(md_is_present)
1166
 
        self.mock_util.load_file.assert_called_with('/proc/mdstat')
1167
 
 
1168
 
 
1169
 
# vi: ts=4 expandtab syntax=python