~smoser/curtin/yakkety.lp1666986

« back to all changes in this revision

Viewing changes to tests/unittests/test_block_mdadm.py

  • Committer: Scott Moser
  • Date: 2016-02-12 21:54:46 UTC
  • mto: (58.1.1 pkg)
  • mto: This revision was merged to the branch mainline in revision 39.
  • Revision ID: smoser@ubuntu.com-20160212215446-0h8ka36oznelsjz1
Tags: upstream-0.1.0~bzr351
ImportĀ upstreamĀ versionĀ 0.1.0~bzr351

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from unittest import TestCase
 
2
from mock import call, patch
 
3
from curtin.block import dev_short
 
4
from curtin.block import mdadm
 
5
import os
 
6
import subprocess
 
7
 
 
8
 
 
9
class MdadmTestBase(TestCase):
 
10
    def setUp(self):
 
11
        super(MdadmTestBase, self).setUp()
 
12
 
 
13
    def add_patch(self, target, attr):
 
14
        """Patches specified target object and sets it as attr on test
 
15
        instance also schedules cleanup"""
 
16
        m = patch(target, autospec=True)
 
17
        p = m.start()
 
18
        self.addCleanup(m.stop)
 
19
        setattr(self, attr, p)
 
20
 
 
21
 
 
22
class TestBlockMdadmAssemble(MdadmTestBase):
 
23
    def setUp(self):
 
24
        super(TestBlockMdadmAssemble, self).setUp()
 
25
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
26
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
27
 
 
28
        # Common mock settings
 
29
        self.mock_valid.return_value = True
 
30
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
 
31
        self.mock_util.subp.side_effect = [
 
32
            ("", ""),  # mdadm assemble
 
33
            ("", ""),  # udevadm settle
 
34
        ]
 
35
 
 
36
    def test_mdadm_assemble_scan(self):
 
37
        mdadm.mdadm_assemble(scan=True)
 
38
        expected_calls = [
 
39
            call(["mdadm", "--assemble", "--scan"], capture=True,
 
40
                 rcs=[0, 1, 2]),
 
41
            call(["udevadm", "settle"]),
 
42
        ]
 
43
        self.mock_util.subp.assert_has_calls(expected_calls)
 
44
 
 
45
    def test_mdadm_assemble_md_devname(self):
 
46
        md_devname = "/dev/md0"
 
47
        mdadm.mdadm_assemble(md_devname=md_devname)
 
48
 
 
49
        expected_calls = [
 
50
            call(["mdadm", "--assemble", md_devname, "--run"], capture=True,
 
51
                 rcs=[0, 1, 2]),
 
52
            call(["udevadm", "settle"]),
 
53
        ]
 
54
        self.mock_util.subp.assert_has_calls(expected_calls)
 
55
 
 
56
    def test_mdadm_assemble_md_devname_short(self):
 
57
        with self.assertRaises(ValueError):
 
58
            md_devname = "md0"
 
59
            mdadm.mdadm_assemble(md_devname=md_devname)
 
60
 
 
61
    def test_mdadm_assemble_md_devname_none(self):
 
62
        with self.assertRaises(ValueError):
 
63
            md_devname = None
 
64
            mdadm.mdadm_assemble(md_devname=md_devname)
 
65
 
 
66
    def test_mdadm_assemble_md_devname_devices(self):
 
67
        md_devname = "/dev/md0"
 
68
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
69
        mdadm.mdadm_assemble(md_devname=md_devname, devices=devices)
 
70
        expected_calls = [
 
71
            call(["mdadm", "--assemble", md_devname, "--run"] + devices,
 
72
                 capture=True, rcs=[0, 1, 2]),
 
73
            call(["udevadm", "settle"]),
 
74
        ]
 
75
        self.mock_util.subp.assert_has_calls(expected_calls)
 
76
 
 
77
 
 
78
class TestBlockMdadmCreate(MdadmTestBase):
 
79
    def setUp(self):
 
80
        super(TestBlockMdadmCreate, self).setUp()
 
81
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
82
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
83
 
 
84
        # Common mock settings
 
85
        self.mock_valid.return_value = True
 
86
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
 
87
 
 
88
    def prepare_mock(self, md_devname, raidlevel, devices, spares):
 
89
        side_effects = []
 
90
        expected_calls = []
 
91
 
 
92
        # don't mock anything if raidlevel and spares mismatch
 
93
        if spares and raidlevel not in mdadm.SPARE_RAID_LEVELS:
 
94
            return (side_effects, expected_calls)
 
95
 
 
96
        # prepare side-effects
 
97
        for d in devices + spares:
 
98
            side_effects.append(("", ""))  # mdadm --zero-superblock
 
99
            expected_calls.append(
 
100
                call(["mdadm", "--zero-superblock", d], capture=True))
 
101
 
 
102
        side_effects.append(("", ""))  # udevadm settle
 
103
        expected_calls.append(call(["udevadm", "settle"]))
 
104
        side_effects.append(("", ""))  # udevadm control --stop-exec-queue
 
105
        expected_calls.append(call(["udevadm", "control",
 
106
                                    "--stop-exec-queue"]))
 
107
        side_effects.append(("", ""))  # mdadm create
 
108
        # build command how mdadm_create does
 
109
        cmd = (["mdadm", "--create", md_devname, "--run",
 
110
               "--level=%s" % raidlevel, "--raid-devices=%s" % len(devices)] +
 
111
               devices)
 
112
        if spares:
 
113
            cmd += ["--spare-devices=%s" % len(spares)] + spares
 
114
 
 
115
        expected_calls.append(call(cmd, capture=True))
 
116
        side_effects.append(("", ""))  # udevadm control --start-exec-queue
 
117
        expected_calls.append(call(["udevadm", "control",
 
118
                                    "--start-exec-queue"]))
 
119
        side_effects.append(("", ""))  # udevadm settle
 
120
        expected_calls.append(call(["udevadm", "settle",
 
121
                                    "--exit-if-exists=%s" % md_devname]))
 
122
 
 
123
        return (side_effects, expected_calls)
 
124
 
 
125
    def test_mdadm_create_raid0(self):
 
126
        md_devname = "/dev/md0"
 
127
        raidlevel = 0
 
128
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
129
        spares = []
 
130
        (side_effects, expected_calls) = self.prepare_mock(md_devname,
 
131
                                                           raidlevel,
 
132
                                                           devices,
 
133
                                                           spares)
 
134
 
 
135
        self.mock_util.subp.side_effect = side_effects
 
136
        mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
137
                           devices=devices, spares=spares)
 
138
        self.mock_util.subp.assert_has_calls(expected_calls)
 
139
 
 
140
    def test_mdadm_create_raid0_devshort(self):
 
141
        md_devname = "md0"
 
142
        raidlevel = 0
 
143
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
144
        spares = []
 
145
        with self.assertRaises(ValueError):
 
146
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
147
                               devices=devices, spares=spares)
 
148
 
 
149
    def test_mdadm_create_raid0_with_spares(self):
 
150
        md_devname = "/dev/md0"
 
151
        raidlevel = 0
 
152
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
153
        spares = ["/dev/vde1"]
 
154
        (side_effects, expected_calls) = self.prepare_mock(md_devname,
 
155
                                                           raidlevel,
 
156
                                                           devices,
 
157
                                                           spares)
 
158
 
 
159
        self.mock_util.subp.side_effect = side_effects
 
160
        with self.assertRaises(ValueError):
 
161
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
162
                               devices=devices, spares=spares)
 
163
        self.mock_util.subp.assert_has_calls(expected_calls)
 
164
 
 
165
    def test_mdadm_create_md_devname_none(self):
 
166
        md_devname = None
 
167
        raidlevel = 0
 
168
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
169
        spares = ["/dev/vde1"]
 
170
        with self.assertRaises(ValueError):
 
171
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
172
                               devices=devices, spares=spares)
 
173
 
 
174
    def test_mdadm_create_md_devname_missing(self):
 
175
        self.mock_valid.return_value = False
 
176
        md_devname = "/dev/wark"
 
177
        raidlevel = 0
 
178
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
179
        spares = ["/dev/vde1"]
 
180
        with self.assertRaises(ValueError):
 
181
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
182
                               devices=devices, spares=spares)
 
183
 
 
184
    def test_mdadm_create_invalid_raidlevel(self):
 
185
        md_devname = "/dev/md0"
 
186
        raidlevel = 27
 
187
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
188
        spares = ["/dev/vde1"]
 
189
        with self.assertRaises(ValueError):
 
190
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
191
                               devices=devices, spares=spares)
 
192
 
 
193
    def test_mdadm_create_check_min_devices(self):
 
194
        md_devname = "/dev/md0"
 
195
        raidlevel = 5
 
196
        devices = ["/dev/vdc1", "/dev/vdd1"]
 
197
        spares = ["/dev/vde1"]
 
198
        with self.assertRaises(ValueError):
 
199
            mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
200
                               devices=devices, spares=spares)
 
201
 
 
202
    def test_mdadm_create_raid5(self):
 
203
        md_devname = "/dev/md0"
 
204
        raidlevel = 5
 
205
        devices = ['/dev/vdc1', '/dev/vdd1', '/dev/vde1']
 
206
        spares = ['/dev/vdg1']
 
207
        (side_effects, expected_calls) = self.prepare_mock(md_devname,
 
208
                                                           raidlevel,
 
209
                                                           devices,
 
210
                                                           spares)
 
211
 
 
212
        self.mock_util.subp.side_effect = side_effects
 
213
        mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
 
214
                           devices=devices, spares=spares)
 
215
        self.mock_util.subp.assert_has_calls(expected_calls)
 
216
 
 
217
 
 
218
class TestBlockMdadmExamine(MdadmTestBase):
 
219
    def setUp(self):
 
220
        super(TestBlockMdadmExamine, self).setUp()
 
221
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
222
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
223
 
 
224
        # Common mock settings
 
225
        self.mock_valid.return_value = True
 
226
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
 
227
 
 
228
    def test_mdadm_examine_export(self):
 
229
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
 
230
        self.mock_util.subp.return_value = (
 
231
            """
 
232
            MD_LEVEL=raid0
 
233
            MD_DEVICES=2
 
234
            MD_METADATA=0.90
 
235
            MD_UUID=93a73e10:427f280b:b7076c02:204b8f7a
 
236
            """, "")
 
237
 
 
238
        device = "/dev/vde"
 
239
        data = mdadm.mdadm_examine(device, export=True)
 
240
 
 
241
        expected_calls = [
 
242
            call(["mdadm", "--examine", "--export", device], capture=True),
 
243
        ]
 
244
        self.mock_util.subp.assert_has_calls(expected_calls)
 
245
        self.assertEqual(data['MD_UUID'],
 
246
                         '93a73e10:427f280b:b7076c02:204b8f7a')
 
247
 
 
248
    def test_mdadm_examine_no_export(self):
 
249
        self.mock_util.subp.return_value = ("""/dev/vde:
 
250
              Magic : a92b4efc
 
251
            Version : 1.2
 
252
        Feature Map : 0x0
 
253
         Array UUID : 93a73e10:427f280b:b7076c02:204b8f7a
 
254
               Name : wily-foobar:0  (local to host wily-foobar)
 
255
      Creation Time : Sat Dec 12 16:06:05 2015
 
256
         Raid Level : raid1
 
257
       Raid Devices : 2
 
258
 
 
259
     Avail Dev Size : 20955136 (9.99 GiB 10.73 GB)
 
260
      Used Dev Size : 20955136 (9.99 GiB 10.73 GB)
 
261
         Array Size : 10477568 (9.99 GiB 10.73 GB)
 
262
        Data Offset : 16384 sectors
 
263
       Super Offset : 8 sectors
 
264
       Unused Space : before=16296 sectors, after=0 sectors
 
265
              State : clean
 
266
        Device UUID : 8fcd62e6:991acc6e:6cb71ee3:7c956919
 
267
 
 
268
        Update Time : Sat Dec 12 16:09:09 2015
 
269
      Bad Block Log : 512 entries available at offset 72 sectors
 
270
           Checksum : 65b57c2e - correct
 
271
             Events : 17
 
272
 
 
273
 
 
274
       Device Role : spare
 
275
       Array State : AA ('A' == active, '.' == missing, 'R' == replacing)
 
276
        """, "")   # mdadm --examine /dev/vde
 
277
 
 
278
        device = "/dev/vde"
 
279
        data = mdadm.mdadm_examine(device, export=False)
 
280
 
 
281
        expected_calls = [
 
282
            call(["mdadm", "--examine", device], capture=True),
 
283
        ]
 
284
        self.mock_util.subp.assert_has_calls(expected_calls)
 
285
        self.assertEqual(data['MD_UUID'],
 
286
                         '93a73e10:427f280b:b7076c02:204b8f7a')
 
287
 
 
288
    def test_mdadm_examine_no_raid(self):
 
289
        self.mock_util.subp.side_effect = subprocess.CalledProcessError("", "")
 
290
 
 
291
        device = "/dev/sda"
 
292
        data = mdadm.mdadm_examine(device, export=False)
 
293
 
 
294
        expected_calls = [
 
295
            call(["mdadm", "--examine", device], capture=True),
 
296
        ]
 
297
 
 
298
        # don't mock anything if raidlevel and spares mismatch
 
299
        self.mock_util.subp.assert_has_calls(expected_calls)
 
300
        self.assertEqual(data, {})
 
301
 
 
302
 
 
303
class TestBlockMdadmStop(MdadmTestBase):
 
304
    def setUp(self):
 
305
        super(TestBlockMdadmStop, self).setUp()
 
306
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
307
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
308
 
 
309
        # Common mock settings
 
310
        self.mock_valid.return_value = True
 
311
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
 
312
        self.mock_util.subp.side_effect = [
 
313
            ("", ""),  # mdadm stop device
 
314
        ]
 
315
 
 
316
    def test_mdadm_stop_no_devpath(self):
 
317
        with self.assertRaises(ValueError):
 
318
            mdadm.mdadm_stop(None)
 
319
 
 
320
    def test_mdadm_stop(self):
 
321
        device = "/dev/vdc"
 
322
        mdadm.mdadm_stop(device)
 
323
        expected_calls = [
 
324
            call(["mdadm", "--stop", device], rcs=[0, 1], capture=True),
 
325
        ]
 
326
        self.mock_util.subp.assert_has_calls(expected_calls)
 
327
 
 
328
 
 
329
class TestBlockMdadmRemove(MdadmTestBase):
 
330
    def setUp(self):
 
331
        super(TestBlockMdadmRemove, self).setUp()
 
332
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
333
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
334
 
 
335
        # Common mock settings
 
336
        self.mock_valid.return_value = True
 
337
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
 
338
        self.mock_util.subp.side_effect = [
 
339
            ("", ""),  # mdadm remove device
 
340
        ]
 
341
 
 
342
    def test_mdadm_remove_no_devpath(self):
 
343
        with self.assertRaises(ValueError):
 
344
            mdadm.mdadm_remove(None)
 
345
 
 
346
    def test_mdadm_remove(self):
 
347
        device = "/dev/vdc"
 
348
        mdadm.mdadm_remove(device)
 
349
        expected_calls = [
 
350
            call(["mdadm", "--remove", device], rcs=[0, 1], capture=True),
 
351
        ]
 
352
        self.mock_util.subp.assert_has_calls(expected_calls)
 
353
 
 
354
 
 
355
class TestBlockMdadmQueryDetail(MdadmTestBase):
 
356
    def setUp(self):
 
357
        super(TestBlockMdadmQueryDetail, self).setUp()
 
358
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
359
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
360
 
 
361
        # Common mock settings
 
362
        self.mock_valid.return_value = True
 
363
        self.mock_util.lsb_release.return_value = {'codename': 'precise'}
 
364
 
 
365
    def test_mdadm_query_detail_export(self):
 
366
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
 
367
        self.mock_util.subp.return_value = (
 
368
            """
 
369
            MD_LEVEL=raid1
 
370
            MD_DEVICES=2
 
371
            MD_METADATA=1.2
 
372
            MD_UUID=93a73e10:427f280b:b7076c02:204b8f7a
 
373
            MD_NAME=wily-foobar:0
 
374
            MD_DEVICE_vdc_ROLE=0
 
375
            MD_DEVICE_vdc_DEV=/dev/vdc
 
376
            MD_DEVICE_vdd_ROLE=1
 
377
            MD_DEVICE_vdd_DEV=/dev/vdd
 
378
            MD_DEVICE_vde_ROLE=spare
 
379
            MD_DEVICE_vde_DEV=/dev/vde
 
380
            """, "")
 
381
 
 
382
        device = "/dev/md0"
 
383
        self.mock_valid.return_value = True
 
384
        data = mdadm.mdadm_query_detail(device, export=True)
 
385
 
 
386
        expected_calls = [
 
387
            call(["mdadm", "--query", "--detail", "--export", device],
 
388
                 capture=True),
 
389
        ]
 
390
        self.mock_util.subp.assert_has_calls(expected_calls)
 
391
        self.assertEqual(data['MD_UUID'],
 
392
                         '93a73e10:427f280b:b7076c02:204b8f7a')
 
393
 
 
394
    def test_mdadm_query_detail_no_export(self):
 
395
        self.mock_util.subp.return_value = ("""/dev/md0:
 
396
        Version : 1.2
 
397
  Creation Time : Sat Dec 12 16:06:05 2015
 
398
     Raid Level : raid1
 
399
     Array Size : 10477568 (9.99 GiB 10.73 GB)
 
400
  Used Dev Size : 10477568 (9.99 GiB 10.73 GB)
 
401
   Raid Devices : 2
 
402
  Total Devices : 3
 
403
    Persistence : Superblock is persistent
 
404
 
 
405
    Update Time : Sat Dec 12 16:09:09 2015
 
406
          State : clean
 
407
 Active Devices : 2
 
408
Working Devices : 3
 
409
 Failed Devices : 0
 
410
  Spare Devices : 1
 
411
 
 
412
           Name : wily-foobar:0  (local to host wily-foobar)
 
413
           UUID : 93a73e10:427f280b:b7076c02:204b8f7a
 
414
         Events : 17
 
415
 
 
416
    Number   Major   Minor   RaidDevice State
 
417
       0     253       32        0      active sync   /dev/vdc
 
418
       1     253       48        1      active sync   /dev/vdd
 
419
 
 
420
       2     253       64        -      spare   /dev/vde
 
421
        """, "")   # mdadm --query --detail /dev/md0
 
422
 
 
423
        device = "/dev/md0"
 
424
        data = mdadm.mdadm_query_detail(device, export=False)
 
425
        expected_calls = [
 
426
            call(["mdadm", "--query", "--detail", device], capture=True),
 
427
        ]
 
428
        self.mock_util.subp.assert_has_calls(expected_calls)
 
429
        self.assertEqual(data['MD_UUID'],
 
430
                         '93a73e10:427f280b:b7076c02:204b8f7a')
 
431
 
 
432
 
 
433
class TestBlockMdadmDetailScan(MdadmTestBase):
 
434
    def setUp(self):
 
435
        super(TestBlockMdadmDetailScan, self).setUp()
 
436
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
437
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
438
 
 
439
        # Common mock settings
 
440
        self.scan_output = ("ARRAY /dev/md0 metadata=1.2 spares=2 name=0 " +
 
441
                            "UUID=b1eae2ff:69b6b02e:1d63bb53:ddfa6e4a")
 
442
        self.mock_valid.return_value = True
 
443
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
 
444
        self.mock_util.subp.side_effect = [
 
445
            (self.scan_output, ""),  # mdadm --detail --scan
 
446
        ]
 
447
 
 
448
    def test_mdadm_remove(self):
 
449
        data = mdadm.mdadm_detail_scan()
 
450
        expected_calls = [
 
451
            call(["mdadm", "--detail", "--scan"], capture=True),
 
452
        ]
 
453
        self.mock_util.subp.assert_has_calls(expected_calls)
 
454
        self.assertEqual(self.scan_output, data)
 
455
 
 
456
    def test_mdadm_remove_error(self):
 
457
        self.mock_util.subp.side_effect = [
 
458
            ("wark", "error"),  # mdadm --detail --scan
 
459
        ]
 
460
        data = mdadm.mdadm_detail_scan()
 
461
        expected_calls = [
 
462
            call(["mdadm", "--detail", "--scan"], capture=True),
 
463
        ]
 
464
        self.mock_util.subp.assert_has_calls(expected_calls)
 
465
        self.assertEqual(None, data)
 
466
 
 
467
 
 
468
class TestBlockMdadmMdHelpers(MdadmTestBase):
 
469
    def setUp(self):
 
470
        super(TestBlockMdadmMdHelpers, self).setUp()
 
471
        self.add_patch('curtin.block.mdadm.util', 'mock_util')
 
472
        self.add_patch('curtin.block.mdadm.is_valid_device', 'mock_valid')
 
473
 
 
474
        self.mock_valid.return_value = True
 
475
        self.mock_util.lsb_release.return_value = {'codename': 'xenial'}
 
476
 
 
477
    def test_valid_mdname(self):
 
478
        mdname = "/dev/md0"
 
479
        result = mdadm.valid_mdname(mdname)
 
480
        expected_calls = [
 
481
            call(mdname)
 
482
        ]
 
483
        self.mock_valid.assert_has_calls(expected_calls)
 
484
        self.assertTrue(result)
 
485
 
 
486
    def test_valid_mdname_short(self):
 
487
        mdname = "md0"
 
488
        with self.assertRaises(ValueError):
 
489
            mdadm.valid_mdname(mdname)
 
490
 
 
491
    def test_valid_mdname_none(self):
 
492
        mdname = None
 
493
        with self.assertRaises(ValueError):
 
494
            mdadm.valid_mdname(mdname)
 
495
 
 
496
    def test_valid_mdname_not_valid_device(self):
 
497
        self.mock_valid.return_value = False
 
498
        mdname = "/dev/md0"
 
499
        with self.assertRaises(ValueError):
 
500
            mdadm.valid_mdname(mdname)
 
501
 
 
502
    @patch('curtin.block.mdadm.sys_block_path')
 
503
    @patch('curtin.block.mdadm.os.path.isfile')
 
504
    def test_md_sysfs_attr(self, mock_isfile, mock_sysblock):
 
505
        mdname = "/dev/md0"
 
506
        attr_name = 'array_state'
 
507
        sysfs_path = '/sys/class/block/{}/md/{}'.format(dev_short(mdname),
 
508
                                                        attr_name)
 
509
        mock_sysblock.side_effect = ['/sys/class/block/md0/md']
 
510
        mock_isfile.side_effect = [True]
 
511
        mdadm.md_sysfs_attr(mdname, attr_name)
 
512
        self.mock_util.load_file.assert_called_with(sysfs_path)
 
513
        mock_sysblock.assert_called_with(mdname, 'md')
 
514
        mock_isfile.assert_called_with(sysfs_path)
 
515
 
 
516
    def test_md_sysfs_attr_devname_none(self):
 
517
        mdname = None
 
518
        attr_name = 'array_state'
 
519
        with self.assertRaises(ValueError):
 
520
            mdadm.md_sysfs_attr(mdname, attr_name)
 
521
 
 
522
    def test_md_raidlevel_short(self):
 
523
        for rl in [0, 1, 5, 6, 10, 'linear', 'stripe']:
 
524
            self.assertEqual(rl, mdadm.md_raidlevel_short(rl))
 
525
            if isinstance(rl, int):
 
526
                long_rl = 'raid%d' % rl
 
527
                self.assertEqual(rl, mdadm.md_raidlevel_short(long_rl))
 
528
 
 
529
    def test_md_minimum_devices(self):
 
530
        min_to_rl = {
 
531
            2: [0, 1, 'linear', 'stripe'],
 
532
            3: [5],
 
533
            4: [6, 10],
 
534
        }
 
535
 
 
536
        for rl in [0, 1, 5, 6, 10, 'linear', 'stripe']:
 
537
            min_devs = mdadm.md_minimum_devices(rl)
 
538
            self.assertTrue(rl in min_to_rl[min_devs])
 
539
 
 
540
    def test_md_minimum_devices_invalid_rl(self):
 
541
        min_devs = mdadm.md_minimum_devices(27)
 
542
        self.assertEqual(min_devs, -1)
 
543
 
 
544
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
545
    def test_md_check_array_state_rw(self, mock_attr):
 
546
        mdname = '/dev/md0'
 
547
        mock_attr.return_value = 'clean'
 
548
        self.assertTrue(mdadm.md_check_array_state_rw(mdname))
 
549
 
 
550
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
551
    def test_md_check_array_state_rw_false(self, mock_attr):
 
552
        mdname = '/dev/md0'
 
553
        mock_attr.return_value = 'inactive'
 
554
        self.assertFalse(mdadm.md_check_array_state_rw(mdname))
 
555
 
 
556
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
557
    def test_md_check_array_state_ro(self, mock_attr):
 
558
        mdname = '/dev/md0'
 
559
        mock_attr.return_value = 'readonly'
 
560
        self.assertTrue(mdadm.md_check_array_state_ro(mdname))
 
561
 
 
562
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
563
    def test_md_check_array_state_ro_false(self, mock_attr):
 
564
        mdname = '/dev/md0'
 
565
        mock_attr.return_value = 'inactive'
 
566
        self.assertFalse(mdadm.md_check_array_state_ro(mdname))
 
567
 
 
568
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
569
    def test_md_check_array_state_error(self, mock_attr):
 
570
        mdname = '/dev/md0'
 
571
        mock_attr.return_value = 'inactive'
 
572
        self.assertTrue(mdadm.md_check_array_state_error(mdname))
 
573
 
 
574
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
575
    def test_md_check_array_state_error_false(self, mock_attr):
 
576
        mdname = '/dev/md0'
 
577
        mock_attr.return_value = 'active'
 
578
        self.assertFalse(mdadm.md_check_array_state_error(mdname))
 
579
 
 
580
    def test_md_device_key_role(self):
 
581
        devname = '/dev/vda'
 
582
        rolekey = mdadm.md_device_key_role(devname)
 
583
        self.assertEqual('MD_DEVICE_vda_ROLE', rolekey)
 
584
 
 
585
    def test_md_device_key_role_no_dev(self):
 
586
        devname = None
 
587
        with self.assertRaises(ValueError):
 
588
            mdadm.md_device_key_role(devname)
 
589
 
 
590
    def test_md_device_key_dev(self):
 
591
        devname = '/dev/vda'
 
592
        devkey = mdadm.md_device_key_dev(devname)
 
593
        self.assertEqual('MD_DEVICE_vda_DEV', devkey)
 
594
 
 
595
    def test_md_device_key_dev_no_dev(self):
 
596
        devname = None
 
597
        with self.assertRaises(ValueError):
 
598
            mdadm.md_device_key_dev(devname)
 
599
 
 
600
    @patch('curtin.block.mdadm.os.path.exists')
 
601
    @patch('curtin.block.mdadm.os.listdir')
 
602
    def tests_md_get_spares_list(self, mock_listdir, mock_exists):
 
603
        mdname = '/dev/md0'
 
604
        devices = ['dev-vda', 'dev-vdb', 'dev-vdc']
 
605
        states = ['in-sync', 'in-sync', 'spare']
 
606
 
 
607
        mock_exists.return_value = True
 
608
        mock_listdir.return_value = devices
 
609
        self.mock_util.load_file.side_effect = states
 
610
 
 
611
        sysfs_path = '/sys/class/block/md0/md/'
 
612
 
 
613
        expected_calls = []
 
614
        for d in devices:
 
615
            expected_calls.append(call(os.path.join(sysfs_path, d, 'state')))
 
616
 
 
617
        spares = mdadm.md_get_spares_list(mdname)
 
618
        self.mock_util.load_file.assert_has_calls(expected_calls)
 
619
        self.assertEqual(['/dev/vdc'], spares)
 
620
 
 
621
    @patch('curtin.block.mdadm.os.path.exists')
 
622
    def tests_md_get_spares_list_nomd(self, mock_exists):
 
623
        mdname = '/dev/md0'
 
624
        mock_exists.return_value = False
 
625
        with self.assertRaises(OSError):
 
626
            mdadm.md_get_spares_list(mdname)
 
627
 
 
628
    @patch('curtin.block.mdadm.os.path.exists')
 
629
    @patch('curtin.block.mdadm.os.listdir')
 
630
    def tests_md_get_devices_list(self, mock_listdir, mock_exists):
 
631
        mdname = '/dev/md0'
 
632
        devices = ['dev-vda', 'dev-vdb', 'dev-vdc']
 
633
        states = ['in-sync', 'in-sync', 'spare']
 
634
 
 
635
        mock_exists.return_value = True
 
636
        mock_listdir.return_value = devices
 
637
        self.mock_util.load_file.side_effect = states
 
638
 
 
639
        sysfs_path = '/sys/class/block/md0/md/'
 
640
 
 
641
        expected_calls = []
 
642
        for d in devices:
 
643
            expected_calls.append(call(os.path.join(sysfs_path, d, 'state')))
 
644
 
 
645
        devs = mdadm.md_get_devices_list(mdname)
 
646
        self.mock_util.load_file.assert_has_calls(expected_calls)
 
647
        self.assertEqual(sorted(['/dev/vda', '/dev/vdb']), sorted(devs))
 
648
 
 
649
    @patch('curtin.block.mdadm.os.path.exists')
 
650
    def tests_md_get_devices_list_nomd(self, mock_exists):
 
651
        mdname = '/dev/md0'
 
652
        mock_exists.return_value = False
 
653
        with self.assertRaises(OSError):
 
654
            mdadm.md_get_devices_list(mdname)
 
655
 
 
656
    @patch('curtin.block.mdadm.os')
 
657
    def test_md_check_array_uuid(self, mock_os):
 
658
        devname = '/dev/md0'
 
659
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
 
660
        mock_os.path.realpath.return_value = devname
 
661
        rv = mdadm.md_check_array_uuid(devname, md_uuid)
 
662
        self.assertTrue(rv)
 
663
 
 
664
    @patch('curtin.block.mdadm.os')
 
665
    def test_md_check_array_uuid_mismatch(self, mock_os):
 
666
        devname = '/dev/md0'
 
667
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
 
668
        mock_os.path.realpath.return_value = '/dev/md1'
 
669
 
 
670
        with self.assertRaises(ValueError):
 
671
            mdadm.md_check_array_uuid(devname, md_uuid)
 
672
 
 
673
    @patch('curtin.block.mdadm.mdadm_query_detail')
 
674
    def test_md_get_uuid(self, mock_query):
 
675
        mdname = '/dev/md0'
 
676
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
 
677
        mock_query.return_value = {'MD_UUID': md_uuid}
 
678
        uuid = mdadm.md_get_uuid(mdname)
 
679
        self.assertEqual(md_uuid, uuid)
 
680
 
 
681
    @patch('curtin.block.mdadm.mdadm_query_detail')
 
682
    def test_md_get_uuid_dev_none(self, mock_query):
 
683
        mdname = None
 
684
        with self.assertRaises(ValueError):
 
685
            mdadm.md_get_uuid(mdname)
 
686
 
 
687
    def test_md_check_raid_level(self):
 
688
        for rl in mdadm.VALID_RAID_LEVELS:
 
689
            self.assertTrue(mdadm.md_check_raidlevel(rl))
 
690
 
 
691
    def test_md_check_raid_level_bad(self):
 
692
        bogus = '27'
 
693
        self.assertTrue(bogus not in mdadm.VALID_RAID_LEVELS)
 
694
        with self.assertRaises(ValueError):
 
695
            mdadm.md_check_raidlevel(bogus)
 
696
 
 
697
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
698
    def test_md_check_array_state(self, mock_attr):
 
699
        mdname = '/dev/md0'
 
700
        mock_attr.side_effect = [
 
701
            'clean',  # array_state
 
702
            '0',  # degraded
 
703
            'idle',  # sync_action
 
704
        ]
 
705
        self.assertTrue(mdadm.md_check_array_state(mdname))
 
706
 
 
707
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
708
    def test_md_check_array_state_norw(self, mock_attr):
 
709
        mdname = '/dev/md0'
 
710
        mock_attr.side_effect = [
 
711
            'suspended',  # array_state
 
712
            '0',  # degraded
 
713
            'idle',  # sync_action
 
714
        ]
 
715
        with self.assertRaises(ValueError):
 
716
            mdadm.md_check_array_state(mdname)
 
717
 
 
718
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
719
    def test_md_check_array_state_degraded(self, mock_attr):
 
720
        mdname = '/dev/md0'
 
721
        mock_attr.side_effect = [
 
722
            'clean',  # array_state
 
723
            '1',  # degraded
 
724
            'idle',  # sync_action
 
725
        ]
 
726
        with self.assertRaises(ValueError):
 
727
            mdadm.md_check_array_state(mdname)
 
728
 
 
729
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
730
    def test_md_check_array_state_degraded_empty(self, mock_attr):
 
731
        mdname = '/dev/md0'
 
732
        mock_attr.side_effect = [
 
733
            'clean',  # array_state
 
734
            '',  # unknown
 
735
            'idle',  # sync_action
 
736
        ]
 
737
        with self.assertRaises(ValueError):
 
738
            mdadm.md_check_array_state(mdname)
 
739
 
 
740
    @patch('curtin.block.mdadm.md_sysfs_attr')
 
741
    def test_md_check_array_state_sync(self, mock_attr):
 
742
        mdname = '/dev/md0'
 
743
        mock_attr.side_effect = [
 
744
            'clean',  # array_state
 
745
            '0',  # degraded
 
746
            'recovery',  # sync_action
 
747
        ]
 
748
        with self.assertRaises(ValueError):
 
749
            mdadm.md_check_array_state(mdname)
 
750
 
 
751
    @patch('curtin.block.mdadm.md_check_array_uuid')
 
752
    @patch('curtin.block.mdadm.md_get_uuid')
 
753
    def test_md_check_uuid(self, mock_guuid, mock_ckuuid):
 
754
        mdname = '/dev/md0'
 
755
        mock_guuid.return_value = '93a73e10:427f280b:b7076c02:204b8f7a'
 
756
        mock_ckuuid.return_value = True
 
757
 
 
758
        rv = mdadm.md_check_uuid(mdname)
 
759
        self.assertTrue(rv)
 
760
 
 
761
    @patch('curtin.block.mdadm.md_check_array_uuid')
 
762
    @patch('curtin.block.mdadm.md_get_uuid')
 
763
    def test_md_check_uuid_nouuid(self, mock_guuid, mock_ckuuid):
 
764
        mdname = '/dev/md0'
 
765
        mock_guuid.return_value = None
 
766
        with self.assertRaises(ValueError):
 
767
            mdadm.md_check_uuid(mdname)
 
768
 
 
769
    @patch('curtin.block.mdadm.md_get_devices_list')
 
770
    def test_md_check_devices(self, mock_devlist):
 
771
        mdname = '/dev/md0'
 
772
        devices = ['/dev/vdc', '/dev/vdd']
 
773
 
 
774
        mock_devlist.return_value = devices
 
775
        rv = mdadm.md_check_devices(mdname, devices)
 
776
        self.assertEqual(rv, None)
 
777
 
 
778
    @patch('curtin.block.mdadm.md_get_devices_list')
 
779
    def test_md_check_devices_wrong_devs(self, mock_devlist):
 
780
        mdname = '/dev/md0'
 
781
        devices = ['/dev/vdc', '/dev/vdd']
 
782
 
 
783
        mock_devlist.return_value = ['/dev/sda']
 
784
        with self.assertRaises(ValueError):
 
785
            mdadm.md_check_devices(mdname, devices)
 
786
 
 
787
    def test_md_check_devices_no_devs(self):
 
788
        mdname = '/dev/md0'
 
789
        devices = []
 
790
 
 
791
        with self.assertRaises(ValueError):
 
792
            mdadm.md_check_devices(mdname, devices)
 
793
 
 
794
    @patch('curtin.block.mdadm.md_get_spares_list')
 
795
    def test_md_check_spares(self, mock_devlist):
 
796
        mdname = '/dev/md0'
 
797
        spares = ['/dev/vdc', '/dev/vdd']
 
798
 
 
799
        mock_devlist.return_value = spares
 
800
        rv = mdadm.md_check_spares(mdname, spares)
 
801
        self.assertEqual(rv, None)
 
802
 
 
803
    @patch('curtin.block.mdadm.md_get_spares_list')
 
804
    def test_md_check_spares_wrong_devs(self, mock_devlist):
 
805
        mdname = '/dev/md0'
 
806
        spares = ['/dev/vdc', '/dev/vdd']
 
807
 
 
808
        mock_devlist.return_value = ['/dev/sda']
 
809
        with self.assertRaises(ValueError):
 
810
            mdadm.md_check_spares(mdname, spares)
 
811
 
 
812
    @patch('curtin.block.mdadm.mdadm_examine')
 
813
    @patch('curtin.block.mdadm.mdadm_query_detail')
 
814
    @patch('curtin.block.mdadm.md_get_uuid')
 
815
    def test_md_check_array_membership(self, mock_uuid, mock_query,
 
816
                                       mock_examine):
 
817
        mdname = '/dev/md0'
 
818
        devices = ['/dev/vda', '/dev/vdb', '/dev/vdc', '/dev/vdd']
 
819
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
 
820
        md_dict = {'MD_UUID': md_uuid}
 
821
        mock_query.return_value = md_dict
 
822
        mock_uuid.return_value = md_uuid
 
823
        mock_examine.side_effect = [md_dict] * len(devices)
 
824
        expected_calls = []
 
825
        for dev in devices:
 
826
            expected_calls.append(call(dev, export=False))
 
827
 
 
828
        rv = mdadm.md_check_array_membership(mdname, devices)
 
829
 
 
830
        self.assertEqual(rv, None)
 
831
        mock_uuid.assert_has_calls([call(mdname)])
 
832
        mock_examine.assert_has_calls(expected_calls)
 
833
 
 
834
    @patch('curtin.block.mdadm.mdadm_examine')
 
835
    @patch('curtin.block.mdadm.mdadm_query_detail')
 
836
    @patch('curtin.block.mdadm.md_get_uuid')
 
837
    def test_md_check_array_membership_bad_dev(self, mock_uuid, mock_query,
 
838
                                               mock_examine):
 
839
        mdname = '/dev/md0'
 
840
        devices = ['/dev/vda', '/dev/vdb', '/dev/vdc', '/dev/vdd']
 
841
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
 
842
        md_dict = {'MD_UUID': md_uuid}
 
843
        mock_query.return_value = md_dict
 
844
        mock_uuid.return_value = md_uuid
 
845
        mock_examine.side_effect = [
 
846
            md_dict,
 
847
            {},
 
848
            md_dict,
 
849
            md_dict,
 
850
        ]  # one device isn't a member
 
851
 
 
852
        with self.assertRaises(ValueError):
 
853
            mdadm.md_check_array_membership(mdname, devices)
 
854
 
 
855
    @patch('curtin.block.mdadm.mdadm_examine')
 
856
    @patch('curtin.block.mdadm.mdadm_query_detail')
 
857
    @patch('curtin.block.mdadm.md_get_uuid')
 
858
    def test_md_check_array_membership_wrong_array(self, mock_uuid, mock_query,
 
859
                                                   mock_examine):
 
860
        mdname = '/dev/md0'
 
861
        devices = ['/dev/vda', '/dev/vdb', '/dev/vdc', '/dev/vdd']
 
862
        md_uuid = '93a73e10:427f280b:b7076c02:204b8f7a'
 
863
        md_dict = {'MD_UUID': '11111111:427f280b:b7076c02:204b8f7a'}
 
864
        mock_query.return_value = md_dict
 
865
        mock_uuid.return_value = md_uuid
 
866
        mock_examine.side_effect = [md_dict] * len(devices)
 
867
 
 
868
        with self.assertRaises(ValueError):
 
869
            mdadm.md_check_array_membership(mdname, devices)
 
870
 
 
871
    @patch('curtin.block.mdadm.md_check_array_membership')
 
872
    @patch('curtin.block.mdadm.md_check_spares')
 
873
    @patch('curtin.block.mdadm.md_check_devices')
 
874
    @patch('curtin.block.mdadm.md_check_uuid')
 
875
    @patch('curtin.block.mdadm.md_check_raidlevel')
 
876
    @patch('curtin.block.mdadm.md_check_array_state')
 
877
    def test_md_check_all_good(self, mock_array, mock_raid, mock_uuid,
 
878
                               mock_dev, mock_spare, mock_member):
 
879
        md_devname = '/dev/md0'
 
880
        raidlevel = 1
 
881
        devices = ['/dev/vda', '/dev/vdb']
 
882
        spares = ['/dev/vdc']
 
883
 
 
884
        mock_array.return_value = None
 
885
        mock_raid.return_value = None
 
886
        mock_uuid.return_value = None
 
887
        mock_dev.return_value = None
 
888
        mock_spare.return_value = None
 
889
        mock_member.return_value = None
 
890
 
 
891
        mdadm.md_check(md_devname, raidlevel, devices=devices, spares=spares)
 
892
 
 
893
        mock_array.assert_has_calls([call(md_devname)])
 
894
        mock_raid.assert_has_calls([call(raidlevel)])
 
895
        mock_uuid.assert_has_calls([call(md_devname)])
 
896
        mock_dev.assert_has_calls([call(md_devname, devices)])
 
897
        mock_spare.assert_has_calls([call(md_devname, spares)])
 
898
        mock_member.assert_has_calls([call(md_devname, devices + spares)])
 
899
 
 
900
    def test_md_check_all_good_devshort(self):
 
901
        md_devname = 'md0'
 
902
        raidlevel = 1
 
903
        devices = ['/dev/vda', '/dev/vdb']
 
904
        spares = ['/dev/vdc']
 
905
 
 
906
        with self.assertRaises(ValueError):
 
907
            mdadm.md_check(md_devname, raidlevel, devices=devices,
 
908
                           spares=spares)
 
909
 
 
910
# vi: ts=4 expandtab syntax=python