Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.bdev_unittest.py @ 8c114acd

History | View | Annotate | Download (17.6 kB)

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

    
4
# Copyright (C) 2006, 2007, 2010, 2012 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
from ganeti import pathutils
33

    
34
import testutils
35

    
36

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

    
73

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
231

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

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

    
264
  def testHelper(self):
265
    """Test reading usermode_helper in /sys."""
266
    sys_drbd_helper = self._TestDataFilename("sys_drbd_usermode_helper.txt")
267
    drbd_helper = bdev.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
                          bdev.DRBD8.GetUsermodeHelper, filename=temp_file)
276

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

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

    
287
  def testMinor0(self):
288
    """Test connected, primary device"""
289
    for data in [self.mass_data, self.mass83_data]:
290
      stats = bdev.DRBD8Status(data[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 data in [self.mass_data, self.mass83_data]:
298
      stats = bdev.DRBD8Status(data[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 data in [self.mass_data, self.mass83_data, self.mass80e_data]:
306
      stats = bdev.DRBD8Status(data[2])
307
      self.failIf(stats.is_in_use)
308

    
309
  def testMinor4(self):
310
    """Test WFconn device"""
311
    for data in [self.mass_data, self.mass83_data]:
312
      stats = bdev.DRBD8Status(data[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 data in [self.mass_data, self.mass83_data]:
321
      stats = bdev.DRBD8Status(data[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 data in [self.mass_data, self.mass83_data]:
329
      stats = bdev.DRBD8Status(data[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 = bdev.DRBD8Status(self.mass83_sync_data[3])
337
    self.failUnless(stats.is_in_resync)
338
    self.failUnless(stats.sync_percent is not None)
339

    
340
  def testDRBD83SyncBroken(self):
341
    stats = bdev.DRBD8Status(self.mass83_sync_krnl_data[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 test_ParseRbdShowmappedOutput(self):
348
    volume_name = "abc9778-8e8ace5b.rbd.disk0"
349
    output_ok = \
350
      ("0\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
351
       "1\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd16\n"
352
       "line\twith\tfewer\tfields\n"
353
       "")
354
    output_empty = ""
355
    output_no_matches = \
356
      ("0\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
357
       "1\trbd\tabcdef01-9817.rbd.disk0\t-\t/dev/rbd10\n"
358
       "2\trbd\tcdef0123-9817.rbd.disk0\t-\t/dev/rbd12\n"
359
       "something\twith\tfewer\tfields"
360
       "")
361
    output_extra_matches = \
362
      ("0\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd11\n"
363
       "1\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
364
       "2\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd16\n"
365
       "something\twith\tfewer\tfields"
366
       "")
367

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

    
376

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

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

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

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

    
395

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

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

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

    
417

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

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

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

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

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

    
439

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

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

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

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

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

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

    
469

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

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

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

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

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

    
495

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