Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.block.bdev_unittest.py @ 2fe690f1

History | View | Annotate | Download (22.7 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.drbd_info = drbd.DRBD8Info.CreateFromFile(filename=proc_data)
250
    self.drbd_info80e = drbd.DRBD8Info.CreateFromFile(filename=proc80e_data)
251
    self.drbd_info83 = drbd.DRBD8Info.CreateFromFile(filename=proc83_data)
252
    self.drbd_info83_sync = \
253
      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_data)
254
    self.drbd_info83_sync_krnl = \
255
      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_krnl_data)
256

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

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

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

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

    
283
  def testLineNotMatch(self):
284
    """Test wrong line passed to drbd.DRBD8Status"""
285
    self.assertRaises(errors.BlockDeviceError, drbd.DRBD8Status, "foo")
286

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

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

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

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

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

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

    
335
  def testDRBD83SyncFine(self):
336
    stats = self.drbd_info83_sync.GetMinorStatus(3)
337
    self.failUnless(stats.is_in_resync)
338
    self.failUnless(stats.sync_percent is not None)
339

    
340
  def testDRBD83SyncBroken(self):
341
    stats = self.drbd_info83_sync_krnl.GetMinorStatus(3)
342
    self.failUnless(stats.is_in_resync)
343
    self.failUnless(stats.sync_percent is not None)
344

    
345

    
346
class TestRADOSBlockDevice(testutils.GanetiTestCase):
347
  def setUp(self):
348
    """Set up input data"""
349
    testutils.GanetiTestCase.setUp(self)
350

    
351
    self.plain_output_old_ok = \
352
      testutils.ReadTestData("bdev-rbd/plain_output_old_ok.txt")
353
    self.plain_output_old_no_matches = \
354
      testutils.ReadTestData("bdev-rbd/plain_output_old_no_matches.txt")
355
    self.plain_output_old_extra_matches = \
356
      testutils.ReadTestData("bdev-rbd/plain_output_old_extra_matches.txt")
357
    self.plain_output_old_empty = \
358
      testutils.ReadTestData("bdev-rbd/plain_output_old_empty.txt")
359
    self.plain_output_new_ok = \
360
      testutils.ReadTestData("bdev-rbd/plain_output_new_ok.txt")
361
    self.plain_output_new_no_matches = \
362
      testutils.ReadTestData("bdev-rbd/plain_output_new_no_matches.txt")
363
    self.plain_output_new_extra_matches = \
364
      testutils.ReadTestData("bdev-rbd/plain_output_new_extra_matches.txt")
365
    # This file is completely empty, and as such it's not shipped.
366
    self.plain_output_new_empty = ""
367
    self.json_output_ok = testutils.ReadTestData("bdev-rbd/json_output_ok.txt")
368
    self.json_output_no_matches = \
369
      testutils.ReadTestData("bdev-rbd/json_output_no_matches.txt")
370
    self.json_output_extra_matches = \
371
      testutils.ReadTestData("bdev-rbd/json_output_extra_matches.txt")
372
    self.json_output_empty = \
373
      testutils.ReadTestData("bdev-rbd/json_output_empty.txt")
374
    self.output_invalid = testutils.ReadTestData("bdev-rbd/output_invalid.txt")
375

    
376
    self.volume_name = "d7ab910a-4933-4ffe-88d0-faf2ce31390a.rbd.disk0"
377

    
378
  def test_ParseRbdShowmappedJson(self):
379
    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedJson
380

    
381
    self.assertEqual(parse_function(self.json_output_ok, self.volume_name),
382
                     "/dev/rbd3")
383
    self.assertEqual(parse_function(self.json_output_empty, self.volume_name),
384
                     None)
385
    self.assertEqual(parse_function(self.json_output_no_matches,
386
                     self.volume_name), None)
387
    self.assertRaises(errors.BlockDeviceError, parse_function,
388
                      self.json_output_extra_matches, self.volume_name)
389
    self.assertRaises(errors.BlockDeviceError, parse_function,
390
                      self.output_invalid, self.volume_name)
391

    
392
  def test_ParseRbdShowmappedPlain(self):
393
    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedPlain
394

    
395
    self.assertEqual(parse_function(self.plain_output_new_ok,
396
                     self.volume_name), "/dev/rbd3")
397
    self.assertEqual(parse_function(self.plain_output_old_ok,
398
                     self.volume_name), "/dev/rbd3")
399
    self.assertEqual(parse_function(self.plain_output_new_empty,
400
                     self.volume_name), None)
401
    self.assertEqual(parse_function(self.plain_output_old_empty,
402
                     self.volume_name), None)
403
    self.assertEqual(parse_function(self.plain_output_new_no_matches,
404
                     self.volume_name), None)
405
    self.assertEqual(parse_function(self.plain_output_old_no_matches,
406
                     self.volume_name), None)
407
    self.assertRaises(errors.BlockDeviceError, parse_function,
408
                      self.plain_output_new_extra_matches, self.volume_name)
409
    self.assertRaises(errors.BlockDeviceError, parse_function,
410
                      self.plain_output_old_extra_matches, self.volume_name)
411
    self.assertRaises(errors.BlockDeviceError, parse_function,
412
                      self.output_invalid, self.volume_name)
413

    
414
class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
415
  def testPaths(self):
416
    paths = bdev._GetForbiddenFileStoragePaths()
417

    
418
    for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
419
      self.assertTrue(path in paths)
420

    
421
    self.assertEqual(set(map(os.path.normpath, paths)), paths)
422

    
423
  def test(self):
424
    vfsp = bdev._ComputeWrongFileStoragePaths
425
    self.assertEqual(vfsp([]), [])
426
    self.assertEqual(vfsp(["/tmp"]), [])
427
    self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
428
    self.assertEqual(vfsp(["/bin"]), ["/bin"])
429
    self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
430
                     ["/usr/sbin/vim"])
431

    
432

    
433
class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
434
  def test(self):
435
    tmpfile = self._CreateTempFile()
436

    
437
    utils.WriteFile(tmpfile, data="""
438
      /tmp
439
      x/y///z/relative
440
      # This is a test file
441
      /srv/storage
442
      /bin
443
      /usr/local/lib32/
444
      relative/path
445
      """)
446

    
447
    self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
448
      "/bin",
449
      "/usr/local/lib32",
450
      "relative/path",
451
      "x/y/z/relative",
452
      ])
453

    
454

    
455
class TestCheckFileStoragePathInternal(unittest.TestCase):
456
  def testNonAbsolute(self):
457
    for i in ["", "tmp", "foo/bar/baz"]:
458
      self.assertRaises(errors.FileStoragePathError,
459
                        bdev._CheckFileStoragePath, i, ["/tmp"])
460

    
461
    self.assertRaises(errors.FileStoragePathError,
462
                      bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
463

    
464
  def testNoAllowed(self):
465
    self.assertRaises(errors.FileStoragePathError,
466
                      bdev._CheckFileStoragePath, "/tmp", [])
467

    
468
  def testNoAdditionalPathComponent(self):
469
    self.assertRaises(errors.FileStoragePathError,
470
                      bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
471

    
472
  def testAllowed(self):
473
    bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
474
    bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
475

    
476

    
477
class TestCheckFileStoragePath(testutils.GanetiTestCase):
478
  def testNonExistantFile(self):
479
    filename = "/tmp/this/file/does/not/exist"
480
    assert not os.path.exists(filename)
481
    self.assertRaises(errors.FileStoragePathError,
482
                      bdev.CheckFileStoragePath, "/bin/", _filename=filename)
483
    self.assertRaises(errors.FileStoragePathError,
484
                      bdev.CheckFileStoragePath, "/srv/file-storage",
485
                      _filename=filename)
486

    
487
  def testAllowedPath(self):
488
    tmpfile = self._CreateTempFile()
489

    
490
    utils.WriteFile(tmpfile, data="""
491
      /srv/storage
492
      """)
493

    
494
    bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
495

    
496
    # No additional path component
497
    self.assertRaises(errors.FileStoragePathError,
498
                      bdev.CheckFileStoragePath, "/srv/storage",
499
                      _filename=tmpfile)
500

    
501
    # Forbidden path
502
    self.assertRaises(errors.FileStoragePathError,
503
                      bdev.CheckFileStoragePath, "/usr/lib64/xyz",
504
                      _filename=tmpfile)
505

    
506

    
507
class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
508
  def testDevNull(self):
509
    self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
510

    
511
  def testNonExistantFile(self):
512
    filename = "/tmp/this/file/does/not/exist"
513
    assert not os.path.exists(filename)
514
    self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
515

    
516
  def test(self):
517
    tmpfile = self._CreateTempFile()
518

    
519
    utils.WriteFile(tmpfile, data="""
520
      # This is a test file
521
      /tmp
522
      /srv/storage
523
      relative/path
524
      """)
525

    
526
    self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
527
      "/tmp",
528
      "/srv/storage",
529
      "relative/path",
530
      ])
531

    
532

    
533
class TestExclusiveStoragePvs(unittest.TestCase):
534
  """Test cases for functions dealing with LVM PV and exclusive storage"""
535
  # Allowance for rounding
536
  _EPS = 1e-4
537
  _MARGIN = constants.PART_MARGIN + constants.PART_RESERVED + _EPS
538

    
539
  @staticmethod
540
  def _GenerateRandomPvInfo(rnd, name, vg):
541
    # Granularity is .01 MiB
542
    size = rnd.randint(1024 * 100, 10 * 1024 * 1024 * 100)
543
    if rnd.choice([False, True]):
544
      free = float(rnd.randint(0, size)) / 100.0
545
    else:
546
      free = float(size) / 100.0
547
    size = float(size) / 100.0
548
    attr = "a-"
549
    return objects.LvmPvInfo(name=name, vg_name=vg, size=size, free=free,
550
                             attributes=attr)
551

    
552
  def testGetStdPvSize(self):
553
    """Test cases for bdev.LogicalVolume._GetStdPvSize()"""
554
    rnd = random.Random(9517)
555
    for _ in range(0, 50):
556
      # Identical volumes
557
      pvi = self._GenerateRandomPvInfo(rnd, "disk", "myvg")
558
      onesize = bdev.LogicalVolume._GetStdPvSize([pvi])
559
      self.assertTrue(onesize <= pvi.size)
560
      self.assertTrue(onesize > pvi.size * (1 - self._MARGIN))
561
      for length in range(2, 10):
562
        n_size = bdev.LogicalVolume._GetStdPvSize([pvi] * length)
563
        self.assertEqual(onesize, n_size)
564

    
565
      # Mixed volumes
566
      for length in range(1, 10):
567
        pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")
568
                  for _ in range(0, length)]
569
        std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
570
        self.assertTrue(compat.all(std_size <= pvi.size for pvi in pvlist))
571
        self.assertTrue(compat.any(std_size > pvi.size * (1 - self._MARGIN)
572
                                   for pvi in pvlist))
573
        pvlist.append(pvlist[0])
574
        p1_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
575
        self.assertEqual(std_size, p1_size)
576

    
577
  def testComputeNumPvs(self):
578
    """Test cases for bdev.LogicalVolume._ComputeNumPvs()"""
579
    rnd = random.Random(8067)
580
    for _ in range(0, 1000):
581
      pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")]
582
      lv_size = float(rnd.randint(10 * 100, 1024 * 1024 * 100)) / 100.0
583
      num_pv = bdev.LogicalVolume._ComputeNumPvs(lv_size, pvlist)
584
      std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
585
      self.assertTrue(num_pv >= 1)
586
      self.assertTrue(num_pv * std_size >= lv_size)
587
      self.assertTrue((num_pv - 1) * std_size < lv_size * (1 + self._EPS))
588

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

    
609

    
610
if __name__ == "__main__":
611
  testutils.GanetiTestProgram()