bash_completion: Enable extglob while parsing file
[ganeti-local] / test / ganeti.bdev_unittest.py
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()