Revision 688b5752
b/lib/storage/bdev.py | ||
---|---|---|
495 | 495 |
self._lv_name = new_name |
496 | 496 |
self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name) |
497 | 497 |
|
498 |
def Attach(self): |
|
499 |
"""Attach to an existing LV. |
|
500 |
|
|
501 |
This method will try to see if an existing and active LV exists |
|
502 |
which matches our name. If so, its major/minor will be |
|
503 |
recorded. |
|
498 |
@classmethod |
|
499 |
def _ParseLvInfoLine(cls, line, sep): |
|
500 |
"""Parse one line of the lvs output used in L{_GetLvInfo}. |
|
504 | 501 |
|
505 | 502 |
""" |
506 |
self.attached = False |
|
507 |
result = utils.RunCmd(["lvs", "--noheadings", "--separator=,", |
|
508 |
"--units=k", "--nosuffix", |
|
509 |
"-olv_attr,lv_kernel_major,lv_kernel_minor," |
|
510 |
"vg_extent_size,stripes", self.dev_path]) |
|
511 |
if result.failed: |
|
512 |
logging.error("Can't find LV %s: %s, %s", |
|
513 |
self.dev_path, result.fail_reason, result.output) |
|
514 |
return False |
|
515 |
# the output can (and will) have multiple lines for multi-segment |
|
516 |
# LVs, as the 'stripes' parameter is a segment one, so we take |
|
517 |
# only the last entry, which is the one we're interested in; note |
|
518 |
# that with LVM2 anyway the 'stripes' value must be constant |
|
519 |
# across segments, so this is a no-op actually |
|
520 |
out = result.stdout.splitlines() |
|
521 |
if not out: # totally empty result? splitlines() returns at least |
|
522 |
# one line for any non-empty string |
|
523 |
logging.error("Can't parse LVS output, no lines? Got '%s'", str(out)) |
|
524 |
return False |
|
525 |
out = out[-1].strip().rstrip(",") |
|
526 |
out = out.split(",") |
|
527 |
if len(out) != 5: |
|
528 |
logging.error("Can't parse LVS output, len(%s) != 5", str(out)) |
|
529 |
return False |
|
503 |
elems = line.strip().rstrip(sep).split(sep) |
|
504 |
if len(elems) != 5: |
|
505 |
base.ThrowError("Can't parse LVS output, len(%s) != 5", str(elems)) |
|
530 | 506 |
|
531 |
status, major, minor, pe_size, stripes = out
|
|
507 |
(status, major, minor, pe_size, stripes) = elems
|
|
532 | 508 |
if len(status) < 6: |
533 |
logging.error("lvs lv_attr is not at least 6 characters (%s)", status) |
|
534 |
return False |
|
509 |
base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status) |
|
535 | 510 |
|
536 | 511 |
try: |
537 | 512 |
major = int(major) |
538 | 513 |
minor = int(minor) |
539 | 514 |
except (TypeError, ValueError), err: |
540 |
logging.error("lvs major/minor cannot be parsed: %s", str(err))
|
|
515 |
base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
|
|
541 | 516 |
|
542 | 517 |
try: |
543 | 518 |
pe_size = int(float(pe_size)) |
544 | 519 |
except (TypeError, ValueError), err: |
545 |
logging.error("Can't parse vg extent size: %s", err) |
|
546 |
return False |
|
520 |
base.ThrowError("Can't parse vg extent size: %s", err) |
|
547 | 521 |
|
548 | 522 |
try: |
549 | 523 |
stripes = int(stripes) |
550 | 524 |
except (TypeError, ValueError), err: |
551 |
logging.error("Can't parse the number of stripes: %s", err) |
|
525 |
base.ThrowError("Can't parse the number of stripes: %s", err) |
|
526 |
|
|
527 |
return (status, major, minor, pe_size, stripes) |
|
528 |
|
|
529 |
@classmethod |
|
530 |
def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd): |
|
531 |
"""Get info about the given existing LV to be used. |
|
532 |
|
|
533 |
""" |
|
534 |
result = _run_cmd(["lvs", "--noheadings", "--separator=,", |
|
535 |
"--units=k", "--nosuffix", |
|
536 |
"-olv_attr,lv_kernel_major,lv_kernel_minor," |
|
537 |
"vg_extent_size,stripes", dev_path]) |
|
538 |
if result.failed: |
|
539 |
base.ThrowError("Can't find LV %s: %s, %s", |
|
540 |
dev_path, result.fail_reason, result.output) |
|
541 |
# the output can (and will) have multiple lines for multi-segment |
|
542 |
# LVs, as the 'stripes' parameter is a segment one, so we take |
|
543 |
# only the last entry, which is the one we're interested in; note |
|
544 |
# that with LVM2 anyway the 'stripes' value must be constant |
|
545 |
# across segments, so this is a no-op actually |
|
546 |
out = result.stdout.splitlines() |
|
547 |
if not out: # totally empty result? splitlines() returns at least |
|
548 |
# one line for any non-empty string |
|
549 |
base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out)) |
|
550 |
return cls._ParseLvInfoLine(out[-1], ",") |
|
551 |
|
|
552 |
def Attach(self): |
|
553 |
"""Attach to an existing LV. |
|
554 |
|
|
555 |
This method will try to see if an existing and active LV exists |
|
556 |
which matches our name. If so, its major/minor will be |
|
557 |
recorded. |
|
558 |
|
|
559 |
""" |
|
560 |
self.attached = False |
|
561 |
try: |
|
562 |
(status, major, minor, pe_size, stripes) = \ |
|
563 |
self._GetLvInfo(self.dev_path) |
|
564 |
except errors.BlockDeviceError: |
|
552 | 565 |
return False |
553 | 566 |
|
554 | 567 |
self.major = major |
b/test/py/ganeti.storage.bdev_unittest.py | ||
---|---|---|
300 | 300 |
self.assertTrue(len(epvs) == num_req or pvi.free != pvi.size) |
301 | 301 |
|
302 | 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", |
|
309 |
" -wi-ao#253#3#4096.00", |
|
310 |
" -wi-a#253#3#4096.00#2", |
|
311 |
" -wi-ao#25.3#3#4096.00#2", |
|
312 |
" -wi-ao#twenty#3#4096.00#2", |
|
313 |
" -wi-ao#253#3.1#4096.00#2", |
|
314 |
" -wi-ao#253#three#4096.00#2", |
|
315 |
" -wi-ao#253#3#four#2", |
|
316 |
" -wi-ao#253#3#4096..00#2", |
|
317 |
" -wi-ao#253#3#4096.00#2.0", |
|
318 |
" -wi-ao#253#3#4096.00#two", |
|
319 |
] |
|
320 |
for broken in broken_lines: |
|
321 |
self.assertRaises(errors.BlockDeviceError, |
|
322 |
bdev.LogicalVolume._ParseLvInfoLine, broken, "#") |
|
323 |
|
|
324 |
true_out = [ |
|
325 |
("-wi-ao", 253, 3, 4096.00, 2), |
|
326 |
("-wi-a-", 253, 7, 4096.00, 4), |
|
327 |
("-ri-a-", 253, 4, 4.00, 5), |
|
328 |
("-wc-ao", 15, 18, 4096.00, 32), |
|
329 |
] |
|
330 |
for exp in true_out: |
|
331 |
for sep in "#;|,": |
|
332 |
lvs_line = sep.join((" %s", "%d", "%d", "%.2f", "%d")) % exp |
|
333 |
parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep) |
|
334 |
self.assertEqual(parsed, exp) |
|
335 |
|
|
336 |
@staticmethod |
|
337 |
def _FakeRunCmd(success, stdout): |
|
338 |
if success: |
|
339 |
exit_code = 0 |
|
340 |
else: |
|
341 |
exit_code = 1 |
|
342 |
return lambda cmd: utils.RunResult(exit_code, None, stdout, "", cmd, |
|
343 |
utils.process._TIMEOUT_NONE, 5) |
|
344 |
|
|
345 |
def testGetLvInfo(self): |
|
346 |
"""Tests for LogicalVolume._GetLvInfo.""" |
|
347 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo, |
|
348 |
"fake_path", |
|
349 |
_run_cmd=self._FakeRunCmd(False, "Fake error msg")) |
|
350 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo, |
|
351 |
"fake_path", _run_cmd=self._FakeRunCmd(True, "")) |
|
352 |
self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo, |
|
353 |
"fake_path", _run_cmd=self._FakeRunCmd(True, "BadStdOut")) |
|
354 |
good_line = " -wi-ao,253,3,4096.00,2" |
|
355 |
fake_cmd = self._FakeRunCmd(True, good_line) |
|
356 |
good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd) |
|
357 |
# Only the last line should be parsed and taken into account |
|
358 |
for lines in [ |
|
359 |
[good_line] * 2, |
|
360 |
[good_line] * 3, |
|
361 |
["bad line", good_line], |
|
362 |
]: |
|
363 |
fake_cmd = self._FakeRunCmd(True, "\n".join(lines)) |
|
364 |
same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd) |
|
365 |
self.assertEqual(same_res, good_res) |
|
366 |
|
|
367 |
|
|
303 | 368 |
if __name__ == "__main__": |
304 | 369 |
testutils.GanetiTestProgram() |
Also available in: Unified diff