diff --git a/lib/block/drbd.py b/lib/block/drbd.py index 5f1d47b673f2156df1d510ed1bdf1ff55cbcbeff..76f58b4dc4308ec0da092c5591b9c58782217be2 100644 --- a/lib/block/drbd.py +++ b/lib/block/drbd.py @@ -265,6 +265,116 @@ class DRBD8Info(object): return DRBD8Info.CreateFromLines(lines) +class DRBD8ShowInfo(object): + """Helper class which parses the output of drbdsetup show + + """ + _PARSE_SHOW = None + + @classmethod + def _GetShowParser(cls): + """Return a parser for `drbd show` output. + + This will either create or return an already-created parser for the + output of the command `drbd show`. + + """ + if cls._PARSE_SHOW is not None: + return cls._PARSE_SHOW + + # pyparsing setup + lbrace = pyp.Literal("{").suppress() + rbrace = pyp.Literal("}").suppress() + lbracket = pyp.Literal("[").suppress() + rbracket = pyp.Literal("]").suppress() + semi = pyp.Literal(";").suppress() + colon = pyp.Literal(":").suppress() + # this also converts the value to an int + number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0])) + + comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine) + defa = pyp.Literal("_is_default").suppress() + dbl_quote = pyp.Literal('"').suppress() + + keyword = pyp.Word(pyp.alphanums + "-") + + # value types + value = pyp.Word(pyp.alphanums + "_-/.:") + quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote + ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() + + pyp.Word(pyp.nums + ".") + colon + number) + ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() + + pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") + + pyp.Optional(rbracket) + colon + number) + # meta device, extended syntax + meta_value = ((value ^ quoted) + lbracket + number + rbracket) + # device name, extended syntax + device_value = pyp.Literal("minor").suppress() + number + + # a statement + stmt = (~rbrace + keyword + ~lbrace + + pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^ + device_value) + + pyp.Optional(defa) + semi + + pyp.Optional(pyp.restOfLine).suppress()) + + # an entire section + section_name = pyp.Word(pyp.alphas + "_") + section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace + + bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt)) + bnf.ignore(comment) + + cls._PARSE_SHOW = bnf + + return bnf + + @classmethod + def GetDevInfo(cls, show_data): + """Parse details about a given DRBD minor. + + This returns, if available, the local backing device (as a path) + and the local and remote (ip, port) information from a string + containing the output of the `drbdsetup show` command as returned + by DRBD8._GetShowData. + + This will return a dict with keys: + - local_dev + - meta_dev + - meta_index + - local_addr + - remote_addr + + """ + retval = {} + if not show_data: + return retval + + try: + # run pyparse + results = (cls._GetShowParser()).parseString(show_data) + except pyp.ParseException, err: + base.ThrowError("Can't parse drbdsetup show output: %s", str(err)) + + # and massage the results into our desired format + for section in results: + sname = section[0] + if sname == "_this_host": + for lst in section[1:]: + if lst[0] == "disk": + retval["local_dev"] = lst[1] + elif lst[0] == "meta-disk": + retval["meta_dev"] = lst[1] + retval["meta_index"] = lst[2] + elif lst[0] == "address": + retval["local_addr"] = tuple(lst[1:]) + elif sname == "_remote_host": + for lst in section[1:]: + if lst[0] == "address": + retval["remote_addr"] = tuple(lst[1:]) + return retval + + class DRBD8(base.BlockDev): """DRBD v8.x block device. @@ -283,7 +393,6 @@ class DRBD8(base.BlockDev): _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper" _MAX_MINORS = 255 - _PARSE_SHOW = None # timeout constants _NET_RECONFIG_TIMEOUT = 60 @@ -449,64 +558,6 @@ class DRBD8(base.BlockDev): return highest + 1 - @classmethod - def _GetShowParser(cls): - """Return a parser for `drbd show` output. - - This will either create or return an already-created parser for the - output of the command `drbd show`. - - """ - if cls._PARSE_SHOW is not None: - return cls._PARSE_SHOW - - # pyparsing setup - lbrace = pyp.Literal("{").suppress() - rbrace = pyp.Literal("}").suppress() - lbracket = pyp.Literal("[").suppress() - rbracket = pyp.Literal("]").suppress() - semi = pyp.Literal(";").suppress() - colon = pyp.Literal(":").suppress() - # this also converts the value to an int - number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0])) - - comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine) - defa = pyp.Literal("_is_default").suppress() - dbl_quote = pyp.Literal('"').suppress() - - keyword = pyp.Word(pyp.alphanums + "-") - - # value types - value = pyp.Word(pyp.alphanums + "_-/.:") - quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote - ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() + - pyp.Word(pyp.nums + ".") + colon + number) - ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() + - pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") + - pyp.Optional(rbracket) + colon + number) - # meta device, extended syntax - meta_value = ((value ^ quoted) + lbracket + number + rbracket) - # device name, extended syntax - device_value = pyp.Literal("minor").suppress() + number - - # a statement - stmt = (~rbrace + keyword + ~lbrace + - pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^ - device_value) + - pyp.Optional(defa) + semi + - pyp.Optional(pyp.restOfLine).suppress()) - - # an entire section - section_name = pyp.Word(pyp.alphas + "_") - section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace - - bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt)) - bnf.ignore(comment) - - cls._PARSE_SHOW = bnf - - return bnf - @classmethod def _GetShowData(cls, minor): """Return the `drbdsetup show` data for a minor. @@ -519,46 +570,6 @@ class DRBD8(base.BlockDev): return None return result.stdout - @classmethod - def _GetDevInfo(cls, out): - """Parse details about a given DRBD minor. - - This return, if available, the local backing device (as a path) - and the local and remote (ip, port) information from a string - containing the output of the `drbdsetup show` command as returned - by _GetShowData. - - """ - data = {} - if not out: - return data - - bnf = cls._GetShowParser() - # run pyparse - - try: - results = bnf.parseString(out) - except pyp.ParseException, err: - base.ThrowError("Can't parse drbdsetup show output: %s", str(err)) - - # and massage the results into our desired format - for section in results: - sname = section[0] - if sname == "_this_host": - for lst in section[1:]: - if lst[0] == "disk": - data["local_dev"] = lst[1] - elif lst[0] == "meta-disk": - data["meta_dev"] = lst[1] - data["meta_index"] = lst[2] - elif lst[0] == "address": - data["local_addr"] = tuple(lst[1:]) - elif sname == "_remote_host": - for lst in section[1:]: - if lst[0] == "address": - data["remote_addr"] = tuple(lst[1:]) - return data - def _MatchesLocal(self, info): """Test if our local config matches with an existing device. @@ -775,7 +786,7 @@ class DRBD8(base.BlockDev): minor, result.fail_reason, result.output) def _CheckNetworkConfig(): - info = self._GetDevInfo(self._GetShowData(minor)) + info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor)) if not "local_addr" in info or not "remote_addr" in info: raise utils.RetryAgain() @@ -797,7 +808,7 @@ class DRBD8(base.BlockDev): self._aminor) if len(devices) != 2: base.ThrowError("drbd%d: need two devices for AddChildren", self.minor) - info = self._GetDevInfo(self._GetShowData(self.minor)) + info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(self.minor)) if "local_dev" in info: base.ThrowError("drbd%d: already attached to a local disk", self.minor) backend, meta = devices @@ -820,7 +831,7 @@ class DRBD8(base.BlockDev): base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren", self._aminor) # early return if we don't actually have backing storage - info = self._GetDevInfo(self._GetShowData(self.minor)) + info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(self.minor)) if "local_dev" not in info: return if len(self._children) != 2: @@ -1172,7 +1183,7 @@ class DRBD8(base.BlockDev): # pylint: disable=W0631 net_data = (self._lhost, self._lport, self._rhost, self._rport) for minor in (self._aminor,): - info = self._GetDevInfo(self._GetShowData(minor)) + info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor)) match_l = self._MatchesLocal(info) match_r = self._MatchesNet(info) @@ -1184,7 +1195,7 @@ class DRBD8(base.BlockDev): # disk matches, but not attached to network, attach and recheck self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL, hmac=constants.DRBD_HMAC_ALG, secret=self._secret) - if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): + if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))): break else: base.ThrowError("drbd%d: network attach successful, but 'drbdsetup" @@ -1194,7 +1205,7 @@ class DRBD8(base.BlockDev): # no local disk, but network attached and it matches self._AssembleLocal(minor, self._children[0].dev_path, self._children[1].dev_path, self.size) - if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): + if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))): break else: base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup" @@ -1221,7 +1232,7 @@ class DRBD8(base.BlockDev): # None) self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL, hmac=constants.DRBD_HMAC_ALG, secret=self._secret) - if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): + if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))): break else: base.ThrowError("drbd%d: network attach successful, but 'drbdsetup" diff --git a/test/py/ganeti.block.bdev_unittest.py b/test/py/ganeti.block.bdev_unittest.py index 58dcbac04ae3cccf8a7c411dd5900d3761776a02..bb6d8a82ff6e4dfca27dd197187509249bec4125 100755 --- a/test/py/ganeti.block.bdev_unittest.py +++ b/test/py/ganeti.block.bdev_unittest.py @@ -104,12 +104,12 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParserCreation(self): """Test drbdsetup show parser creation""" - drbd.DRBD8._GetShowParser() + drbd.DRBD8ShowInfo._GetShowParser() def testParser80(self): """Test drbdsetup show parser for disk and network version 8.0""" data = testutils.ReadTestData("bdev-drbd-8.0.txt") - result = drbd.DRBD8._GetDevInfo(data) + result = drbd.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(self._has_disk(result, "/dev/xenvg/test.data", "/dev/xenvg/test.meta"), "Wrong local disk info") @@ -120,7 +120,7 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParser83(self): """Test drbdsetup show parser for disk and network version 8.3""" data = testutils.ReadTestData("bdev-drbd-8.3.txt") - result = drbd.DRBD8._GetDevInfo(data) + result = drbd.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(self._has_disk(result, "/dev/xenvg/test.data", "/dev/xenvg/test.meta"), "Wrong local disk info") @@ -131,7 +131,7 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParserNetIP4(self): """Test drbdsetup show parser for IPv4 network""" data = testutils.ReadTestData("bdev-drbd-net-ip4.txt") - result = drbd.DRBD8._GetDevInfo(data) + result = drbd.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(("local_dev" not in result and "meta_dev" not in result and "meta_index" not in result), @@ -143,7 +143,7 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParserNetIP6(self): """Test drbdsetup show parser for IPv6 network""" data = testutils.ReadTestData("bdev-drbd-net-ip6.txt") - result = drbd.DRBD8._GetDevInfo(data) + result = drbd.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(("local_dev" not in result and "meta_dev" not in result and "meta_index" not in result), @@ -155,7 +155,7 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParserDisk(self): """Test drbdsetup show parser for disk""" data = testutils.ReadTestData("bdev-drbd-disk.txt") - result = drbd.DRBD8._GetDevInfo(data) + result = drbd.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(self._has_disk(result, "/dev/xenvg/test.data", "/dev/xenvg/test.meta"), "Wrong local disk info")