jqueue: Add new in-memory attribute for archived jobs
[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 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()