Revision b5d48e87

b/lib/storage/bdev.py
172 172

  
173 173
  """
174 174
  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
175
  _PARSE_PV_DEV_RE = re.compile("^([^ ()]+)\([0-9]+\)$")
175 176
  _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
176 177
  _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
177 178

  
......
190 191
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
191 192
    self._degraded = True
192 193
    self.major = self.minor = self.pe_size = self.stripe_count = None
194
    self.pv_names = None
193 195
    self.Attach()
194 196

  
195 197
  @staticmethod
......
501 503

  
502 504
    """
503 505
    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))
506
    if len(elems) != 6:
507
      base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
506 508

  
507
    (status, major, minor, pe_size, stripes) = elems
509
    (status, major, minor, pe_size, stripes, pvs) = elems
508 510
    if len(status) < 6:
509 511
      base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
510 512

  
......
524 526
    except (TypeError, ValueError), err:
525 527
      base.ThrowError("Can't parse the number of stripes: %s", err)
526 528

  
527
    return (status, major, minor, pe_size, stripes)
529
    pv_names = []
530
    for pv in pvs.split(","):
531
      m = re.match(cls._PARSE_PV_DEV_RE, pv)
532
      if not m:
533
        base.ThrowError("Can't parse this device list: %s", pvs)
534
      pv_names.append(m.group(1))
535
    assert len(pv_names) > 0
536

  
537
    return (status, major, minor, pe_size, stripes, pv_names)
528 538

  
529 539
  @classmethod
530 540
  def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd):
531 541
    """Get info about the given existing LV to be used.
532 542

  
533 543
    """
534
    result = _run_cmd(["lvs", "--noheadings", "--separator=,",
544
    sep = "|"
545
    result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
535 546
                       "--units=k", "--nosuffix",
536 547
                       "-olv_attr,lv_kernel_major,lv_kernel_minor,"
537
                       "vg_extent_size,stripes", dev_path])
548
                       "vg_extent_size,stripes,devices", dev_path])
538 549
    if result.failed:
539 550
      base.ThrowError("Can't find LV %s: %s, %s",
540 551
                      dev_path, result.fail_reason, result.output)
......
547 558
    if not out: # totally empty result? splitlines() returns at least
548 559
                # one line for any non-empty string
549 560
      base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
550
    return cls._ParseLvInfoLine(out[-1], ",")
561
    pv_names = set()
562
    for line in out:
563
      (status, major, minor, pe_size, stripes, more_pvs) = \
564
        cls._ParseLvInfoLine(line, sep)
565
      pv_names.update(more_pvs)
566
    return (status, major, minor, pe_size, stripes, pv_names)
551 567

  
552 568
  def Attach(self):
553 569
    """Attach to an existing LV.
......
559 575
    """
560 576
    self.attached = False
561 577
    try:
562
      (status, major, minor, pe_size, stripes) = \
578
      (status, major, minor, pe_size, stripes, pv_names) = \
563 579
        self._GetLvInfo(self.dev_path)
564 580
    except errors.BlockDeviceError:
565 581
      return False
......
570 586
    self.stripe_count = stripes
571 587
    self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
572 588
                                      # storage
589
    self.pv_names = pv_names
573 590
    self.attached = True
574 591
    return True
575 592

  
b/test/py/ganeti.storage.bdev_unittest.py
305 305
  def testParseLvInfoLine(self):
306 306
    """Tests for LogicalVolume._ParseLvInfoLine."""
307 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",
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 319
      ]
320 320
    for broken in broken_lines:
321 321
      self.assertRaises(errors.BlockDeviceError,
322 322
                        bdev.LogicalVolume._ParseLvInfoLine, broken, "#")
323 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)
324 327
    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),
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"]),
329 332
      ]
330 333
    for exp in true_out:
331
      for sep in "#;|,":
332
        lvs_line = sep.join(("  %s", "%d", "%d", "%.2f", "%d")) % exp
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,)))
333 338
        parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep)
334 339
        self.assertEqual(parsed, exp)
335 340

  
......
351 356
                      "fake_path", _run_cmd=self._FakeRunCmd(True, ""))
352 357
    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
353 358
                      "fake_path", _run_cmd=self._FakeRunCmd(True, "BadStdOut"))
354
    good_line = "  -wi-ao,253,3,4096.00,2"
359
    good_line = "  -wi-ao|253|3|4096.00|2|/dev/abc(20)"
355 360
    fake_cmd = self._FakeRunCmd(True, good_line)
356 361
    good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
357
    # Only the last line should be parsed and taken into account
362
    # If the same line is repeated, the result should be the same
358 363
    for lines in [
359 364
      [good_line] * 2,
360 365
      [good_line] * 3,
361
      ["bad line", good_line],
362 366
      ]:
363 367
      fake_cmd = self._FakeRunCmd(True, "\n".join(lines))
364 368
      same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd)
365 369
      self.assertEqual(same_res, good_res)
366 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

  
367 389

  
368 390
if __name__ == "__main__":
369 391
  testutils.GanetiTestProgram()

Also available in: Unified diff