4 # Copyright (C) 2006, 2007, 2010, 2012 Google Inc.
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.
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.
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
22 """Script for unittesting the bdev module"""
28 from ganeti import bdev
29 from ganeti import errors
30 from ganeti import constants
31 from ganeti import utils
32 from ganeti import pathutils
37 class TestBaseDRBD(testutils.GanetiTestCase):
38 def testGetVersion(self):
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)"],
70 for d,r in zip(data, result):
71 self.assertEqual(bdev.BaseDRBD._GetVersion(d), r)
74 class TestDRBD8Runner(testutils.GanetiTestCase):
75 """Testing case for DRBD8"""
78 def _has_disk(data, dname, mname):
79 """Check local disk corectness"""
81 "local_dev" in data and
82 data["local_dev"] == dname and
83 "meta_dev" in data and
84 data["meta_dev"] == mname and
85 "meta_index" in data and
86 data["meta_index"] == 0
91 def _has_net(data, local, remote):
92 """Check network connection parameters"""
94 "local_addr" in data and
95 data["local_addr"] == local and
96 "remote_addr" in data and
97 data["remote_addr"] == remote
101 def testParserCreation(self):
102 """Test drbdsetup show parser creation"""
103 bdev.DRBD8._GetShowParser()
105 def testParser80(self):
106 """Test drbdsetup show parser for disk and network version 8.0"""
107 data = self._ReadTestData("bdev-drbd-8.0.txt")
108 result = bdev.DRBD8._GetDevInfo(data)
109 self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
110 "/dev/xenvg/test.meta"),
111 "Wrong local disk info")
112 self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
113 ("192.0.2.2", 11000)),
114 "Wrong network info (8.0.x)")
116 def testParser83(self):
117 """Test drbdsetup show parser for disk and network version 8.3"""
118 data = self._ReadTestData("bdev-drbd-8.3.txt")
119 result = bdev.DRBD8._GetDevInfo(data)
120 self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
121 "/dev/xenvg/test.meta"),
122 "Wrong local disk info")
123 self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
124 ("192.0.2.2", 11000)),
125 "Wrong network info (8.0.x)")
127 def testParserNetIP4(self):
128 """Test drbdsetup show parser for IPv4 network"""
129 data = self._ReadTestData("bdev-drbd-net-ip4.txt")
130 result = bdev.DRBD8._GetDevInfo(data)
131 self.failUnless(("local_dev" not in result and
132 "meta_dev" not in result and
133 "meta_index" not in result),
134 "Should not find local disk info")
135 self.failUnless(self._has_net(result, ("192.0.2.1", 11002),
136 ("192.0.2.2", 11002)),
137 "Wrong network info (IPv4)")
139 def testParserNetIP6(self):
140 """Test drbdsetup show parser for IPv6 network"""
141 data = self._ReadTestData("bdev-drbd-net-ip6.txt")
142 result = bdev.DRBD8._GetDevInfo(data)
143 self.failUnless(("local_dev" not in result and
144 "meta_dev" not in result and
145 "meta_index" not in result),
146 "Should not find local disk info")
147 self.failUnless(self._has_net(result, ("2001:db8:65::1", 11048),
148 ("2001:db8:66::1", 11048)),
149 "Wrong network info (IPv6)")
151 def testParserDisk(self):
152 """Test drbdsetup show parser for disk"""
153 data = self._ReadTestData("bdev-drbd-disk.txt")
154 result = bdev.DRBD8._GetDevInfo(data)
155 self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
156 "/dev/xenvg/test.meta"),
157 "Wrong local disk info")
158 self.failUnless(("local_addr" not in result and
159 "remote_addr" not in result),
160 "Should not find network info")
162 def testBarriersOptions(self):
163 """Test class method that generates drbdsetup options for disk barriers"""
164 # Tests that should fail because of wrong version/options combinations
166 (8, 0, 12, "bfd", True),
167 (8, 0, 12, "fd", False),
168 (8, 0, 12, "b", True),
169 (8, 2, 7, "bfd", True),
173 for vmaj, vmin, vrel, opts, meta in should_fail:
174 self.assertRaises(errors.BlockDeviceError,
175 bdev.DRBD8._ComputeDiskBarrierArgs,
176 vmaj, vmin, vrel, opts, meta)
178 # get the valid options from the frozenset(frozenset()) in constants.
179 valid_options = [list(x)[0] for x in constants.DRBD_VALID_BARRIER_OPT]
181 # Versions that do not support anything
182 for vmaj, vmin, vrel in ((8, 0, 0), (8, 0, 11), (8, 2, 6)):
183 for opts in valid_options:
184 self.assertRaises(errors.BlockDeviceError,
185 bdev.DRBD8._ComputeDiskBarrierArgs,
186 vmaj, vmin, vrel, opts, True)
188 # Versions with partial support (testing only options that are supported)
190 (8, 0, 12, "n", False, []),
191 (8, 0, 12, "n", True, ["--no-md-flushes"]),
192 (8, 2, 7, "n", False, []),
193 (8, 2, 7, "fd", False, ["--no-disk-flushes", "--no-disk-drain"]),
194 (8, 0, 12, "n", True, ["--no-md-flushes"]),
197 # Versions that support everything
198 for vmaj, vmin, vrel in ((8, 3, 0), (8, 3, 12)):
199 tests.append((vmaj, vmin, vrel, "bfd", True,
200 ["--no-disk-barrier", "--no-disk-drain",
201 "--no-disk-flushes", "--no-md-flushes"]))
202 tests.append((vmaj, vmin, vrel, "n", False, []))
203 tests.append((vmaj, vmin, vrel, "b", True,
204 ["--no-disk-barrier", "--no-md-flushes"]))
205 tests.append((vmaj, vmin, vrel, "fd", False,
206 ["--no-disk-flushes", "--no-disk-drain"]))
207 tests.append((vmaj, vmin, vrel, "n", True, ["--no-md-flushes"]))
211 vmaj, vmin, vrel, disabled_barriers, disable_meta_flush, expected = test
213 bdev.DRBD8._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
216 self.failUnless(set(args) == set(expected),
217 "For test %s, got wrong results %s" % (test, args))
219 # Unsupported or invalid versions
220 for vmaj, vmin, vrel in ((0, 7, 25), (9, 0, 0), (7, 0, 0), (8, 4, 0)):
221 self.assertRaises(errors.BlockDeviceError,
222 bdev.DRBD8._ComputeDiskBarrierArgs,
223 vmaj, vmin, vrel, "n", True)
226 for option in ("", "c", "whatever", "nbdfc", "nf"):
227 self.assertRaises(errors.BlockDeviceError,
228 bdev.DRBD8._ComputeDiskBarrierArgs,
229 8, 3, 11, option, True)
232 class TestDRBD8Status(testutils.GanetiTestCase):
233 """Testing case for DRBD8 /proc status"""
236 """Read in txt data"""
237 testutils.GanetiTestCase.setUp(self)
238 proc_data = self._TestDataFilename("proc_drbd8.txt")
239 proc80e_data = self._TestDataFilename("proc_drbd80-emptyline.txt")
240 proc83_data = self._TestDataFilename("proc_drbd83.txt")
241 proc83_sync_data = self._TestDataFilename("proc_drbd83_sync.txt")
242 proc83_sync_krnl_data = \
243 self._TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
244 self.proc_data = bdev.DRBD8._GetProcData(filename=proc_data)
245 self.proc80e_data = bdev.DRBD8._GetProcData(filename=proc80e_data)
246 self.proc83_data = bdev.DRBD8._GetProcData(filename=proc83_data)
247 self.proc83_sync_data = bdev.DRBD8._GetProcData(filename=proc83_sync_data)
248 self.proc83_sync_krnl_data = \
249 bdev.DRBD8._GetProcData(filename=proc83_sync_krnl_data)
250 self.mass_data = bdev.DRBD8._MassageProcData(self.proc_data)
251 self.mass80e_data = bdev.DRBD8._MassageProcData(self.proc80e_data)
252 self.mass83_data = bdev.DRBD8._MassageProcData(self.proc83_data)
253 self.mass83_sync_data = bdev.DRBD8._MassageProcData(self.proc83_sync_data)
254 self.mass83_sync_krnl_data = \
255 bdev.DRBD8._MassageProcData(self.proc83_sync_krnl_data)
257 def testIOErrors(self):
258 """Test handling of errors while reading the proc file."""
259 temp_file = self._CreateTempFile()
261 self.failUnlessRaises(errors.BlockDeviceError,
262 bdev.DRBD8._GetProcData, filename=temp_file)
264 def testHelper(self):
265 """Test reading usermode_helper in /sys."""
266 sys_drbd_helper = self._TestDataFilename("sys_drbd_usermode_helper.txt")
267 drbd_helper = bdev.DRBD8.GetUsermodeHelper(filename=sys_drbd_helper)
268 self.failUnlessEqual(drbd_helper, "/bin/true")
270 def testHelperIOErrors(self):
271 """Test handling of errors while reading usermode_helper in /sys."""
272 temp_file = self._CreateTempFile()
274 self.failUnlessRaises(errors.BlockDeviceError,
275 bdev.DRBD8.GetUsermodeHelper, filename=temp_file)
277 def testMinorNotFound(self):
278 """Test not-found-minor in /proc"""
279 self.failUnless(9 not in self.mass_data)
280 self.failUnless(9 not in self.mass83_data)
281 self.failUnless(3 not in self.mass80e_data)
283 def testLineNotMatch(self):
284 """Test wrong line passed to DRBD8Status"""
285 self.assertRaises(errors.BlockDeviceError, bdev.DRBD8Status, "foo")
287 def testMinor0(self):
288 """Test connected, primary device"""
289 for data in [self.mass_data, self.mass83_data]:
290 stats = bdev.DRBD8Status(data[0])
291 self.failUnless(stats.is_in_use)
292 self.failUnless(stats.is_connected and stats.is_primary and
293 stats.peer_secondary and stats.is_disk_uptodate)
295 def testMinor1(self):
296 """Test connected, secondary device"""
297 for data in [self.mass_data, self.mass83_data]:
298 stats = bdev.DRBD8Status(data[1])
299 self.failUnless(stats.is_in_use)
300 self.failUnless(stats.is_connected and stats.is_secondary and
301 stats.peer_primary and stats.is_disk_uptodate)
303 def testMinor2(self):
304 """Test unconfigured device"""
305 for data in [self.mass_data, self.mass83_data, self.mass80e_data]:
306 stats = bdev.DRBD8Status(data[2])
307 self.failIf(stats.is_in_use)
309 def testMinor4(self):
310 """Test WFconn device"""
311 for data in [self.mass_data, self.mass83_data]:
312 stats = bdev.DRBD8Status(data[4])
313 self.failUnless(stats.is_in_use)
314 self.failUnless(stats.is_wfconn and stats.is_primary and
315 stats.rrole == "Unknown" and
316 stats.is_disk_uptodate)
318 def testMinor6(self):
319 """Test diskless device"""
320 for data in [self.mass_data, self.mass83_data]:
321 stats = bdev.DRBD8Status(data[6])
322 self.failUnless(stats.is_in_use)
323 self.failUnless(stats.is_connected and stats.is_secondary and
324 stats.peer_primary and stats.is_diskless)
326 def testMinor8(self):
327 """Test standalone device"""
328 for data in [self.mass_data, self.mass83_data]:
329 stats = bdev.DRBD8Status(data[8])
330 self.failUnless(stats.is_in_use)
331 self.failUnless(stats.is_standalone and
332 stats.rrole == "Unknown" and
333 stats.is_disk_uptodate)
335 def testDRBD83SyncFine(self):
336 stats = bdev.DRBD8Status(self.mass83_sync_data[3])
337 self.failUnless(stats.is_in_resync)
338 self.failUnless(stats.sync_percent is not None)
340 def testDRBD83SyncBroken(self):
341 stats = bdev.DRBD8Status(self.mass83_sync_krnl_data[3])
342 self.failUnless(stats.is_in_resync)
343 self.failUnless(stats.sync_percent is not None)
346 class TestRADOSBlockDevice(testutils.GanetiTestCase):
347 def test_ParseRbdShowmappedOutput(self):
348 volume_name = "abc9778-8e8ace5b.rbd.disk0"
350 ("0\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
351 "1\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd16\n"
352 "line\twith\tfewer\tfields\n"
355 output_no_matches = \
356 ("0\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
357 "1\trbd\tabcdef01-9817.rbd.disk0\t-\t/dev/rbd10\n"
358 "2\trbd\tcdef0123-9817.rbd.disk0\t-\t/dev/rbd12\n"
359 "something\twith\tfewer\tfields"
361 output_extra_matches = \
362 ("0\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd11\n"
363 "1\trbd\te69f28e5-9817.rbd.disk0\t-\t/dev/rbd0\n"
364 "2\t/dev/rbd0\tabc9778-8e8ace5b.rbd.disk0\t-\t/dev/rbd16\n"
365 "something\twith\tfewer\tfields"
368 parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedOutput
369 self.assertEqual(parse_function(output_ok, volume_name), "/dev/rbd16")
370 self.assertRaises(errors.BlockDeviceError, parse_function,
371 output_empty, volume_name)
372 self.assertEqual(parse_function(output_no_matches, volume_name), None)
373 self.assertRaises(errors.BlockDeviceError, parse_function,
374 output_extra_matches, volume_name)
377 class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
379 paths = bdev._GetForbiddenFileStoragePaths()
381 for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
382 self.assertTrue(path in paths)
384 self.assertEqual(set(map(os.path.normpath, paths)), paths)
387 vfsp = bdev._ComputeWrongFileStoragePaths
388 self.assertEqual(vfsp([]), [])
389 self.assertEqual(vfsp(["/tmp"]), [])
390 self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
391 self.assertEqual(vfsp(["/bin"]), ["/bin"])
392 self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
396 class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
398 tmpfile = self._CreateTempFile()
400 utils.WriteFile(tmpfile, data="""
403 # This is a test file
410 self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
418 class TestCheckFileStoragePathInternal(unittest.TestCase):
419 def testNonAbsolute(self):
420 for i in ["", "tmp", "foo/bar/baz"]:
421 self.assertRaises(errors.FileStoragePathError,
422 bdev._CheckFileStoragePath, i, ["/tmp"])
424 self.assertRaises(errors.FileStoragePathError,
425 bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
427 def testNoAllowed(self):
428 self.assertRaises(errors.FileStoragePathError,
429 bdev._CheckFileStoragePath, "/tmp", [])
431 def testNoAdditionalPathComponent(self):
432 self.assertRaises(errors.FileStoragePathError,
433 bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
435 def testAllowed(self):
436 bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
437 bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
440 class TestCheckFileStoragePath(testutils.GanetiTestCase):
441 def testNonExistantFile(self):
442 filename = "/tmp/this/file/does/not/exist"
443 assert not os.path.exists(filename)
444 self.assertRaises(errors.FileStoragePathError,
445 bdev.CheckFileStoragePath, "/bin/", _filename=filename)
446 self.assertRaises(errors.FileStoragePathError,
447 bdev.CheckFileStoragePath, "/srv/file-storage",
450 def testAllowedPath(self):
451 tmpfile = self._CreateTempFile()
453 utils.WriteFile(tmpfile, data="""
457 bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
459 # No additional path component
460 self.assertRaises(errors.FileStoragePathError,
461 bdev.CheckFileStoragePath, "/srv/storage",
465 self.assertRaises(errors.FileStoragePathError,
466 bdev.CheckFileStoragePath, "/usr/lib64/xyz",
470 class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
471 def testDevNull(self):
472 self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
474 def testNonExistantFile(self):
475 filename = "/tmp/this/file/does/not/exist"
476 assert not os.path.exists(filename)
477 self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
480 tmpfile = self._CreateTempFile()
482 utils.WriteFile(tmpfile, data="""
483 # This is a test file
489 self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
496 if __name__ == "__main__":
497 testutils.GanetiTestProgram()