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