Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.bdev_unittest.py @ 7181fba0

History | View | Annotate | Download (14 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

    
32
import testutils
33

    
34

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

    
71

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
229

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
343

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

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

    
374

    
375
if __name__ == '__main__':
376
  testutils.GanetiTestProgram()