Add unit tests for LUGroupRename
[ganeti-local] / test / py / ganeti.storage.bdev_unittest.py
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()