Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.storage.bdev_unittest.py @ f3ebe73e

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()