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