From: Bernardo Dal Seno Date: Tue, 21 May 2013 22:33:08 +0000 (+0200) Subject: Attaching a logical volume builds a list of PVs X-Git-Tag: v2.9.0beta1~309 X-Git-Url: https://code.grnet.gr/git/ganeti-local/commitdiff_plain/b5d48e87510374658fce37421af0072d04dacc94 Attaching a logical volume builds a list of PVs When an LV gets attached, the list of the PVs used by the LV is built. This will be used to count spindles for exclusive_storage, but it could also be useful to optimize disk growing and snapshotting. Signed-off-by: Bernardo Dal Seno Reviewed-by: Helga Velroyen --- diff --git a/lib/storage/bdev.py b/lib/storage/bdev.py index 4c754aa..24465ff 100644 --- a/lib/storage/bdev.py +++ b/lib/storage/bdev.py @@ -172,6 +172,7 @@ class LogicalVolume(base.BlockDev): """ _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$") + _PARSE_PV_DEV_RE = re.compile("^([^ ()]+)\([0-9]+\)$") _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"]) _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"]) @@ -190,6 +191,7 @@ class LogicalVolume(base.BlockDev): self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name) self._degraded = True self.major = self.minor = self.pe_size = self.stripe_count = None + self.pv_names = None self.Attach() @staticmethod @@ -501,10 +503,10 @@ class LogicalVolume(base.BlockDev): """ elems = line.strip().rstrip(sep).split(sep) - if len(elems) != 5: - base.ThrowError("Can't parse LVS output, len(%s) != 5", str(elems)) + if len(elems) != 6: + base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems)) - (status, major, minor, pe_size, stripes) = elems + (status, major, minor, pe_size, stripes, pvs) = elems if len(status) < 6: base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status) @@ -524,17 +526,26 @@ class LogicalVolume(base.BlockDev): except (TypeError, ValueError), err: base.ThrowError("Can't parse the number of stripes: %s", err) - return (status, major, minor, pe_size, stripes) + pv_names = [] + for pv in pvs.split(","): + m = re.match(cls._PARSE_PV_DEV_RE, pv) + if not m: + base.ThrowError("Can't parse this device list: %s", pvs) + pv_names.append(m.group(1)) + assert len(pv_names) > 0 + + return (status, major, minor, pe_size, stripes, pv_names) @classmethod def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd): """Get info about the given existing LV to be used. """ - result = _run_cmd(["lvs", "--noheadings", "--separator=,", + sep = "|" + result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep, "--units=k", "--nosuffix", "-olv_attr,lv_kernel_major,lv_kernel_minor," - "vg_extent_size,stripes", dev_path]) + "vg_extent_size,stripes,devices", dev_path]) if result.failed: base.ThrowError("Can't find LV %s: %s, %s", dev_path, result.fail_reason, result.output) @@ -547,7 +558,12 @@ class LogicalVolume(base.BlockDev): if not out: # totally empty result? splitlines() returns at least # one line for any non-empty string base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out)) - return cls._ParseLvInfoLine(out[-1], ",") + pv_names = set() + for line in out: + (status, major, minor, pe_size, stripes, more_pvs) = \ + cls._ParseLvInfoLine(line, sep) + pv_names.update(more_pvs) + return (status, major, minor, pe_size, stripes, pv_names) def Attach(self): """Attach to an existing LV. @@ -559,7 +575,7 @@ class LogicalVolume(base.BlockDev): """ self.attached = False try: - (status, major, minor, pe_size, stripes) = \ + (status, major, minor, pe_size, stripes, pv_names) = \ self._GetLvInfo(self.dev_path) except errors.BlockDeviceError: return False @@ -570,6 +586,7 @@ class LogicalVolume(base.BlockDev): self.stripe_count = stripes self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing # storage + self.pv_names = pv_names self.attached = True return True diff --git a/test/py/ganeti.storage.bdev_unittest.py b/test/py/ganeti.storage.bdev_unittest.py index 4235c57..f623cef 100755 --- a/test/py/ganeti.storage.bdev_unittest.py +++ b/test/py/ganeti.storage.bdev_unittest.py @@ -305,31 +305,36 @@ class TestLogicalVolume(unittest.TestCase): def testParseLvInfoLine(self): """Tests for LogicalVolume._ParseLvInfoLine.""" broken_lines = [ - " toomuch#-wi-ao#253#3#4096.00#2", - " -wi-ao#253#3#4096.00", - " -wi-a#253#3#4096.00#2", - " -wi-ao#25.3#3#4096.00#2", - " -wi-ao#twenty#3#4096.00#2", - " -wi-ao#253#3.1#4096.00#2", - " -wi-ao#253#three#4096.00#2", - " -wi-ao#253#3#four#2", - " -wi-ao#253#3#4096..00#2", - " -wi-ao#253#3#4096.00#2.0", - " -wi-ao#253#3#4096.00#two", + " toomuch#-wi-ao#253#3#4096.00#2#/dev/abc(20)", + " -wi-ao#253#3#4096.00#/dev/abc(20)", + " -wi-a#253#3#4096.00#2#/dev/abc(20)", + " -wi-ao#25.3#3#4096.00#2#/dev/abc(20)", + " -wi-ao#twenty#3#4096.00#2#/dev/abc(20)", + " -wi-ao#253#3.1#4096.00#2#/dev/abc(20)", + " -wi-ao#253#three#4096.00#2#/dev/abc(20)", + " -wi-ao#253#3#four#2#/dev/abc(20)", + " -wi-ao#253#3#4096..00#2#/dev/abc(20)", + " -wi-ao#253#3#4096.00#2.0#/dev/abc(20)", + " -wi-ao#253#3#4096.00#two#/dev/abc(20)", ] for broken in broken_lines: self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._ParseLvInfoLine, broken, "#") + # Examples of good lines from "lvs": + # -wi-ao|253|3|4096.00|2|/dev/sdb(144),/dev/sdc(0) + # -wi-a-|253|4|4096.00|1|/dev/sdb(208) true_out = [ - ("-wi-ao", 253, 3, 4096.00, 2), - ("-wi-a-", 253, 7, 4096.00, 4), - ("-ri-a-", 253, 4, 4.00, 5), - ("-wc-ao", 15, 18, 4096.00, 32), + ("-wi-ao", 253, 3, 4096.00, 2, ["/dev/abc"]), + ("-wi-a-", 253, 7, 4096.00, 4, ["/dev/abc"]), + ("-ri-a-", 253, 4, 4.00, 5, ["/dev/abc", "/dev/def"]), + ("-wc-ao", 15, 18, 4096.00, 32, ["/dev/abc", "/dev/def", "/dev/ghi0"]), ] for exp in true_out: - for sep in "#;|,": - lvs_line = sep.join((" %s", "%d", "%d", "%.2f", "%d")) % exp + for sep in "#;|": + pvs = ",".join("%s(%s)" % (d, i * 12) for (i, d) in enumerate(exp[-1])) + lvs_line = (sep.join((" %s", "%d", "%d", "%.2f", "%d", "%s")) % + (exp[0:-1] + (pvs,))) parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep) self.assertEqual(parsed, exp) @@ -351,19 +356,36 @@ class TestLogicalVolume(unittest.TestCase): "fake_path", _run_cmd=self._FakeRunCmd(True, "")) self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo, "fake_path", _run_cmd=self._FakeRunCmd(True, "BadStdOut")) - good_line = " -wi-ao,253,3,4096.00,2" + good_line = " -wi-ao|253|3|4096.00|2|/dev/abc(20)" fake_cmd = self._FakeRunCmd(True, good_line) good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd) - # Only the last line should be parsed and taken into account + # If the same line is repeated, the result should be the same for lines in [ [good_line] * 2, [good_line] * 3, - ["bad line", good_line], ]: fake_cmd = self._FakeRunCmd(True, "\n".join(lines)) same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd) self.assertEqual(same_res, good_res) + # Complex multi-line examples + one_line = " -wi-ao|253|3|4096.00|2|/dev/sda(20),/dev/sdb(50),/dev/sdc(0)" + fake_cmd = self._FakeRunCmd(True, one_line) + one_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd) + # These should give the same results + for multi_lines in [ + (" -wi-ao|253|3|4096.00|2|/dev/sda(30),/dev/sdb(50)\n" + " -wi-ao|253|3|4096.00|2|/dev/sdb(200),/dev/sdc(300)"), + (" -wi-ao|253|3|4096.00|2|/dev/sda(0)\n" + " -wi-ao|253|3|4096.00|2|/dev/sdb(20)\n" + " -wi-ao|253|3|4096.00|2|/dev/sdc(30)"), + (" -wi-ao|253|3|4096.00|2|/dev/sda(20)\n" + " -wi-ao|253|3|4096.00|2|/dev/sdb(50),/dev/sdc(0)"), + ]: + fake_cmd = self._FakeRunCmd(True, multi_lines) + multi_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd) + self.assertEqual(multi_res, one_res) + if __name__ == "__main__": testutils.GanetiTestProgram()