Fixing renaming of DISK_LD_DEFAULTS
[ganeti-local] / test / py / ganeti.storage.drbd_unittest.py
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 drbd module"""
23
24
25 import os
26
27 from ganeti import constants
28 from ganeti import errors
29 from ganeti.storage import drbd
30 from ganeti.storage import drbd_info
31 from ganeti.storage import drbd_cmdgen
32
33 import testutils
34
35
36 class TestDRBD8(testutils.GanetiTestCase):
37   def testGetVersion(self):
38     data = [
39       "version: 8.0.0 (api:76/proto:80)",
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": 0,
49         "api": 76,
50         "proto": 80,
51       },
52       {
53         "k_major": 8,
54         "k_minor": 0,
55         "k_point": 12,
56         "api": 76,
57         "proto": 86,
58         "proto2": "91",
59       },
60       {
61         "k_major": 8,
62         "k_minor": 2,
63         "k_point": 7,
64         "api": 88,
65         "proto": 0,
66         "proto2": "100",
67       },
68       {
69         "k_major": 8,
70         "k_minor": 3,
71         "k_point": 7,
72         "k_fix": "49",
73         "api": 188,
74         "proto": 13,
75         "proto2": "191",
76       }
77     ]
78     for d, r in zip(data, result):
79       info = drbd.DRBD8Info.CreateFromLines([d])
80       self.assertEqual(info.GetVersion(), r)
81       self.assertEqual(info.GetVersionString(), d.replace("version: ", ""))
82
83
84 class TestDRBD8Runner(testutils.GanetiTestCase):
85   """Testing case for drbd.DRBD8Dev"""
86
87   @staticmethod
88   def _has_disk(data, dname, mname, meta_index=0):
89     """Check local disk corectness"""
90     retval = (
91       "local_dev" in data and
92       data["local_dev"] == dname and
93       "meta_dev" in data and
94       data["meta_dev"] == mname and
95       ((meta_index is None and
96         "meta_index" not in data) or
97        ("meta_index" in data and
98         data["meta_index"] == meta_index)
99       )
100       )
101     return retval
102
103   @staticmethod
104   def _has_net(data, local, remote):
105     """Check network connection parameters"""
106     retval = (
107       "local_addr" in data and
108       data["local_addr"] == local and
109       "remote_addr" in data and
110       data["remote_addr"] == remote
111       )
112     return retval
113
114   def testParser83Creation(self):
115     """Test drbdsetup show parser creation"""
116     drbd_info.DRBD83ShowInfo._GetShowParser()
117
118   def testParser84Creation(self):
119     """Test drbdsetup show parser creation"""
120     drbd_info.DRBD84ShowInfo._GetShowParser()
121
122   def testParser80(self):
123     """Test drbdsetup show parser for disk and network version 8.0"""
124     data = testutils.ReadTestData("bdev-drbd-8.0.txt")
125     result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
126     self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
127                                    "/dev/xenvg/test.meta"),
128                     "Wrong local disk info")
129     self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
130                                   ("192.0.2.2", 11000)),
131                     "Wrong network info (8.0.x)")
132
133   def testParser83(self):
134     """Test drbdsetup show parser for disk and network version 8.3"""
135     data = testutils.ReadTestData("bdev-drbd-8.3.txt")
136     result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
137     self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
138                                    "/dev/xenvg/test.meta"),
139                     "Wrong local disk info")
140     self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
141                                   ("192.0.2.2", 11000)),
142                     "Wrong network info (8.3.x)")
143
144   def testParser84(self):
145     """Test drbdsetup show parser for disk and network version 8.4"""
146     data = testutils.ReadTestData("bdev-drbd-8.4.txt")
147     result = drbd_info.DRBD84ShowInfo.GetDevInfo(data)
148     self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
149                                    "/dev/xenvg/test.meta"),
150                     "Wrong local disk info")
151     self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
152                                   ("192.0.2.2", 11000)),
153                     "Wrong network info (8.4.x)")
154
155   def testParser84NoDiskParams(self):
156     """Test drbdsetup show parser for 8.4 without disk params
157
158     The missing disk parameters occur after re-attaching a local disk but
159     before setting the disk params.
160
161     """
162     data = testutils.ReadTestData("bdev-drbd-8.4-no-disk-params.txt")
163     result = drbd_info.DRBD84ShowInfo.GetDevInfo(data)
164     self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
165                                    "/dev/xenvg/test.meta", meta_index=None),
166                     "Wrong local disk info")
167     self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
168                                   ("192.0.2.2", 11000)),
169                     "Wrong network info (8.4.x)")
170
171   def testParserNetIP4(self):
172     """Test drbdsetup show parser for IPv4 network"""
173     data = testutils.ReadTestData("bdev-drbd-net-ip4.txt")
174     result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
175     self.failUnless(("local_dev" not in result and
176                      "meta_dev" not in result and
177                      "meta_index" not in result),
178                     "Should not find local disk info")
179     self.failUnless(self._has_net(result, ("192.0.2.1", 11002),
180                                   ("192.0.2.2", 11002)),
181                     "Wrong network info (IPv4)")
182
183   def testParserNetIP6(self):
184     """Test drbdsetup show parser for IPv6 network"""
185     data = testutils.ReadTestData("bdev-drbd-net-ip6.txt")
186     result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
187     self.failUnless(("local_dev" not in result and
188                      "meta_dev" not in result and
189                      "meta_index" not in result),
190                     "Should not find local disk info")
191     self.failUnless(self._has_net(result, ("2001:db8:65::1", 11048),
192                                   ("2001:db8:66::1", 11048)),
193                     "Wrong network info (IPv6)")
194
195   def testParserDisk(self):
196     """Test drbdsetup show parser for disk"""
197     data = testutils.ReadTestData("bdev-drbd-disk.txt")
198     result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
199     self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
200                                    "/dev/xenvg/test.meta"),
201                     "Wrong local disk info")
202     self.failUnless(("local_addr" not in result and
203                      "remote_addr" not in result),
204                     "Should not find network info")
205
206   def testBarriersOptions(self):
207     """Test class method that generates drbdsetup options for disk barriers"""
208     # Tests that should fail because of wrong version/options combinations
209     should_fail = [
210       (8, 0, 12, "bfd", True),
211       (8, 0, 12, "fd", False),
212       (8, 0, 12, "b", True),
213       (8, 2, 7, "bfd", True),
214       (8, 2, 7, "b", True)
215     ]
216
217     for vmaj, vmin, vrel, opts, meta in should_fail:
218       self.assertRaises(errors.BlockDeviceError,
219                         drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
220                         vmaj, vmin, vrel, opts, meta)
221
222     # get the valid options from the frozenset(frozenset()) in constants.
223     valid_options = [list(x)[0] for x in constants.DRBD_VALID_BARRIER_OPT]
224
225     # Versions that do not support anything
226     for vmaj, vmin, vrel in ((8, 0, 0), (8, 0, 11), (8, 2, 6)):
227       for opts in valid_options:
228         self.assertRaises(
229           errors.BlockDeviceError,
230           drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
231           vmaj, vmin, vrel, opts, True)
232
233     # Versions with partial support (testing only options that are supported)
234     tests = [
235       (8, 0, 12, "n", False, []),
236       (8, 0, 12, "n", True, ["--no-md-flushes"]),
237       (8, 2, 7, "n", False, []),
238       (8, 2, 7, "fd", False, ["--no-disk-flushes", "--no-disk-drain"]),
239       (8, 0, 12, "n", True, ["--no-md-flushes"]),
240       ]
241
242     # Versions that support everything
243     for vmaj, vmin, vrel in ((8, 3, 0), (8, 3, 12)):
244       tests.append((vmaj, vmin, vrel, "bfd", True,
245                     ["--no-disk-barrier", "--no-disk-drain",
246                      "--no-disk-flushes", "--no-md-flushes"]))
247       tests.append((vmaj, vmin, vrel, "n", False, []))
248       tests.append((vmaj, vmin, vrel, "b", True,
249                     ["--no-disk-barrier", "--no-md-flushes"]))
250       tests.append((vmaj, vmin, vrel, "fd", False,
251                     ["--no-disk-flushes", "--no-disk-drain"]))
252       tests.append((vmaj, vmin, vrel, "n", True, ["--no-md-flushes"]))
253
254     # Test execution
255     for test in tests:
256       vmaj, vmin, vrel, disabled_barriers, disable_meta_flush, expected = test
257       args = \
258         drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs(
259           vmaj, vmin, vrel,
260           disabled_barriers,
261           disable_meta_flush)
262       self.failUnless(set(args) == set(expected),
263                       "For test %s, got wrong results %s" % (test, args))
264
265     # Unsupported or invalid versions
266     for vmaj, vmin, vrel in ((0, 7, 25), (9, 0, 0), (7, 0, 0), (8, 4, 0)):
267       self.assertRaises(errors.BlockDeviceError,
268                         drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
269                         vmaj, vmin, vrel, "n", True)
270
271     # Invalid options
272     for option in ("", "c", "whatever", "nbdfc", "nf"):
273       self.assertRaises(errors.BlockDeviceError,
274                         drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
275                         8, 3, 11, option, True)
276
277
278 class TestDRBD8Status(testutils.GanetiTestCase):
279   """Testing case for DRBD8Dev /proc status"""
280
281   def setUp(self):
282     """Read in txt data"""
283     testutils.GanetiTestCase.setUp(self)
284     proc_data = testutils.TestDataFilename("proc_drbd8.txt")
285     proc80e_data = testutils.TestDataFilename("proc_drbd80-emptyline.txt")
286     proc83_data = testutils.TestDataFilename("proc_drbd83.txt")
287     proc83_sync_data = testutils.TestDataFilename("proc_drbd83_sync.txt")
288     proc83_sync_krnl_data = \
289       testutils.TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
290     proc84_data = testutils.TestDataFilename("proc_drbd84.txt")
291     proc84_sync_data = testutils.TestDataFilename("proc_drbd84_sync.txt")
292
293     self.proc80ev_data = \
294       testutils.TestDataFilename("proc_drbd80-emptyversion.txt")
295
296     self.drbd_info = drbd.DRBD8Info.CreateFromFile(filename=proc_data)
297     self.drbd_info80e = drbd.DRBD8Info.CreateFromFile(filename=proc80e_data)
298     self.drbd_info83 = drbd.DRBD8Info.CreateFromFile(filename=proc83_data)
299     self.drbd_info83_sync = \
300       drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_data)
301     self.drbd_info83_sync_krnl = \
302       drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_krnl_data)
303     self.drbd_info84 = drbd.DRBD8Info.CreateFromFile(filename=proc84_data)
304     self.drbd_info84_sync = \
305       drbd.DRBD8Info.CreateFromFile(filename=proc84_sync_data)
306
307   def testIOErrors(self):
308     """Test handling of errors while reading the proc file."""
309     temp_file = self._CreateTempFile()
310     os.unlink(temp_file)
311     self.failUnlessRaises(errors.BlockDeviceError,
312                           drbd.DRBD8Info.CreateFromFile, filename=temp_file)
313
314   def testHelper(self):
315     """Test reading usermode_helper in /sys."""
316     sys_drbd_helper = testutils.TestDataFilename("sys_drbd_usermode_helper.txt")
317     drbd_helper = drbd.DRBD8.GetUsermodeHelper(filename=sys_drbd_helper)
318     self.failUnlessEqual(drbd_helper, "/bin/true")
319
320   def testHelperIOErrors(self):
321     """Test handling of errors while reading usermode_helper in /sys."""
322     temp_file = self._CreateTempFile()
323     os.unlink(temp_file)
324     self.failUnlessRaises(errors.BlockDeviceError,
325                           drbd.DRBD8.GetUsermodeHelper, filename=temp_file)
326
327   def testMinorNotFound(self):
328     """Test not-found-minor in /proc"""
329     self.failUnless(not self.drbd_info.HasMinorStatus(9))
330     self.failUnless(not self.drbd_info83.HasMinorStatus(9))
331     self.failUnless(not self.drbd_info80e.HasMinorStatus(3))
332
333   def testLineNotMatch(self):
334     """Test wrong line passed to drbd_info.DRBD8Status"""
335     self.assertRaises(errors.BlockDeviceError, drbd_info.DRBD8Status, "foo")
336
337   def testMinor0(self):
338     """Test connected, primary device"""
339     for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
340       stats = info.GetMinorStatus(0)
341       self.failUnless(stats.is_in_use)
342       self.failUnless(stats.is_connected and stats.is_primary and
343                       stats.peer_secondary and stats.is_disk_uptodate)
344
345   def testMinor1(self):
346     """Test connected, secondary device"""
347     for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
348       stats = info.GetMinorStatus(1)
349       self.failUnless(stats.is_in_use)
350       self.failUnless(stats.is_connected and stats.is_secondary and
351                       stats.peer_primary and stats.is_disk_uptodate)
352
353   def testMinor2(self):
354     """Test unconfigured device"""
355     for info in [self.drbd_info, self.drbd_info83,
356                  self.drbd_info80e, self.drbd_info84]:
357       stats = info.GetMinorStatus(2)
358       self.failIf(stats.is_in_use)
359
360   def testMinor4(self):
361     """Test WFconn device"""
362     for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
363       stats = info.GetMinorStatus(4)
364       self.failUnless(stats.is_in_use)
365       self.failUnless(stats.is_wfconn and stats.is_primary and
366                       stats.rrole == "Unknown" and
367                       stats.is_disk_uptodate)
368
369   def testMinor6(self):
370     """Test diskless device"""
371     for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
372       stats = info.GetMinorStatus(6)
373       self.failUnless(stats.is_in_use)
374       self.failUnless(stats.is_connected and stats.is_secondary and
375                       stats.peer_primary and stats.is_diskless)
376
377   def testMinor8(self):
378     """Test standalone device"""
379     for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
380       stats = info.GetMinorStatus(8)
381       self.failUnless(stats.is_in_use)
382       self.failUnless(stats.is_standalone and
383                       stats.rrole == "Unknown" and
384                       stats.is_disk_uptodate)
385
386   def testDRBD83SyncFine(self):
387     stats = self.drbd_info83_sync.GetMinorStatus(3)
388     self.failUnless(stats.is_in_resync)
389     self.assertAlmostEqual(stats.sync_percent, 34.9)
390
391   def testDRBD83SyncBroken(self):
392     stats = self.drbd_info83_sync_krnl.GetMinorStatus(3)
393     self.failUnless(stats.is_in_resync)
394     self.assertAlmostEqual(stats.sync_percent, 2.4)
395
396   def testDRBD84Sync(self):
397     stats = self.drbd_info84_sync.GetMinorStatus(5)
398     self.failUnless(stats.is_in_resync)
399     self.assertAlmostEqual(stats.sync_percent, 68.5)
400
401   def testDRBDEmptyVersion(self):
402     self.assertRaises(errors.BlockDeviceError,
403                       drbd.DRBD8Info.CreateFromFile,
404                       filename=self.proc80ev_data)
405
406
407 class TestDRBD8Construction(testutils.GanetiTestCase):
408   def setUp(self):
409     """Read in txt data"""
410     testutils.GanetiTestCase.setUp(self)
411     self.proc80_info = \
412       drbd_info.DRBD8Info.CreateFromFile(
413         filename=testutils.TestDataFilename("proc_drbd8.txt"))
414     self.proc83_info = \
415       drbd_info.DRBD8Info.CreateFromFile(
416         filename=testutils.TestDataFilename("proc_drbd83.txt"))
417     self.proc84_info = \
418       drbd_info.DRBD8Info.CreateFromFile(
419         filename=testutils.TestDataFilename("proc_drbd84.txt"))
420
421     self.test_unique_id = ("hosta.com", 123, "host2.com", 123, 0, "secret")
422
423   @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
424   def testConstructionWith80Data(self, mock_create_from_file):
425     mock_create_from_file.return_value = self.proc80_info
426
427     inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
428     self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo)
429     self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator))
430
431   @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
432   def testConstructionWith83Data(self, mock_create_from_file):
433     mock_create_from_file.return_value = self.proc83_info
434
435     inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
436     self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo)
437     self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator))
438
439   @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
440   def testConstructionWith84Data(self, mock_create_from_file):
441     mock_create_from_file.return_value = self.proc84_info
442
443     inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
444     self.assertEqual(inst._show_info_cls, drbd_info.DRBD84ShowInfo)
445     self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD84CmdGenerator))
446
447
448 if __name__ == "__main__":
449   testutils.GanetiTestProgram()