Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.bdev_unittest.py @ fbdac0d9

History | View | Annotate | Download (15.4 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 TestCheckFileStoragePath(unittest.TestCase):
377
  def testNonAbsolute(self):
378
    for i in ["", "tmp", "foo/bar/baz"]:
379
      self.assertRaises(errors.FileStoragePathError,
380
                        bdev._CheckFileStoragePath, i, ["/tmp"])
381

    
382
    self.assertRaises(errors.FileStoragePathError,
383
                      bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
384

    
385
  def testNoAllowed(self):
386
    self.assertRaises(errors.FileStoragePathError,
387
                      bdev._CheckFileStoragePath, "/tmp", [])
388

    
389
  def testNoAdditionalPathComponent(self):
390
    self.assertRaises(errors.FileStoragePathError,
391
                      bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
392

    
393
  def testAllowed(self):
394
    bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
395
    bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
396

    
397

    
398
class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
399
  def testDevNull(self):
400
    self.assertEqual(bdev.LoadAllowedFileStoragePaths("/dev/null"), [])
401

    
402
  def testNonExistantFile(self):
403
    filename = "/tmp/this/file/does/not/exist"
404
    assert not os.path.exists(filename)
405
    self.assertEqual(bdev.LoadAllowedFileStoragePaths(filename), [])
406

    
407
  def test(self):
408
    tmpfile = self._CreateTempFile()
409

    
410
    utils.WriteFile(tmpfile, data="""
411
      # This is a test file
412
      /tmp
413
      /srv/storage
414
      relative/path
415
      """)
416

    
417
    self.assertEqual(bdev.LoadAllowedFileStoragePaths(tmpfile), [
418
      "/tmp",
419
      "/srv/storage",
420
      "relative/path",
421
      ])
422

    
423

    
424
if __name__ == "__main__":
425
  testutils.GanetiTestProgram()