Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.block.bdev_unittest.py @ d41efc42

History | View | Annotate | Download (23 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Script for unittesting the bdev module"""
23

    
24

    
25
import os
26
import random
27
import unittest
28

    
29
from ganeti import compat
30
from ganeti import constants
31
from ganeti import errors
32
from ganeti import objects
33
from ganeti import utils
34
from ganeti.block import bdev
35
from ganeti.block import drbd
36

    
37
import testutils
38

    
39

    
40
class TestDRBD8(testutils.GanetiTestCase):
41
  def testGetVersion(self):
42
    data = [
43
      ["version: 8.0.12 (api:76/proto:86-91)"],
44
      ["version: 8.2.7 (api:88/proto:0-100)"],
45
      ["version: 8.3.7.49 (api:188/proto:13-191)"],
46
    ]
47
    result = [
48
      {
49
      "k_major": 8,
50
      "k_minor": 0,
51
      "k_point": 12,
52
      "api": 76,
53
      "proto": 86,
54
      "proto2": "91",
55
      },
56
      {
57
      "k_major": 8,
58
      "k_minor": 2,
59
      "k_point": 7,
60
      "api": 88,
61
      "proto": 0,
62
      "proto2": "100",
63
      },
64
      {
65
      "k_major": 8,
66
      "k_minor": 3,
67
      "k_point": 7,
68
      "api": 188,
69
      "proto": 13,
70
      "proto2": "191",
71
      }
72
    ]
73
    for d,r in zip(data, result):
74
      info = drbd.DRBD8Info.CreateFromLines(d)
75
      self.assertEqual(info.GetVersion(), r)
76

    
77

    
78
class TestDRBD8Runner(testutils.GanetiTestCase):
79
  """Testing case for drbd.DRBD8"""
80

    
81
  @staticmethod
82
  def _has_disk(data, dname, mname):
83
    """Check local disk corectness"""
84
    retval = (
85
      "local_dev" in data and
86
      data["local_dev"] == dname and
87
      "meta_dev" in data and
88
      data["meta_dev"] == mname and
89
      "meta_index" in data and
90
      data["meta_index"] == 0
91
      )
92
    return retval
93

    
94
  @staticmethod
95
  def _has_net(data, local, remote):
96
    """Check network connection parameters"""
97
    retval = (
98
      "local_addr" in data and
99
      data["local_addr"] == local and
100
      "remote_addr" in data and
101
      data["remote_addr"] == remote
102
      )
103
    return retval
104

    
105
  def testParserCreation(self):
106
    """Test drbdsetup show parser creation"""
107
    drbd.DRBD8._GetShowParser()
108

    
109
  def testParser80(self):
110
    """Test drbdsetup show parser for disk and network version 8.0"""
111
    data = testutils.ReadTestData("bdev-drbd-8.0.txt")
112
    result = drbd.DRBD8._GetDevInfo(data)
113
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
114
                                   "/dev/xenvg/test.meta"),
115
                    "Wrong local disk info")
116
    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
117
                                  ("192.0.2.2", 11000)),
118
                    "Wrong network info (8.0.x)")
119

    
120
  def testParser83(self):
121
    """Test drbdsetup show parser for disk and network version 8.3"""
122
    data = testutils.ReadTestData("bdev-drbd-8.3.txt")
123
    result = drbd.DRBD8._GetDevInfo(data)
124
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
125
                                   "/dev/xenvg/test.meta"),
126
                    "Wrong local disk info")
127
    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
128
                                  ("192.0.2.2", 11000)),
129
                    "Wrong network info (8.0.x)")
130

    
131
  def testParserNetIP4(self):
132
    """Test drbdsetup show parser for IPv4 network"""
133
    data = testutils.ReadTestData("bdev-drbd-net-ip4.txt")
134
    result = drbd.DRBD8._GetDevInfo(data)
135
    self.failUnless(("local_dev" not in result and
136
                     "meta_dev" not in result and
137
                     "meta_index" not in result),
138
                    "Should not find local disk info")
139
    self.failUnless(self._has_net(result, ("192.0.2.1", 11002),
140
                                  ("192.0.2.2", 11002)),
141
                    "Wrong network info (IPv4)")
142

    
143
  def testParserNetIP6(self):
144
    """Test drbdsetup show parser for IPv6 network"""
145
    data = testutils.ReadTestData("bdev-drbd-net-ip6.txt")
146
    result = drbd.DRBD8._GetDevInfo(data)
147
    self.failUnless(("local_dev" not in result and
148
                     "meta_dev" not in result and
149
                     "meta_index" not in result),
150
                    "Should not find local disk info")
151
    self.failUnless(self._has_net(result, ("2001:db8:65::1", 11048),
152
                                  ("2001:db8:66::1", 11048)),
153
                    "Wrong network info (IPv6)")
154

    
155
  def testParserDisk(self):
156
    """Test drbdsetup show parser for disk"""
157
    data = testutils.ReadTestData("bdev-drbd-disk.txt")
158
    result = drbd.DRBD8._GetDevInfo(data)
159
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
160
                                   "/dev/xenvg/test.meta"),
161
                    "Wrong local disk info")
162
    self.failUnless(("local_addr" not in result and
163
                     "remote_addr" not in result),
164
                    "Should not find network info")
165

    
166
  def testBarriersOptions(self):
167
    """Test class method that generates drbdsetup options for disk barriers"""
168
    # Tests that should fail because of wrong version/options combinations
169
    should_fail = [
170
      (8, 0, 12, "bfd", True),
171
      (8, 0, 12, "fd", False),
172
      (8, 0, 12, "b", True),
173
      (8, 2, 7, "bfd", True),
174
      (8, 2, 7, "b", True)
175
    ]
176

    
177
    for vmaj, vmin, vrel, opts, meta in should_fail:
178
      self.assertRaises(errors.BlockDeviceError,
179
                        drbd.DRBD8._ComputeDiskBarrierArgs,
180
                        vmaj, vmin, vrel, opts, meta)
181

    
182
    # get the valid options from the frozenset(frozenset()) in constants.
183
    valid_options = [list(x)[0] for x in constants.DRBD_VALID_BARRIER_OPT]
184

    
185
    # Versions that do not support anything
186
    for vmaj, vmin, vrel in ((8, 0, 0), (8, 0, 11), (8, 2, 6)):
187
      for opts in valid_options:
188
        self.assertRaises(errors.BlockDeviceError,
189
                          drbd.DRBD8._ComputeDiskBarrierArgs,
190
                          vmaj, vmin, vrel, opts, True)
191

    
192
    # Versions with partial support (testing only options that are supported)
193
    tests = [
194
      (8, 0, 12, "n", False, []),
195
      (8, 0, 12, "n", True, ["--no-md-flushes"]),
196
      (8, 2, 7, "n", False, []),
197
      (8, 2, 7, "fd", False, ["--no-disk-flushes", "--no-disk-drain"]),
198
      (8, 0, 12, "n", True, ["--no-md-flushes"]),
199
      ]
200

    
201
    # Versions that support everything
202
    for vmaj, vmin, vrel in ((8, 3, 0), (8, 3, 12)):
203
      tests.append((vmaj, vmin, vrel, "bfd", True,
204
                    ["--no-disk-barrier", "--no-disk-drain",
205
                     "--no-disk-flushes", "--no-md-flushes"]))
206
      tests.append((vmaj, vmin, vrel, "n", False, []))
207
      tests.append((vmaj, vmin, vrel, "b", True,
208
                    ["--no-disk-barrier", "--no-md-flushes"]))
209
      tests.append((vmaj, vmin, vrel, "fd", False,
210
                    ["--no-disk-flushes", "--no-disk-drain"]))
211
      tests.append((vmaj, vmin, vrel, "n", True, ["--no-md-flushes"]))
212

    
213
    # Test execution
214
    for test in tests:
215
      vmaj, vmin, vrel, disabled_barriers, disable_meta_flush, expected = test
216
      args = \
217
        drbd.DRBD8._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
218
                                           disabled_barriers,
219
                                           disable_meta_flush)
220
      self.failUnless(set(args) == set(expected),
221
                      "For test %s, got wrong results %s" % (test, args))
222

    
223
    # Unsupported or invalid versions
224
    for vmaj, vmin, vrel in ((0, 7, 25), (9, 0, 0), (7, 0, 0), (8, 4, 0)):
225
      self.assertRaises(errors.BlockDeviceError,
226
                        drbd.DRBD8._ComputeDiskBarrierArgs,
227
                        vmaj, vmin, vrel, "n", True)
228

    
229
    # Invalid options
230
    for option in ("", "c", "whatever", "nbdfc", "nf"):
231
      self.assertRaises(errors.BlockDeviceError,
232
                        drbd.DRBD8._ComputeDiskBarrierArgs,
233
                        8, 3, 11, option, True)
234

    
235

    
236
class TestDRBD8Status(testutils.GanetiTestCase):
237
  """Testing case for DRBD8 /proc status"""
238

    
239
  def setUp(self):
240
    """Read in txt data"""
241
    testutils.GanetiTestCase.setUp(self)
242
    proc_data = testutils.TestDataFilename("proc_drbd8.txt")
243
    proc80e_data = testutils.TestDataFilename("proc_drbd80-emptyline.txt")
244
    proc83_data = testutils.TestDataFilename("proc_drbd83.txt")
245
    proc83_sync_data = testutils.TestDataFilename("proc_drbd83_sync.txt")
246
    proc83_sync_krnl_data = \
247
      testutils.TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
248

    
249
    self.proc80ev_data = \
250
      testutils.TestDataFilename("proc_drbd80-emptyversion.txt")
251

    
252
    self.drbd_info = drbd.DRBD8Info.CreateFromFile(filename=proc_data)
253
    self.drbd_info80e = drbd.DRBD8Info.CreateFromFile(filename=proc80e_data)
254
    self.drbd_info83 = drbd.DRBD8Info.CreateFromFile(filename=proc83_data)
255
    self.drbd_info83_sync = \
256
      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_data)
257
    self.drbd_info83_sync_krnl = \
258
      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_krnl_data)
259

    
260
  def testIOErrors(self):
261
    """Test handling of errors while reading the proc file."""
262
    temp_file = self._CreateTempFile()
263
    os.unlink(temp_file)
264
    self.failUnlessRaises(errors.BlockDeviceError,
265
                          drbd.DRBD8Info.CreateFromFile, filename=temp_file)
266

    
267
  def testHelper(self):
268
    """Test reading usermode_helper in /sys."""
269
    sys_drbd_helper = testutils.TestDataFilename("sys_drbd_usermode_helper.txt")
270
    drbd_helper = drbd.DRBD8.GetUsermodeHelper(filename=sys_drbd_helper)
271
    self.failUnlessEqual(drbd_helper, "/bin/true")
272

    
273
  def testHelperIOErrors(self):
274
    """Test handling of errors while reading usermode_helper in /sys."""
275
    temp_file = self._CreateTempFile()
276
    os.unlink(temp_file)
277
    self.failUnlessRaises(errors.BlockDeviceError,
278
                          drbd.DRBD8.GetUsermodeHelper, filename=temp_file)
279

    
280
  def testMinorNotFound(self):
281
    """Test not-found-minor in /proc"""
282
    self.failUnless(not self.drbd_info.HasMinorStatus(9))
283
    self.failUnless(not self.drbd_info83.HasMinorStatus(9))
284
    self.failUnless(not self.drbd_info80e.HasMinorStatus(3))
285

    
286
  def testLineNotMatch(self):
287
    """Test wrong line passed to drbd.DRBD8Status"""
288
    self.assertRaises(errors.BlockDeviceError, drbd.DRBD8Status, "foo")
289

    
290
  def testMinor0(self):
291
    """Test connected, primary device"""
292
    for info in [self.drbd_info, self.drbd_info83]:
293
      stats = info.GetMinorStatus(0)
294
      self.failUnless(stats.is_in_use)
295
      self.failUnless(stats.is_connected and stats.is_primary and
296
                      stats.peer_secondary and stats.is_disk_uptodate)
297

    
298
  def testMinor1(self):
299
    """Test connected, secondary device"""
300
    for info in [self.drbd_info, self.drbd_info83]:
301
      stats = info.GetMinorStatus(1)
302
      self.failUnless(stats.is_in_use)
303
      self.failUnless(stats.is_connected and stats.is_secondary and
304
                      stats.peer_primary and stats.is_disk_uptodate)
305

    
306
  def testMinor2(self):
307
    """Test unconfigured device"""
308
    for info in [self.drbd_info, self.drbd_info83, self.drbd_info80e]:
309
      stats = info.GetMinorStatus(2)
310
      self.failIf(stats.is_in_use)
311

    
312
  def testMinor4(self):
313
    """Test WFconn device"""
314
    for info in [self.drbd_info, self.drbd_info83]:
315
      stats = info.GetMinorStatus(4)
316
      self.failUnless(stats.is_in_use)
317
      self.failUnless(stats.is_wfconn and stats.is_primary and
318
                      stats.rrole == "Unknown" and
319
                      stats.is_disk_uptodate)
320

    
321
  def testMinor6(self):
322
    """Test diskless device"""
323
    for info in [self.drbd_info, self.drbd_info83]:
324
      stats = info.GetMinorStatus(6)
325
      self.failUnless(stats.is_in_use)
326
      self.failUnless(stats.is_connected and stats.is_secondary and
327
                      stats.peer_primary and stats.is_diskless)
328

    
329
  def testMinor8(self):
330
    """Test standalone device"""
331
    for info in [self.drbd_info, self.drbd_info83]:
332
      stats = info.GetMinorStatus(8)
333
      self.failUnless(stats.is_in_use)
334
      self.failUnless(stats.is_standalone and
335
                      stats.rrole == "Unknown" and
336
                      stats.is_disk_uptodate)
337

    
338
  def testDRBD83SyncFine(self):
339
    stats = self.drbd_info83_sync.GetMinorStatus(3)
340
    self.failUnless(stats.is_in_resync)
341
    self.failUnless(stats.sync_percent is not None)
342

    
343
  def testDRBD83SyncBroken(self):
344
    stats = self.drbd_info83_sync_krnl.GetMinorStatus(3)
345
    self.failUnless(stats.is_in_resync)
346
    self.failUnless(stats.sync_percent is not None)
347

    
348
  def testDRBDEmptyVersion(self):
349
    self.assertRaises(errors.BlockDeviceError,
350
                      drbd.DRBD8Info.CreateFromFile,
351
                      filename=self.proc80ev_data)
352

    
353

    
354
class TestRADOSBlockDevice(testutils.GanetiTestCase):
355
  def setUp(self):
356
    """Set up input data"""
357
    testutils.GanetiTestCase.setUp(self)
358

    
359
    self.plain_output_old_ok = \
360
      testutils.ReadTestData("bdev-rbd/plain_output_old_ok.txt")
361
    self.plain_output_old_no_matches = \
362
      testutils.ReadTestData("bdev-rbd/plain_output_old_no_matches.txt")
363
    self.plain_output_old_extra_matches = \
364
      testutils.ReadTestData("bdev-rbd/plain_output_old_extra_matches.txt")
365
    self.plain_output_old_empty = \
366
      testutils.ReadTestData("bdev-rbd/plain_output_old_empty.txt")
367
    self.plain_output_new_ok = \
368
      testutils.ReadTestData("bdev-rbd/plain_output_new_ok.txt")
369
    self.plain_output_new_no_matches = \
370
      testutils.ReadTestData("bdev-rbd/plain_output_new_no_matches.txt")
371
    self.plain_output_new_extra_matches = \
372
      testutils.ReadTestData("bdev-rbd/plain_output_new_extra_matches.txt")
373
    # This file is completely empty, and as such it's not shipped.
374
    self.plain_output_new_empty = ""
375
    self.json_output_ok = testutils.ReadTestData("bdev-rbd/json_output_ok.txt")
376
    self.json_output_no_matches = \
377
      testutils.ReadTestData("bdev-rbd/json_output_no_matches.txt")
378
    self.json_output_extra_matches = \
379
      testutils.ReadTestData("bdev-rbd/json_output_extra_matches.txt")
380
    self.json_output_empty = \
381
      testutils.ReadTestData("bdev-rbd/json_output_empty.txt")
382
    self.output_invalid = testutils.ReadTestData("bdev-rbd/output_invalid.txt")
383

    
384
    self.volume_name = "d7ab910a-4933-4ffe-88d0-faf2ce31390a.rbd.disk0"
385

    
386
  def test_ParseRbdShowmappedJson(self):
387
    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedJson
388

    
389
    self.assertEqual(parse_function(self.json_output_ok, self.volume_name),
390
                     "/dev/rbd3")
391
    self.assertEqual(parse_function(self.json_output_empty, self.volume_name),
392
                     None)
393
    self.assertEqual(parse_function(self.json_output_no_matches,
394
                     self.volume_name), None)
395
    self.assertRaises(errors.BlockDeviceError, parse_function,
396
                      self.json_output_extra_matches, self.volume_name)
397
    self.assertRaises(errors.BlockDeviceError, parse_function,
398
                      self.output_invalid, self.volume_name)
399

    
400
  def test_ParseRbdShowmappedPlain(self):
401
    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedPlain
402

    
403
    self.assertEqual(parse_function(self.plain_output_new_ok,
404
                     self.volume_name), "/dev/rbd3")
405
    self.assertEqual(parse_function(self.plain_output_old_ok,
406
                     self.volume_name), "/dev/rbd3")
407
    self.assertEqual(parse_function(self.plain_output_new_empty,
408
                     self.volume_name), None)
409
    self.assertEqual(parse_function(self.plain_output_old_empty,
410
                     self.volume_name), None)
411
    self.assertEqual(parse_function(self.plain_output_new_no_matches,
412
                     self.volume_name), None)
413
    self.assertEqual(parse_function(self.plain_output_old_no_matches,
414
                     self.volume_name), None)
415
    self.assertRaises(errors.BlockDeviceError, parse_function,
416
                      self.plain_output_new_extra_matches, self.volume_name)
417
    self.assertRaises(errors.BlockDeviceError, parse_function,
418
                      self.plain_output_old_extra_matches, self.volume_name)
419
    self.assertRaises(errors.BlockDeviceError, parse_function,
420
                      self.output_invalid, self.volume_name)
421

    
422
class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
423
  def testPaths(self):
424
    paths = bdev._GetForbiddenFileStoragePaths()
425

    
426
    for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
427
      self.assertTrue(path in paths)
428

    
429
    self.assertEqual(set(map(os.path.normpath, paths)), paths)
430

    
431
  def test(self):
432
    vfsp = bdev._ComputeWrongFileStoragePaths
433
    self.assertEqual(vfsp([]), [])
434
    self.assertEqual(vfsp(["/tmp"]), [])
435
    self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
436
    self.assertEqual(vfsp(["/bin"]), ["/bin"])
437
    self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
438
                     ["/usr/sbin/vim"])
439

    
440

    
441
class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
442
  def test(self):
443
    tmpfile = self._CreateTempFile()
444

    
445
    utils.WriteFile(tmpfile, data="""
446
      /tmp
447
      x/y///z/relative
448
      # This is a test file
449
      /srv/storage
450
      /bin
451
      /usr/local/lib32/
452
      relative/path
453
      """)
454

    
455
    self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
456
      "/bin",
457
      "/usr/local/lib32",
458
      "relative/path",
459
      "x/y/z/relative",
460
      ])
461

    
462

    
463
class TestCheckFileStoragePathInternal(unittest.TestCase):
464
  def testNonAbsolute(self):
465
    for i in ["", "tmp", "foo/bar/baz"]:
466
      self.assertRaises(errors.FileStoragePathError,
467
                        bdev._CheckFileStoragePath, i, ["/tmp"])
468

    
469
    self.assertRaises(errors.FileStoragePathError,
470
                      bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
471

    
472
  def testNoAllowed(self):
473
    self.assertRaises(errors.FileStoragePathError,
474
                      bdev._CheckFileStoragePath, "/tmp", [])
475

    
476
  def testNoAdditionalPathComponent(self):
477
    self.assertRaises(errors.FileStoragePathError,
478
                      bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
479

    
480
  def testAllowed(self):
481
    bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
482
    bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
483

    
484

    
485
class TestCheckFileStoragePath(testutils.GanetiTestCase):
486
  def testNonExistantFile(self):
487
    filename = "/tmp/this/file/does/not/exist"
488
    assert not os.path.exists(filename)
489
    self.assertRaises(errors.FileStoragePathError,
490
                      bdev.CheckFileStoragePath, "/bin/", _filename=filename)
491
    self.assertRaises(errors.FileStoragePathError,
492
                      bdev.CheckFileStoragePath, "/srv/file-storage",
493
                      _filename=filename)
494

    
495
  def testAllowedPath(self):
496
    tmpfile = self._CreateTempFile()
497

    
498
    utils.WriteFile(tmpfile, data="""
499
      /srv/storage
500
      """)
501

    
502
    bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
503

    
504
    # No additional path component
505
    self.assertRaises(errors.FileStoragePathError,
506
                      bdev.CheckFileStoragePath, "/srv/storage",
507
                      _filename=tmpfile)
508

    
509
    # Forbidden path
510
    self.assertRaises(errors.FileStoragePathError,
511
                      bdev.CheckFileStoragePath, "/usr/lib64/xyz",
512
                      _filename=tmpfile)
513

    
514

    
515
class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
516
  def testDevNull(self):
517
    self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
518

    
519
  def testNonExistantFile(self):
520
    filename = "/tmp/this/file/does/not/exist"
521
    assert not os.path.exists(filename)
522
    self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
523

    
524
  def test(self):
525
    tmpfile = self._CreateTempFile()
526

    
527
    utils.WriteFile(tmpfile, data="""
528
      # This is a test file
529
      /tmp
530
      /srv/storage
531
      relative/path
532
      """)
533

    
534
    self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
535
      "/tmp",
536
      "/srv/storage",
537
      "relative/path",
538
      ])
539

    
540

    
541
class TestExclusiveStoragePvs(unittest.TestCase):
542
  """Test cases for functions dealing with LVM PV and exclusive storage"""
543
  # Allowance for rounding
544
  _EPS = 1e-4
545
  _MARGIN = constants.PART_MARGIN + constants.PART_RESERVED + _EPS
546

    
547
  @staticmethod
548
  def _GenerateRandomPvInfo(rnd, name, vg):
549
    # Granularity is .01 MiB
550
    size = rnd.randint(1024 * 100, 10 * 1024 * 1024 * 100)
551
    if rnd.choice([False, True]):
552
      free = float(rnd.randint(0, size)) / 100.0
553
    else:
554
      free = float(size) / 100.0
555
    size = float(size) / 100.0
556
    attr = "a-"
557
    return objects.LvmPvInfo(name=name, vg_name=vg, size=size, free=free,
558
                             attributes=attr)
559

    
560
  def testGetStdPvSize(self):
561
    """Test cases for bdev.LogicalVolume._GetStdPvSize()"""
562
    rnd = random.Random(9517)
563
    for _ in range(0, 50):
564
      # Identical volumes
565
      pvi = self._GenerateRandomPvInfo(rnd, "disk", "myvg")
566
      onesize = bdev.LogicalVolume._GetStdPvSize([pvi])
567
      self.assertTrue(onesize <= pvi.size)
568
      self.assertTrue(onesize > pvi.size * (1 - self._MARGIN))
569
      for length in range(2, 10):
570
        n_size = bdev.LogicalVolume._GetStdPvSize([pvi] * length)
571
        self.assertEqual(onesize, n_size)
572

    
573
      # Mixed volumes
574
      for length in range(1, 10):
575
        pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")
576
                  for _ in range(0, length)]
577
        std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
578
        self.assertTrue(compat.all(std_size <= pvi.size for pvi in pvlist))
579
        self.assertTrue(compat.any(std_size > pvi.size * (1 - self._MARGIN)
580
                                   for pvi in pvlist))
581
        pvlist.append(pvlist[0])
582
        p1_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
583
        self.assertEqual(std_size, p1_size)
584

    
585
  def testComputeNumPvs(self):
586
    """Test cases for bdev.LogicalVolume._ComputeNumPvs()"""
587
    rnd = random.Random(8067)
588
    for _ in range(0, 1000):
589
      pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")]
590
      lv_size = float(rnd.randint(10 * 100, 1024 * 1024 * 100)) / 100.0
591
      num_pv = bdev.LogicalVolume._ComputeNumPvs(lv_size, pvlist)
592
      std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
593
      self.assertTrue(num_pv >= 1)
594
      self.assertTrue(num_pv * std_size >= lv_size)
595
      self.assertTrue((num_pv - 1) * std_size < lv_size * (1 + self._EPS))
596

    
597
  def testGetEmptyPvNames(self):
598
    """Test cases for bdev.LogicalVolume._GetEmptyPvNames()"""
599
    rnd = random.Random(21126)
600
    for _ in range(0, 100):
601
      num_pvs = rnd.randint(1, 20)
602
      pvlist = [self._GenerateRandomPvInfo(rnd, "disk%d" % n, "myvg")
603
                for n in range(0, num_pvs)]
604
      for num_req in range(1, num_pvs + 2):
605
        epvs = bdev.LogicalVolume._GetEmptyPvNames(pvlist, num_req)
606
        epvs_set = compat.UniqueFrozenset(epvs)
607
        if len(epvs) > 1:
608
          self.assertEqual(len(epvs), len(epvs_set))
609
        for pvi in pvlist:
610
          if pvi.name in epvs_set:
611
            self.assertEqual(pvi.size, pvi.free)
612
          else:
613
            # There should be no remaining empty PV when less than the
614
            # requeste number of PVs has been returned
615
            self.assertTrue(len(epvs) == num_req or pvi.free != pvi.size)
616

    
617

    
618
if __name__ == "__main__":
619
  testutils.GanetiTestProgram()