Statistics
| Branch: | Tag: | Revision:

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

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