root / test / py / ganeti.storage.bdev_unittest.py @ 20faaa74
History | View | Annotate | Download (15 kB)
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 bdev module"""
|
23 |
|
24 |
|
25 |
import os |
26 |
import random |
27 |
import unittest |
28 |
|
29 |
from ganeti import compat |
30 |
from ganeti import constants |
31 |
from ganeti import errors |
32 |
from ganeti import objects |
33 |
from ganeti import utils |
34 |
from ganeti.storage import bdev |
35 |
|
36 |
import testutils |
37 |
|
38 |
|
39 |
class TestRADOSBlockDevice(testutils.GanetiTestCase): |
40 |
def setUp(self): |
41 |
"""Set up input data"""
|
42 |
testutils.GanetiTestCase.setUp(self)
|
43 |
|
44 |
self.plain_output_old_ok = \
|
45 |
testutils.ReadTestData("bdev-rbd/plain_output_old_ok.txt")
|
46 |
self.plain_output_old_no_matches = \
|
47 |
testutils.ReadTestData("bdev-rbd/plain_output_old_no_matches.txt")
|
48 |
self.plain_output_old_extra_matches = \
|
49 |
testutils.ReadTestData("bdev-rbd/plain_output_old_extra_matches.txt")
|
50 |
self.plain_output_old_empty = \
|
51 |
testutils.ReadTestData("bdev-rbd/plain_output_old_empty.txt")
|
52 |
self.plain_output_new_ok = \
|
53 |
testutils.ReadTestData("bdev-rbd/plain_output_new_ok.txt")
|
54 |
self.plain_output_new_no_matches = \
|
55 |
testutils.ReadTestData("bdev-rbd/plain_output_new_no_matches.txt")
|
56 |
self.plain_output_new_extra_matches = \
|
57 |
testutils.ReadTestData("bdev-rbd/plain_output_new_extra_matches.txt")
|
58 |
# This file is completely empty, and as such it's not shipped.
|
59 |
self.plain_output_new_empty = "" |
60 |
self.json_output_ok = testutils.ReadTestData("bdev-rbd/json_output_ok.txt") |
61 |
self.json_output_no_matches = \
|
62 |
testutils.ReadTestData("bdev-rbd/json_output_no_matches.txt")
|
63 |
self.json_output_extra_matches = \
|
64 |
testutils.ReadTestData("bdev-rbd/json_output_extra_matches.txt")
|
65 |
self.json_output_empty = \
|
66 |
testutils.ReadTestData("bdev-rbd/json_output_empty.txt")
|
67 |
self.output_invalid = testutils.ReadTestData("bdev-rbd/output_invalid.txt") |
68 |
|
69 |
self.volume_name = "d7ab910a-4933-4ffe-88d0-faf2ce31390a.rbd.disk0" |
70 |
|
71 |
def test_ParseRbdShowmappedJson(self): |
72 |
parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedJson |
73 |
|
74 |
self.assertEqual(parse_function(self.json_output_ok, self.volume_name), |
75 |
"/dev/rbd3")
|
76 |
self.assertEqual(parse_function(self.json_output_empty, self.volume_name), |
77 |
None)
|
78 |
self.assertEqual(parse_function(self.json_output_no_matches, |
79 |
self.volume_name), None) |
80 |
self.assertRaises(errors.BlockDeviceError, parse_function,
|
81 |
self.json_output_extra_matches, self.volume_name) |
82 |
self.assertRaises(errors.BlockDeviceError, parse_function,
|
83 |
self.output_invalid, self.volume_name) |
84 |
|
85 |
def test_ParseRbdShowmappedPlain(self): |
86 |
parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedPlain |
87 |
|
88 |
self.assertEqual(parse_function(self.plain_output_new_ok, |
89 |
self.volume_name), "/dev/rbd3") |
90 |
self.assertEqual(parse_function(self.plain_output_old_ok, |
91 |
self.volume_name), "/dev/rbd3") |
92 |
self.assertEqual(parse_function(self.plain_output_new_empty, |
93 |
self.volume_name), None) |
94 |
self.assertEqual(parse_function(self.plain_output_old_empty, |
95 |
self.volume_name), None) |
96 |
self.assertEqual(parse_function(self.plain_output_new_no_matches, |
97 |
self.volume_name), None) |
98 |
self.assertEqual(parse_function(self.plain_output_old_no_matches, |
99 |
self.volume_name), None) |
100 |
self.assertRaises(errors.BlockDeviceError, parse_function,
|
101 |
self.plain_output_new_extra_matches, self.volume_name) |
102 |
self.assertRaises(errors.BlockDeviceError, parse_function,
|
103 |
self.plain_output_old_extra_matches, self.volume_name) |
104 |
self.assertRaises(errors.BlockDeviceError, parse_function,
|
105 |
self.output_invalid, self.volume_name) |
106 |
|
107 |
class TestComputeWrongFileStoragePathsInternal(unittest.TestCase): |
108 |
def testPaths(self): |
109 |
paths = bdev._GetForbiddenFileStoragePaths() |
110 |
|
111 |
for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]: |
112 |
self.assertTrue(path in paths) |
113 |
|
114 |
self.assertEqual(set(map(os.path.normpath, paths)), paths) |
115 |
|
116 |
def test(self): |
117 |
vfsp = bdev._ComputeWrongFileStoragePaths |
118 |
self.assertEqual(vfsp([]), [])
|
119 |
self.assertEqual(vfsp(["/tmp"]), []) |
120 |
self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"]) |
121 |
self.assertEqual(vfsp(["/bin"]), ["/bin"]) |
122 |
self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]), |
123 |
["/usr/sbin/vim"])
|
124 |
|
125 |
|
126 |
class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase): |
127 |
def test(self): |
128 |
tmpfile = self._CreateTempFile()
|
129 |
|
130 |
utils.WriteFile(tmpfile, data="""
|
131 |
/tmp
|
132 |
x/y///z/relative
|
133 |
# This is a test file
|
134 |
/srv/storage
|
135 |
/bin
|
136 |
/usr/local/lib32/
|
137 |
relative/path
|
138 |
""")
|
139 |
|
140 |
self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
|
141 |
"/bin",
|
142 |
"/usr/local/lib32",
|
143 |
"relative/path",
|
144 |
"x/y/z/relative",
|
145 |
]) |
146 |
|
147 |
|
148 |
class TestCheckFileStoragePathInternal(unittest.TestCase): |
149 |
def testNonAbsolute(self): |
150 |
for i in ["", "tmp", "foo/bar/baz"]: |
151 |
self.assertRaises(errors.FileStoragePathError,
|
152 |
bdev._CheckFileStoragePath, i, ["/tmp"])
|
153 |
|
154 |
self.assertRaises(errors.FileStoragePathError,
|
155 |
bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"]) |
156 |
|
157 |
def testNoAllowed(self): |
158 |
self.assertRaises(errors.FileStoragePathError,
|
159 |
bdev._CheckFileStoragePath, "/tmp", [])
|
160 |
|
161 |
def testNoAdditionalPathComponent(self): |
162 |
self.assertRaises(errors.FileStoragePathError,
|
163 |
bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"]) |
164 |
|
165 |
def testAllowed(self): |
166 |
bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"]) |
167 |
bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"]) |
168 |
|
169 |
|
170 |
class TestCheckFileStoragePath(testutils.GanetiTestCase): |
171 |
def testNonExistantFile(self): |
172 |
filename = "/tmp/this/file/does/not/exist"
|
173 |
assert not os.path.exists(filename) |
174 |
self.assertRaises(errors.FileStoragePathError,
|
175 |
bdev.CheckFileStoragePath, "/bin/", _filename=filename)
|
176 |
self.assertRaises(errors.FileStoragePathError,
|
177 |
bdev.CheckFileStoragePath, "/srv/file-storage",
|
178 |
_filename=filename) |
179 |
|
180 |
def testAllowedPath(self): |
181 |
tmpfile = self._CreateTempFile()
|
182 |
|
183 |
utils.WriteFile(tmpfile, data="""
|
184 |
/srv/storage
|
185 |
""")
|
186 |
|
187 |
bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
|
188 |
|
189 |
# No additional path component
|
190 |
self.assertRaises(errors.FileStoragePathError,
|
191 |
bdev.CheckFileStoragePath, "/srv/storage",
|
192 |
_filename=tmpfile) |
193 |
|
194 |
# Forbidden path
|
195 |
self.assertRaises(errors.FileStoragePathError,
|
196 |
bdev.CheckFileStoragePath, "/usr/lib64/xyz",
|
197 |
_filename=tmpfile) |
198 |
|
199 |
|
200 |
class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase): |
201 |
def testDevNull(self): |
202 |
self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), []) |
203 |
|
204 |
def testNonExistantFile(self): |
205 |
filename = "/tmp/this/file/does/not/exist"
|
206 |
assert not os.path.exists(filename) |
207 |
self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
|
208 |
|
209 |
def test(self): |
210 |
tmpfile = self._CreateTempFile()
|
211 |
|
212 |
utils.WriteFile(tmpfile, data="""
|
213 |
# This is a test file
|
214 |
/tmp
|
215 |
/srv/storage
|
216 |
relative/path
|
217 |
""")
|
218 |
|
219 |
self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
|
220 |
"/tmp",
|
221 |
"/srv/storage",
|
222 |
"relative/path",
|
223 |
]) |
224 |
|
225 |
|
226 |
class TestExclusiveStoragePvs(unittest.TestCase): |
227 |
"""Test cases for functions dealing with LVM PV and exclusive storage"""
|
228 |
# Allowance for rounding
|
229 |
_EPS = 1e-4
|
230 |
_MARGIN = constants.PART_MARGIN + constants.PART_RESERVED + _EPS |
231 |
|
232 |
@staticmethod
|
233 |
def _GenerateRandomPvInfo(rnd, name, vg): |
234 |
# Granularity is .01 MiB
|
235 |
size = rnd.randint(1024 * 100, 10 * 1024 * 1024 * 100) |
236 |
if rnd.choice([False, True]): |
237 |
free = float(rnd.randint(0, size)) / 100.0 |
238 |
else:
|
239 |
free = float(size) / 100.0 |
240 |
size = float(size) / 100.0 |
241 |
attr = "a-"
|
242 |
return objects.LvmPvInfo(name=name, vg_name=vg, size=size, free=free,
|
243 |
attributes=attr) |
244 |
|
245 |
def testGetStdPvSize(self): |
246 |
"""Test cases for bdev.LogicalVolume._GetStdPvSize()"""
|
247 |
rnd = random.Random(9517)
|
248 |
for _ in range(0, 50): |
249 |
# Identical volumes
|
250 |
pvi = self._GenerateRandomPvInfo(rnd, "disk", "myvg") |
251 |
onesize = bdev.LogicalVolume._GetStdPvSize([pvi]) |
252 |
self.assertTrue(onesize <= pvi.size)
|
253 |
self.assertTrue(onesize > pvi.size * (1 - self._MARGIN)) |
254 |
for length in range(2, 10): |
255 |
n_size = bdev.LogicalVolume._GetStdPvSize([pvi] * length) |
256 |
self.assertEqual(onesize, n_size)
|
257 |
|
258 |
# Mixed volumes
|
259 |
for length in range(1, 10): |
260 |
pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg") |
261 |
for _ in range(0, length)] |
262 |
std_size = bdev.LogicalVolume._GetStdPvSize(pvlist) |
263 |
self.assertTrue(compat.all(std_size <= pvi.size for pvi in pvlist)) |
264 |
self.assertTrue(compat.any(std_size > pvi.size * (1 - self._MARGIN) |
265 |
for pvi in pvlist)) |
266 |
pvlist.append(pvlist[0])
|
267 |
p1_size = bdev.LogicalVolume._GetStdPvSize(pvlist) |
268 |
self.assertEqual(std_size, p1_size)
|
269 |
|
270 |
def testComputeNumPvs(self): |
271 |
"""Test cases for bdev.LogicalVolume._ComputeNumPvs()"""
|
272 |
rnd = random.Random(8067)
|
273 |
for _ in range(0, 1000): |
274 |
pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")] |
275 |
lv_size = float(rnd.randint(10 * 100, 1024 * 1024 * 100)) / 100.0 |
276 |
num_pv = bdev.LogicalVolume._ComputeNumPvs(lv_size, pvlist) |
277 |
std_size = bdev.LogicalVolume._GetStdPvSize(pvlist) |
278 |
self.assertTrue(num_pv >= 1) |
279 |
self.assertTrue(num_pv * std_size >= lv_size)
|
280 |
self.assertTrue((num_pv - 1) * std_size < lv_size * (1 + self._EPS)) |
281 |
|
282 |
def testGetEmptyPvNames(self): |
283 |
"""Test cases for bdev.LogicalVolume._GetEmptyPvNames()"""
|
284 |
rnd = random.Random(21126)
|
285 |
for _ in range(0, 100): |
286 |
num_pvs = rnd.randint(1, 20) |
287 |
pvlist = [self._GenerateRandomPvInfo(rnd, "disk%d" % n, "myvg") |
288 |
for n in range(0, num_pvs)] |
289 |
for num_req in range(1, num_pvs + 2): |
290 |
epvs = bdev.LogicalVolume._GetEmptyPvNames(pvlist, num_req) |
291 |
epvs_set = compat.UniqueFrozenset(epvs) |
292 |
if len(epvs) > 1: |
293 |
self.assertEqual(len(epvs), len(epvs_set)) |
294 |
for pvi in pvlist: |
295 |
if pvi.name in epvs_set: |
296 |
self.assertEqual(pvi.size, pvi.free)
|
297 |
else:
|
298 |
# There should be no remaining empty PV when less than the
|
299 |
# requeste number of PVs has been returned
|
300 |
self.assertTrue(len(epvs) == num_req or pvi.free != pvi.size) |
301 |
|
302 |
|
303 |
class TestLogicalVolume(unittest.TestCase): |
304 |
"""Tests for bdev.LogicalVolume."""
|
305 |
def testParseLvInfoLine(self): |
306 |
"""Tests for LogicalVolume._ParseLvInfoLine."""
|
307 |
broken_lines = [ |
308 |
" toomuch#-wi-ao#253#3#4096.00#2#/dev/abc(20)",
|
309 |
" -wi-ao#253#3#4096.00#/dev/abc(20)",
|
310 |
" -wi-a#253#3#4096.00#2#/dev/abc(20)",
|
311 |
" -wi-ao#25.3#3#4096.00#2#/dev/abc(20)",
|
312 |
" -wi-ao#twenty#3#4096.00#2#/dev/abc(20)",
|
313 |
" -wi-ao#253#3.1#4096.00#2#/dev/abc(20)",
|
314 |
" -wi-ao#253#three#4096.00#2#/dev/abc(20)",
|
315 |
" -wi-ao#253#3#four#2#/dev/abc(20)",
|
316 |
" -wi-ao#253#3#4096..00#2#/dev/abc(20)",
|
317 |
" -wi-ao#253#3#4096.00#2.0#/dev/abc(20)",
|
318 |
" -wi-ao#253#3#4096.00#two#/dev/abc(20)",
|
319 |
] |
320 |
for broken in broken_lines: |
321 |
self.assertRaises(errors.BlockDeviceError,
|
322 |
bdev.LogicalVolume._ParseLvInfoLine, broken, "#")
|
323 |
|
324 |
# Examples of good lines from "lvs":
|
325 |
# -wi-ao|253|3|4096.00|2|/dev/sdb(144),/dev/sdc(0)
|
326 |
# -wi-a-|253|4|4096.00|1|/dev/sdb(208)
|
327 |
true_out = [ |
328 |
("-wi-ao", 253, 3, 4096.00, 2, ["/dev/abc"]), |
329 |
("-wi-a-", 253, 7, 4096.00, 4, ["/dev/abc"]), |
330 |
("-ri-a-", 253, 4, 4.00, 5, ["/dev/abc", "/dev/def"]), |
331 |
("-wc-ao", 15, 18, 4096.00, 32, ["/dev/abc", "/dev/def", "/dev/ghi0"]), |
332 |
] |
333 |
for exp in true_out: |
334 |
for sep in "#;|": |
335 |
pvs = ",".join("%s(%s)" % (d, i * 12) for (i, d) in enumerate(exp[-1])) |
336 |
lvs_line = (sep.join((" %s", "%d", "%d", "%.2f", "%d", "%s")) % |
337 |
(exp[0:-1] + (pvs,))) |
338 |
parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep) |
339 |
self.assertEqual(parsed, exp)
|
340 |
|
341 |
@staticmethod
|
342 |
def _FakeRunCmd(success, stdout): |
343 |
if success:
|
344 |
exit_code = 0
|
345 |
else:
|
346 |
exit_code = 1
|
347 |
return lambda cmd: utils.RunResult(exit_code, None, stdout, "", cmd, |
348 |
utils.process._TIMEOUT_NONE, 5)
|
349 |
|
350 |
def testGetLvInfo(self): |
351 |
"""Tests for LogicalVolume._GetLvInfo."""
|
352 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
|
353 |
"fake_path",
|
354 |
_run_cmd=self._FakeRunCmd(False, "Fake error msg")) |
355 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
|
356 |
"fake_path", _run_cmd=self._FakeRunCmd(True, "")) |
357 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
|
358 |
"fake_path", _run_cmd=self._FakeRunCmd(True, "BadStdOut")) |
359 |
good_line = " -wi-ao|253|3|4096.00|2|/dev/abc(20)"
|
360 |
fake_cmd = self._FakeRunCmd(True, good_line) |
361 |
good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
|
362 |
# If the same line is repeated, the result should be the same
|
363 |
for lines in [ |
364 |
[good_line] * 2,
|
365 |
[good_line] * 3,
|
366 |
]: |
367 |
fake_cmd = self._FakeRunCmd(True, "\n".join(lines)) |
368 |
same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd)
|
369 |
self.assertEqual(same_res, good_res)
|
370 |
|
371 |
# Complex multi-line examples
|
372 |
one_line = " -wi-ao|253|3|4096.00|2|/dev/sda(20),/dev/sdb(50),/dev/sdc(0)"
|
373 |
fake_cmd = self._FakeRunCmd(True, one_line) |
374 |
one_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
|
375 |
# These should give the same results
|
376 |
for multi_lines in [ |
377 |
(" -wi-ao|253|3|4096.00|2|/dev/sda(30),/dev/sdb(50)\n"
|
378 |
" -wi-ao|253|3|4096.00|2|/dev/sdb(200),/dev/sdc(300)"),
|
379 |
(" -wi-ao|253|3|4096.00|2|/dev/sda(0)\n"
|
380 |
" -wi-ao|253|3|4096.00|2|/dev/sdb(20)\n"
|
381 |
" -wi-ao|253|3|4096.00|2|/dev/sdc(30)"),
|
382 |
(" -wi-ao|253|3|4096.00|2|/dev/sda(20)\n"
|
383 |
" -wi-ao|253|3|4096.00|2|/dev/sdb(50),/dev/sdc(0)"),
|
384 |
]: |
385 |
fake_cmd = self._FakeRunCmd(True, multi_lines) |
386 |
multi_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
|
387 |
self.assertEqual(multi_res, one_res)
|
388 |
|
389 |
|
390 |
if __name__ == "__main__": |
391 |
testutils.GanetiTestProgram() |