Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.bdev_unittest.py @ 23e3c9b7

History | View | Annotate | Download (17.6 kB)

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

    
4
# Copyright (C) 2006, 2007, 2010 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 unittest
27

    
28
from ganeti import bdev
29
from ganeti import errors
30
from ganeti import constants
31
from ganeti import utils
32

    
33
import testutils
34

    
35

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

    
72

    
73
class TestDRBD8Runner(testutils.GanetiTestCase):
74
  """Testing case for DRBD8"""
75

    
76
  @staticmethod
77
  def _has_disk(data, dname, mname):
78
    """Check local disk corectness"""
79
    retval = (
80
      "local_dev" in data and
81
      data["local_dev"] == dname and
82
      "meta_dev" in data and
83
      data["meta_dev"] == mname and
84
      "meta_index" in data and
85
      data["meta_index"] == 0
86
      )
87
    return retval
88

    
89
  @staticmethod
90
  def _has_net(data, local, remote):
91
    """Check network connection parameters"""
92
    retval = (
93
      "local_addr" in data and
94
      data["local_addr"] == local and
95
      "remote_addr" in data and
96
      data["remote_addr"] == remote
97
      )
98
    return retval
99

    
100
  def testParserCreation(self):
101
    """Test drbdsetup show parser creation"""
102
    bdev.DRBD8._GetShowParser()
103

    
104
  def testParser80(self):
105
    """Test drbdsetup show parser for disk and network version 8.0"""
106
    data = self._ReadTestData("bdev-drbd-8.0.txt")
107
    result = bdev.DRBD8._GetDevInfo(data)
108
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
109
                                   "/dev/xenvg/test.meta"),
110
                    "Wrong local disk info")
111
    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
112
                                  ("192.0.2.2", 11000)),
113
                    "Wrong network info (8.0.x)")
114

    
115
  def testParser83(self):
116
    """Test drbdsetup show parser for disk and network version 8.3"""
117
    data = self._ReadTestData("bdev-drbd-8.3.txt")
118
    result = bdev.DRBD8._GetDevInfo(data)
119
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
120
                                   "/dev/xenvg/test.meta"),
121
                    "Wrong local disk info")
122
    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
123
                                  ("192.0.2.2", 11000)),
124
                    "Wrong network info (8.0.x)")
125

    
126
  def testParserNetIP4(self):
127
    """Test drbdsetup show parser for IPv4 network"""
128
    data = self._ReadTestData("bdev-drbd-net-ip4.txt")
129
    result = bdev.DRBD8._GetDevInfo(data)
130
    self.failUnless(("local_dev" not in result and
131
                     "meta_dev" not in result and
132
                     "meta_index" not in result),
133
                    "Should not find local disk info")
134
    self.failUnless(self._has_net(result, ("192.0.2.1", 11002),
135
                                  ("192.0.2.2", 11002)),
136
                    "Wrong network info (IPv4)")
137

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

    
150
  def testParserDisk(self):
151
    """Test drbdsetup show parser for disk"""
152
    data = self._ReadTestData("bdev-drbd-disk.txt")
153
    result = bdev.DRBD8._GetDevInfo(data)
154
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
155
                                   "/dev/xenvg/test.meta"),
156
                    "Wrong local disk info")
157
    self.failUnless(("local_addr" not in result and
158
                     "remote_addr" not in result),
159
                    "Should not find network info")
160

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

    
172
    for vmaj, vmin, vrel, opts, meta in should_fail:
173
      self.assertRaises(errors.BlockDeviceError,
174
                        bdev.DRBD8._ComputeDiskBarrierArgs,
175
                        vmaj, vmin, vrel, opts, meta)
176

    
177
    # get the valid options from the frozenset(frozenset()) in constants.
178
    valid_options = [list(x)[0] for x in constants.DRBD_VALID_BARRIER_OPT]
179

    
180
    # Versions that do not support anything
181
    for vmaj, vmin, vrel in ((8, 0, 0), (8, 0, 11), (8, 2, 6)):
182
      for opts in valid_options:
183
        self.assertRaises(errors.BlockDeviceError,
184
                          bdev.DRBD8._ComputeDiskBarrierArgs,
185
                          vmaj, vmin, vrel, opts, True)
186

    
187
    # Versions with partial support (testing only options that are supported)
188
    tests = [
189
      (8, 0, 12, "n", False, []),
190
      (8, 0, 12, "n", True, ["--no-md-flushes"]),
191
      (8, 2, 7, "n", False, []),
192
      (8, 2, 7, "fd", False, ["--no-disk-flushes", "--no-disk-drain"]),
193
      (8, 0, 12, "n", True, ["--no-md-flushes"]),
194
      ]
195

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

    
208
    # Test execution
209
    for test in tests:
210
      vmaj, vmin, vrel, disabled_barriers, disable_meta_flush, expected = test
211
      args = \
212
        bdev.DRBD8._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
213
                                           disabled_barriers,
214
                                           disable_meta_flush)
215
      self.failUnless(set(args) == set(expected),
216
                      "For test %s, got wrong results %s" % (test, args))
217

    
218
    # Unsupported or invalid versions
219
    for vmaj, vmin, vrel in ((0, 7, 25), (9, 0, 0), (7, 0, 0), (8, 4, 0)):
220
      self.assertRaises(errors.BlockDeviceError,
221
                        bdev.DRBD8._ComputeDiskBarrierArgs,
222
                        vmaj, vmin, vrel, "n", True)
223

    
224
    # Invalid options
225
    for option in ("", "c", "whatever", "nbdfc", "nf"):
226
      self.assertRaises(errors.BlockDeviceError,
227
                        bdev.DRBD8._ComputeDiskBarrierArgs,
228
                        8, 3, 11, option, True)
229

    
230

    
231
class TestDRBD8Status(testutils.GanetiTestCase):
232
  """Testing case for DRBD8 /proc status"""
233

    
234
  def setUp(self):
235
    """Read in txt data"""
236
    testutils.GanetiTestCase.setUp(self)
237
    proc_data = self._TestDataFilename("proc_drbd8.txt")
238
    proc80e_data = self._TestDataFilename("proc_drbd80-emptyline.txt")
239
    proc83_data = self._TestDataFilename("proc_drbd83.txt")
240
    proc83_sync_data = self._TestDataFilename("proc_drbd83_sync.txt")
241
    proc83_sync_krnl_data = \
242
      self._TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
243
    self.proc_data = bdev.DRBD8._GetProcData(filename=proc_data)
244
    self.proc80e_data = bdev.DRBD8._GetProcData(filename=proc80e_data)
245
    self.proc83_data = bdev.DRBD8._GetProcData(filename=proc83_data)
246
    self.proc83_sync_data = bdev.DRBD8._GetProcData(filename=proc83_sync_data)
247
    self.proc83_sync_krnl_data = \
248
      bdev.DRBD8._GetProcData(filename=proc83_sync_krnl_data)
249
    self.mass_data = bdev.DRBD8._MassageProcData(self.proc_data)
250
    self.mass80e_data = bdev.DRBD8._MassageProcData(self.proc80e_data)
251
    self.mass83_data = bdev.DRBD8._MassageProcData(self.proc83_data)
252
    self.mass83_sync_data = bdev.DRBD8._MassageProcData(self.proc83_sync_data)
253
    self.mass83_sync_krnl_data = \
254
      bdev.DRBD8._MassageProcData(self.proc83_sync_krnl_data)
255

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

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

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

    
276
  def testMinorNotFound(self):
277
    """Test not-found-minor in /proc"""
278
    self.failUnless(9 not in self.mass_data)
279
    self.failUnless(9 not in self.mass83_data)
280
    self.failUnless(3 not in self.mass80e_data)
281

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

    
286
  def testMinor0(self):
287
    """Test connected, primary device"""
288
    for data in [self.mass_data, self.mass83_data]:
289
      stats = bdev.DRBD8Status(data[0])
290
      self.failUnless(stats.is_in_use)
291
      self.failUnless(stats.is_connected and stats.is_primary and
292
                      stats.peer_secondary and stats.is_disk_uptodate)
293

    
294
  def testMinor1(self):
295
    """Test connected, secondary device"""
296
    for data in [self.mass_data, self.mass83_data]:
297
      stats = bdev.DRBD8Status(data[1])
298
      self.failUnless(stats.is_in_use)
299
      self.failUnless(stats.is_connected and stats.is_secondary and
300
                      stats.peer_primary and stats.is_disk_uptodate)
301

    
302
  def testMinor2(self):
303
    """Test unconfigured device"""
304
    for data in [self.mass_data, self.mass83_data, self.mass80e_data]:
305
      stats = bdev.DRBD8Status(data[2])
306
      self.failIf(stats.is_in_use)
307

    
308
  def testMinor4(self):
309
    """Test WFconn device"""
310
    for data in [self.mass_data, self.mass83_data]:
311
      stats = bdev.DRBD8Status(data[4])
312
      self.failUnless(stats.is_in_use)
313
      self.failUnless(stats.is_wfconn and stats.is_primary and
314
                      stats.rrole == 'Unknown' and
315
                      stats.is_disk_uptodate)
316

    
317
  def testMinor6(self):
318
    """Test diskless device"""
319
    for data in [self.mass_data, self.mass83_data]:
320
      stats = bdev.DRBD8Status(data[6])
321
      self.failUnless(stats.is_in_use)
322
      self.failUnless(stats.is_connected and stats.is_secondary and
323
                      stats.peer_primary and stats.is_diskless)
324

    
325
  def testMinor8(self):
326
    """Test standalone device"""
327
    for data in [self.mass_data, self.mass83_data]:
328
      stats = bdev.DRBD8Status(data[8])
329
      self.failUnless(stats.is_in_use)
330
      self.failUnless(stats.is_standalone and
331
                      stats.rrole == 'Unknown' and
332
                      stats.is_disk_uptodate)
333

    
334
  def testDRBD83SyncFine(self):
335
    stats = bdev.DRBD8Status(self.mass83_sync_data[3])
336
    self.failUnless(stats.is_in_resync)
337
    self.failUnless(stats.sync_percent is not None)
338

    
339
  def testDRBD83SyncBroken(self):
340
    stats = bdev.DRBD8Status(self.mass83_sync_krnl_data[3])
341
    self.failUnless(stats.is_in_resync)
342
    self.failUnless(stats.sync_percent is not None)
343

    
344

    
345
class TestRADOSBlockDevice(testutils.GanetiTestCase):
346
  def test_ParseRbdShowmappedOutput(self):
347
    volume_name = "abc9778-8e8ace5b.rbd.disk0"
348
    output_ok = \
349
      ("0\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
350
       "1\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd16\n"
351
       "line\twith\tfewer\tfields\n"
352
       "")
353
    output_empty = ""
354
    output_no_matches = \
355
      ("0\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
356
       "1\trbd\tabcdef01-9817.rbd.disk0\t-\t/dev/rbd10\n"
357
       "2\trbd\tcdef0123-9817.rbd.disk0\t-\t/dev/rbd12\n"
358
       "something\twith\tfewer\tfields"
359
       "")
360
    output_extra_matches = \
361
      ("0\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd11\n"
362
       "1\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
363
       "2\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd16\n"
364
       "something\twith\tfewer\tfields"
365
       "")
366

    
367
    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedOutput
368
    self.assertEqual(parse_function(output_ok, volume_name), "/dev/rbd16")
369
    self.assertRaises(errors.BlockDeviceError, parse_function,
370
                      output_empty, volume_name)
371
    self.assertEqual(parse_function(output_no_matches, volume_name), None)
372
    self.assertRaises(errors.BlockDeviceError, parse_function,
373
                      output_extra_matches, volume_name)
374

    
375

    
376
class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
377
  def testPaths(self):
378
    paths = bdev._GetForbiddenFileStoragePaths()
379

    
380
    for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
381
      self.assertTrue(path in paths)
382

    
383
    self.assertEqual(set(map(os.path.normpath, paths)), paths)
384

    
385
  def test(self):
386
    vfsp = bdev._ComputeWrongFileStoragePaths
387
    self.assertEqual(vfsp([]), [])
388
    self.assertEqual(vfsp(["/tmp"]), [])
389
    self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
390
    self.assertEqual(vfsp(["/bin"]), ["/bin"])
391
    self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
392
                     ["/usr/sbin/vim"])
393

    
394

    
395
class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
396
  def test(self):
397
    tmpfile = self._CreateTempFile()
398

    
399
    utils.WriteFile(tmpfile, data="""
400
      /tmp
401
      x/y///z/relative
402
      # This is a test file
403
      /srv/storage
404
      /bin
405
      /usr/local/lib32/
406
      relative/path
407
      """)
408

    
409
    self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
410
      "/bin",
411
      "/usr/local/lib32",
412
      "relative/path",
413
      "x/y/z/relative",
414
      ])
415

    
416

    
417
class TestCheckFileStoragePathInternal(unittest.TestCase):
418
  def testNonAbsolute(self):
419
    for i in ["", "tmp", "foo/bar/baz"]:
420
      self.assertRaises(errors.FileStoragePathError,
421
                        bdev._CheckFileStoragePath, i, ["/tmp"])
422

    
423
    self.assertRaises(errors.FileStoragePathError,
424
                      bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
425

    
426
  def testNoAllowed(self):
427
    self.assertRaises(errors.FileStoragePathError,
428
                      bdev._CheckFileStoragePath, "/tmp", [])
429

    
430
  def testNoAdditionalPathComponent(self):
431
    self.assertRaises(errors.FileStoragePathError,
432
                      bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
433

    
434
  def testAllowed(self):
435
    bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
436
    bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
437

    
438

    
439
class TestCheckFileStoragePath(testutils.GanetiTestCase):
440
  def testNonExistantFile(self):
441
    filename = "/tmp/this/file/does/not/exist"
442
    assert not os.path.exists(filename)
443
    self.assertRaises(errors.FileStoragePathError,
444
                      bdev.CheckFileStoragePath, "/bin/", _filename=filename)
445
    self.assertRaises(errors.FileStoragePathError,
446
                      bdev.CheckFileStoragePath, "/srv/file-storage",
447
                      _filename=filename)
448

    
449
  def testAllowedPath(self):
450
    tmpfile = self._CreateTempFile()
451

    
452
    utils.WriteFile(tmpfile, data="""
453
      /srv/storage
454
      """)
455

    
456
    bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
457

    
458
    # No additional path component
459
    self.assertRaises(errors.FileStoragePathError,
460
                      bdev.CheckFileStoragePath, "/srv/storage",
461
                      _filename=tmpfile)
462

    
463
    # Forbidden path
464
    self.assertRaises(errors.FileStoragePathError,
465
                      bdev.CheckFileStoragePath, "/usr/lib64/xyz",
466
                      _filename=tmpfile)
467

    
468

    
469
class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
470
  def testDevNull(self):
471
    self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
472

    
473
  def testNonExistantFile(self):
474
    filename = "/tmp/this/file/does/not/exist"
475
    assert not os.path.exists(filename)
476
    self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
477

    
478
  def test(self):
479
    tmpfile = self._CreateTempFile()
480

    
481
    utils.WriteFile(tmpfile, data="""
482
      # This is a test file
483
      /tmp
484
      /srv/storage
485
      relative/path
486
      """)
487

    
488
    self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
489
      "/tmp",
490
      "/srv/storage",
491
      "relative/path",
492
      ])
493

    
494

    
495
if __name__ == "__main__":
496
  testutils.GanetiTestProgram()