root / test / py / ganeti.storage.bdev_unittest.py @ 14933c17
History | View | Annotate | Download (11.4 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 TestExclusiveStoragePvs(unittest.TestCase): |
108 |
"""Test cases for functions dealing with LVM PV and exclusive storage"""
|
109 |
# Allowance for rounding
|
110 |
_EPS = 1e-4
|
111 |
_MARGIN = constants.PART_MARGIN + constants.PART_RESERVED + _EPS |
112 |
|
113 |
@staticmethod
|
114 |
def _GenerateRandomPvInfo(rnd, name, vg): |
115 |
# Granularity is .01 MiB
|
116 |
size = rnd.randint(1024 * 100, 10 * 1024 * 1024 * 100) |
117 |
if rnd.choice([False, True]): |
118 |
free = float(rnd.randint(0, size)) / 100.0 |
119 |
else:
|
120 |
free = float(size) / 100.0 |
121 |
size = float(size) / 100.0 |
122 |
attr = "a-"
|
123 |
return objects.LvmPvInfo(name=name, vg_name=vg, size=size, free=free,
|
124 |
attributes=attr) |
125 |
|
126 |
def testGetStdPvSize(self): |
127 |
"""Test cases for bdev.LogicalVolume._GetStdPvSize()"""
|
128 |
rnd = random.Random(9517)
|
129 |
for _ in range(0, 50): |
130 |
# Identical volumes
|
131 |
pvi = self._GenerateRandomPvInfo(rnd, "disk", "myvg") |
132 |
onesize = bdev.LogicalVolume._GetStdPvSize([pvi]) |
133 |
self.assertTrue(onesize <= pvi.size)
|
134 |
self.assertTrue(onesize > pvi.size * (1 - self._MARGIN)) |
135 |
for length in range(2, 10): |
136 |
n_size = bdev.LogicalVolume._GetStdPvSize([pvi] * length) |
137 |
self.assertEqual(onesize, n_size)
|
138 |
|
139 |
# Mixed volumes
|
140 |
for length in range(1, 10): |
141 |
pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg") |
142 |
for _ in range(0, length)] |
143 |
std_size = bdev.LogicalVolume._GetStdPvSize(pvlist) |
144 |
self.assertTrue(compat.all(std_size <= pvi.size for pvi in pvlist)) |
145 |
self.assertTrue(compat.any(std_size > pvi.size * (1 - self._MARGIN) |
146 |
for pvi in pvlist)) |
147 |
pvlist.append(pvlist[0])
|
148 |
p1_size = bdev.LogicalVolume._GetStdPvSize(pvlist) |
149 |
self.assertEqual(std_size, p1_size)
|
150 |
|
151 |
def testComputeNumPvs(self): |
152 |
"""Test cases for bdev.LogicalVolume._ComputeNumPvs()"""
|
153 |
rnd = random.Random(8067)
|
154 |
for _ in range(0, 1000): |
155 |
pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")] |
156 |
lv_size = float(rnd.randint(10 * 100, 1024 * 1024 * 100)) / 100.0 |
157 |
num_pv = bdev.LogicalVolume._ComputeNumPvs(lv_size, pvlist) |
158 |
std_size = bdev.LogicalVolume._GetStdPvSize(pvlist) |
159 |
self.assertTrue(num_pv >= 1) |
160 |
self.assertTrue(num_pv * std_size >= lv_size)
|
161 |
self.assertTrue((num_pv - 1) * std_size < lv_size * (1 + self._EPS)) |
162 |
|
163 |
def testGetEmptyPvNames(self): |
164 |
"""Test cases for bdev.LogicalVolume._GetEmptyPvNames()"""
|
165 |
rnd = random.Random(21126)
|
166 |
for _ in range(0, 100): |
167 |
num_pvs = rnd.randint(1, 20) |
168 |
pvlist = [self._GenerateRandomPvInfo(rnd, "disk%d" % n, "myvg") |
169 |
for n in range(0, num_pvs)] |
170 |
for num_req in range(1, num_pvs + 2): |
171 |
epvs = bdev.LogicalVolume._GetEmptyPvNames(pvlist, num_req) |
172 |
epvs_set = compat.UniqueFrozenset(epvs) |
173 |
if len(epvs) > 1: |
174 |
self.assertEqual(len(epvs), len(epvs_set)) |
175 |
for pvi in pvlist: |
176 |
if pvi.name in epvs_set: |
177 |
self.assertEqual(pvi.size, pvi.free)
|
178 |
else:
|
179 |
# There should be no remaining empty PV when less than the
|
180 |
# requeste number of PVs has been returned
|
181 |
self.assertTrue(len(epvs) == num_req or pvi.free != pvi.size) |
182 |
|
183 |
|
184 |
class TestLogicalVolume(unittest.TestCase): |
185 |
"""Tests for bdev.LogicalVolume."""
|
186 |
def testParseLvInfoLine(self): |
187 |
"""Tests for LogicalVolume._ParseLvInfoLine."""
|
188 |
broken_lines = [ |
189 |
" toomuch#-wi-ao#253#3#4096.00#2#/dev/abc(20)",
|
190 |
" -wi-ao#253#3#4096.00#/dev/abc(20)",
|
191 |
" -wi-a#253#3#4096.00#2#/dev/abc(20)",
|
192 |
" -wi-ao#25.3#3#4096.00#2#/dev/abc(20)",
|
193 |
" -wi-ao#twenty#3#4096.00#2#/dev/abc(20)",
|
194 |
" -wi-ao#253#3.1#4096.00#2#/dev/abc(20)",
|
195 |
" -wi-ao#253#three#4096.00#2#/dev/abc(20)",
|
196 |
" -wi-ao#253#3#four#2#/dev/abc(20)",
|
197 |
" -wi-ao#253#3#4096..00#2#/dev/abc(20)",
|
198 |
" -wi-ao#253#3#4096.00#2.0#/dev/abc(20)",
|
199 |
" -wi-ao#253#3#4096.00#two#/dev/abc(20)",
|
200 |
] |
201 |
for broken in broken_lines: |
202 |
self.assertRaises(errors.BlockDeviceError,
|
203 |
bdev.LogicalVolume._ParseLvInfoLine, broken, "#")
|
204 |
|
205 |
# Examples of good lines from "lvs":
|
206 |
# -wi-ao|253|3|4096.00|2|/dev/sdb(144),/dev/sdc(0)
|
207 |
# -wi-a-|253|4|4096.00|1|/dev/sdb(208)
|
208 |
true_out = [ |
209 |
("-wi-ao", 253, 3, 4096.00, 2, ["/dev/abc"]), |
210 |
("-wi-a-", 253, 7, 4096.00, 4, ["/dev/abc"]), |
211 |
("-ri-a-", 253, 4, 4.00, 5, ["/dev/abc", "/dev/def"]), |
212 |
("-wc-ao", 15, 18, 4096.00, 32, ["/dev/abc", "/dev/def", "/dev/ghi0"]), |
213 |
] |
214 |
for exp in true_out: |
215 |
for sep in "#;|": |
216 |
pvs = ",".join("%s(%s)" % (d, i * 12) for (i, d) in enumerate(exp[-1])) |
217 |
lvs_line = (sep.join((" %s", "%d", "%d", "%.2f", "%d", "%s")) % |
218 |
(exp[0:-1] + (pvs,))) |
219 |
parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep) |
220 |
self.assertEqual(parsed, exp)
|
221 |
|
222 |
@staticmethod
|
223 |
def _FakeRunCmd(success, stdout): |
224 |
if success:
|
225 |
exit_code = 0
|
226 |
else:
|
227 |
exit_code = 1
|
228 |
return lambda cmd: utils.RunResult(exit_code, None, stdout, "", cmd, |
229 |
utils.process._TIMEOUT_NONE, 5)
|
230 |
|
231 |
def testGetLvInfo(self): |
232 |
"""Tests for LogicalVolume._GetLvInfo."""
|
233 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
|
234 |
"fake_path",
|
235 |
_run_cmd=self._FakeRunCmd(False, "Fake error msg")) |
236 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
|
237 |
"fake_path", _run_cmd=self._FakeRunCmd(True, "")) |
238 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
|
239 |
"fake_path", _run_cmd=self._FakeRunCmd(True, "BadStdOut")) |
240 |
good_line = " -wi-ao|253|3|4096.00|2|/dev/abc(20)"
|
241 |
fake_cmd = self._FakeRunCmd(True, good_line) |
242 |
good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
|
243 |
# If the same line is repeated, the result should be the same
|
244 |
for lines in [ |
245 |
[good_line] * 2,
|
246 |
[good_line] * 3,
|
247 |
]: |
248 |
fake_cmd = self._FakeRunCmd(True, "\n".join(lines)) |
249 |
same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd)
|
250 |
self.assertEqual(same_res, good_res)
|
251 |
|
252 |
# Complex multi-line examples
|
253 |
one_line = " -wi-ao|253|3|4096.00|2|/dev/sda(20),/dev/sdb(50),/dev/sdc(0)"
|
254 |
fake_cmd = self._FakeRunCmd(True, one_line) |
255 |
one_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
|
256 |
# These should give the same results
|
257 |
for multi_lines in [ |
258 |
(" -wi-ao|253|3|4096.00|2|/dev/sda(30),/dev/sdb(50)\n"
|
259 |
" -wi-ao|253|3|4096.00|2|/dev/sdb(200),/dev/sdc(300)"),
|
260 |
(" -wi-ao|253|3|4096.00|2|/dev/sda(0)\n"
|
261 |
" -wi-ao|253|3|4096.00|2|/dev/sdb(20)\n"
|
262 |
" -wi-ao|253|3|4096.00|2|/dev/sdc(30)"),
|
263 |
(" -wi-ao|253|3|4096.00|2|/dev/sda(20)\n"
|
264 |
" -wi-ao|253|3|4096.00|2|/dev/sdb(50),/dev/sdc(0)"),
|
265 |
]: |
266 |
fake_cmd = self._FakeRunCmd(True, multi_lines) |
267 |
multi_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
|
268 |
self.assertEqual(multi_res, one_res)
|
269 |
|
270 |
|
271 |
if __name__ == "__main__": |
272 |
testutils.GanetiTestProgram() |