diff --git a/Makefile.am b/Makefile.am index 61f2adacc039c801250d70ac32466c2ca37247e2..97dd9df35c4df42f0a2bdcc558cb26dd23415dd5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -319,7 +319,8 @@ block_PYTHON = \ lib/block/__init__.py \ lib/block/bdev.py \ lib/block/base.py \ - lib/block/drbd.py + lib/block/drbd.py \ + lib/block/drbd_info.py rapi_PYTHON = \ lib/rapi/__init__.py \ diff --git a/lib/block/drbd.py b/lib/block/drbd.py index 76f58b4dc4308ec0da092c5591b9c58782217be2..34421994c5d32c66ea35780ab23a6095d91c4bc7 100644 --- a/lib/block/drbd.py +++ b/lib/block/drbd.py @@ -23,18 +23,17 @@ import errno import logging -import pyparsing as pyp -import re import shlex import time from ganeti import constants from ganeti import utils from ganeti import errors -from ganeti import compat from ganeti import netutils from ganeti import objects from ganeti.block import base +from ganeti.block.drbd_info import DRBD8Info +from ganeti.block.drbd_info import DRBD8ShowInfo # Size of reads in _CanReadDevice @@ -42,339 +41,6 @@ from ganeti.block import base _DEVICE_READ_SIZE = 128 * 1024 -class DRBD8Status(object): # pylint: disable=R0902 - """A DRBD status representation class. - - Note that this class is meant to be used to parse one of the entries returned - from L{DRBD8Info._JoinLinesPerMinor}. - - """ - UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$") - LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)" - "\s+ds:([^/]+)/(\S+)\s+.*$") - SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*" - # Due to a bug in drbd in the kernel, introduced in - # commit 4b0715f096 (still unfixed as of 2011-08-22) - "(?:\s|M)" - "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") - - CS_UNCONFIGURED = "Unconfigured" - CS_STANDALONE = "StandAlone" - CS_WFCONNECTION = "WFConnection" - CS_WFREPORTPARAMS = "WFReportParams" - CS_CONNECTED = "Connected" - CS_STARTINGSYNCS = "StartingSyncS" - CS_STARTINGSYNCT = "StartingSyncT" - CS_WFBITMAPS = "WFBitMapS" - CS_WFBITMAPT = "WFBitMapT" - CS_WFSYNCUUID = "WFSyncUUID" - CS_SYNCSOURCE = "SyncSource" - CS_SYNCTARGET = "SyncTarget" - CS_PAUSEDSYNCS = "PausedSyncS" - CS_PAUSEDSYNCT = "PausedSyncT" - CSET_SYNC = compat.UniqueFrozenset([ - CS_WFREPORTPARAMS, - CS_STARTINGSYNCS, - CS_STARTINGSYNCT, - CS_WFBITMAPS, - CS_WFBITMAPT, - CS_WFSYNCUUID, - CS_SYNCSOURCE, - CS_SYNCTARGET, - CS_PAUSEDSYNCS, - CS_PAUSEDSYNCT, - ]) - - DS_DISKLESS = "Diskless" - DS_ATTACHING = "Attaching" # transient state - DS_FAILED = "Failed" # transient state, next: diskless - DS_NEGOTIATING = "Negotiating" # transient state - DS_INCONSISTENT = "Inconsistent" # while syncing or after creation - DS_OUTDATED = "Outdated" - DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected - DS_CONSISTENT = "Consistent" - DS_UPTODATE = "UpToDate" # normal state - - RO_PRIMARY = "Primary" - RO_SECONDARY = "Secondary" - RO_UNKNOWN = "Unknown" - - def __init__(self, procline): - u = self.UNCONF_RE.match(procline) - if u: - self.cstatus = self.CS_UNCONFIGURED - self.lrole = self.rrole = self.ldisk = self.rdisk = None - else: - m = self.LINE_RE.match(procline) - if not m: - raise errors.BlockDeviceError("Can't parse input data '%s'" % procline) - self.cstatus = m.group(1) - self.lrole = m.group(2) - self.rrole = m.group(3) - self.ldisk = m.group(4) - self.rdisk = m.group(5) - - # end reading of data from the LINE_RE or UNCONF_RE - - self.is_standalone = self.cstatus == self.CS_STANDALONE - self.is_wfconn = self.cstatus == self.CS_WFCONNECTION - self.is_connected = self.cstatus == self.CS_CONNECTED - self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED - self.is_primary = self.lrole == self.RO_PRIMARY - self.is_secondary = self.lrole == self.RO_SECONDARY - self.peer_primary = self.rrole == self.RO_PRIMARY - self.peer_secondary = self.rrole == self.RO_SECONDARY - self.both_primary = self.is_primary and self.peer_primary - self.both_secondary = self.is_secondary and self.peer_secondary - - self.is_diskless = self.ldisk == self.DS_DISKLESS - self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE - - self.is_in_resync = self.cstatus in self.CSET_SYNC - self.is_in_use = self.cstatus != self.CS_UNCONFIGURED - - m = self.SYNC_RE.match(procline) - if m: - self.sync_percent = float(m.group(1)) - hours = int(m.group(2)) - minutes = int(m.group(3)) - seconds = int(m.group(4)) - self.est_time = hours * 3600 + minutes * 60 + seconds - else: - # we have (in this if branch) no percent information, but if - # we're resyncing we need to 'fake' a sync percent information, - # as this is how cmdlib determines if it makes sense to wait for - # resyncing or not - if self.is_in_resync: - self.sync_percent = 0 - else: - self.sync_percent = None - self.est_time = None - - -class DRBD8Info(object): - """Represents information DRBD exports (usually via /proc/drbd). - - An instance of this class is created by one of the CreateFrom... methods. - - """ - - _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?" - r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)") - _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$") - - def __init__(self, lines): - self._version = self._ParseVersion(lines) - self._minors, self._line_per_minor = self._JoinLinesPerMinor(lines) - - def GetVersion(self): - """Return the DRBD version. - - This will return a dict with keys: - - k_major - - k_minor - - k_point - - api - - proto - - proto2 (only on drbd > 8.2.X) - - """ - return self._version - - def GetMinors(self): - """Return a list of minor for which information is available. - - This list is ordered in exactly the order which was found in the underlying - data. - - """ - return self._minors - - def HasMinorStatus(self, minor): - return minor in self._line_per_minor - - def GetMinorStatus(self, minor): - return DRBD8Status(self._line_per_minor[minor]) - - def _ParseVersion(self, lines): - first_line = lines[0].strip() - version = self._VERSION_RE.match(first_line) - if not version: - raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" % - first_line) - - values = version.groups() - retval = { - "k_major": int(values[0]), - "k_minor": int(values[1]), - "k_point": int(values[2]), - "api": int(values[3]), - "proto": int(values[4]), - } - if values[5] is not None: - retval["proto2"] = values[5] - - return retval - - def _JoinLinesPerMinor(self, lines): - """Transform the raw lines into a dictionary based on the minor. - - @return: a dictionary of minor: joined lines from /proc/drbd - for that minor - - """ - minors = [] - results = {} - old_minor = old_line = None - for line in lines: - if not line: # completely empty lines, as can be returned by drbd8.0+ - continue - lresult = self._VALID_LINE_RE.match(line) - if lresult is not None: - if old_minor is not None: - minors.append(old_minor) - results[old_minor] = old_line - old_minor = int(lresult.group(1)) - old_line = line - else: - if old_minor is not None: - old_line += " " + line.strip() - # add last line - if old_minor is not None: - minors.append(old_minor) - results[old_minor] = old_line - return minors, results - - @staticmethod - def CreateFromLines(lines): - return DRBD8Info(lines) - - @staticmethod - def CreateFromFile(filename=constants.DRBD_STATUS_FILE): - try: - lines = utils.ReadFile(filename).splitlines() - except EnvironmentError, err: - if err.errno == errno.ENOENT: - base.ThrowError("The file %s cannot be opened, check if the module" - " is loaded (%s)", filename, str(err)) - else: - base.ThrowError("Can't read the DRBD proc file %s: %s", - filename, str(err)) - if not lines: - base.ThrowError("Can't read any data from %s", filename) - 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. diff --git a/lib/block/drbd_info.py b/lib/block/drbd_info.py new file mode 100644 index 0000000000000000000000000000000000000000..4d1e14d66418e11e88d5e491d3592881caa168c5 --- /dev/null +++ b/lib/block/drbd_info.py @@ -0,0 +1,365 @@ +# +# + +# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +"""DRBD information parsing utilities""" + +import errno +import pyparsing as pyp +import re + +from ganeti import constants +from ganeti import utils +from ganeti import errors +from ganeti import compat +from ganeti.block import base + + +class DRBD8Status(object): # pylint: disable=R0902 + """A DRBD status representation class. + + Note that this class is meant to be used to parse one of the entries returned + from L{DRBD8Info._JoinLinesPerMinor}. + + """ + UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$") + LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)" + "\s+ds:([^/]+)/(\S+)\s+.*$") + SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*" + # Due to a bug in drbd in the kernel, introduced in + # commit 4b0715f096 (still unfixed as of 2011-08-22) + "(?:\s|M)" + "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") + + CS_UNCONFIGURED = "Unconfigured" + CS_STANDALONE = "StandAlone" + CS_WFCONNECTION = "WFConnection" + CS_WFREPORTPARAMS = "WFReportParams" + CS_CONNECTED = "Connected" + CS_STARTINGSYNCS = "StartingSyncS" + CS_STARTINGSYNCT = "StartingSyncT" + CS_WFBITMAPS = "WFBitMapS" + CS_WFBITMAPT = "WFBitMapT" + CS_WFSYNCUUID = "WFSyncUUID" + CS_SYNCSOURCE = "SyncSource" + CS_SYNCTARGET = "SyncTarget" + CS_PAUSEDSYNCS = "PausedSyncS" + CS_PAUSEDSYNCT = "PausedSyncT" + CSET_SYNC = compat.UniqueFrozenset([ + CS_WFREPORTPARAMS, + CS_STARTINGSYNCS, + CS_STARTINGSYNCT, + CS_WFBITMAPS, + CS_WFBITMAPT, + CS_WFSYNCUUID, + CS_SYNCSOURCE, + CS_SYNCTARGET, + CS_PAUSEDSYNCS, + CS_PAUSEDSYNCT, + ]) + + DS_DISKLESS = "Diskless" + DS_ATTACHING = "Attaching" # transient state + DS_FAILED = "Failed" # transient state, next: diskless + DS_NEGOTIATING = "Negotiating" # transient state + DS_INCONSISTENT = "Inconsistent" # while syncing or after creation + DS_OUTDATED = "Outdated" + DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected + DS_CONSISTENT = "Consistent" + DS_UPTODATE = "UpToDate" # normal state + + RO_PRIMARY = "Primary" + RO_SECONDARY = "Secondary" + RO_UNKNOWN = "Unknown" + + def __init__(self, procline): + u = self.UNCONF_RE.match(procline) + if u: + self.cstatus = self.CS_UNCONFIGURED + self.lrole = self.rrole = self.ldisk = self.rdisk = None + else: + m = self.LINE_RE.match(procline) + if not m: + raise errors.BlockDeviceError("Can't parse input data '%s'" % procline) + self.cstatus = m.group(1) + self.lrole = m.group(2) + self.rrole = m.group(3) + self.ldisk = m.group(4) + self.rdisk = m.group(5) + + # end reading of data from the LINE_RE or UNCONF_RE + + self.is_standalone = self.cstatus == self.CS_STANDALONE + self.is_wfconn = self.cstatus == self.CS_WFCONNECTION + self.is_connected = self.cstatus == self.CS_CONNECTED + self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED + self.is_primary = self.lrole == self.RO_PRIMARY + self.is_secondary = self.lrole == self.RO_SECONDARY + self.peer_primary = self.rrole == self.RO_PRIMARY + self.peer_secondary = self.rrole == self.RO_SECONDARY + self.both_primary = self.is_primary and self.peer_primary + self.both_secondary = self.is_secondary and self.peer_secondary + + self.is_diskless = self.ldisk == self.DS_DISKLESS + self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE + + self.is_in_resync = self.cstatus in self.CSET_SYNC + self.is_in_use = self.cstatus != self.CS_UNCONFIGURED + + m = self.SYNC_RE.match(procline) + if m: + self.sync_percent = float(m.group(1)) + hours = int(m.group(2)) + minutes = int(m.group(3)) + seconds = int(m.group(4)) + self.est_time = hours * 3600 + minutes * 60 + seconds + else: + # we have (in this if branch) no percent information, but if + # we're resyncing we need to 'fake' a sync percent information, + # as this is how cmdlib determines if it makes sense to wait for + # resyncing or not + if self.is_in_resync: + self.sync_percent = 0 + else: + self.sync_percent = None + self.est_time = None + + +class DRBD8Info(object): + """Represents information DRBD exports (usually via /proc/drbd). + + An instance of this class is created by one of the CreateFrom... methods. + + """ + + _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?" + r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)") + _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$") + + def __init__(self, lines): + self._version = self._ParseVersion(lines) + self._minors, self._line_per_minor = self._JoinLinesPerMinor(lines) + + def GetVersion(self): + """Return the DRBD version. + + This will return a dict with keys: + - k_major + - k_minor + - k_point + - api + - proto + - proto2 (only on drbd > 8.2.X) + + """ + return self._version + + def GetMinors(self): + """Return a list of minor for which information is available. + + This list is ordered in exactly the order which was found in the underlying + data. + + """ + return self._minors + + def HasMinorStatus(self, minor): + return minor in self._line_per_minor + + def GetMinorStatus(self, minor): + return DRBD8Status(self._line_per_minor[minor]) + + def _ParseVersion(self, lines): + first_line = lines[0].strip() + version = self._VERSION_RE.match(first_line) + if not version: + raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" % + first_line) + + values = version.groups() + retval = { + "k_major": int(values[0]), + "k_minor": int(values[1]), + "k_point": int(values[2]), + "api": int(values[3]), + "proto": int(values[4]), + } + if values[5] is not None: + retval["proto2"] = values[5] + + return retval + + def _JoinLinesPerMinor(self, lines): + """Transform the raw lines into a dictionary based on the minor. + + @return: a dictionary of minor: joined lines from /proc/drbd + for that minor + + """ + minors = [] + results = {} + old_minor = old_line = None + for line in lines: + if not line: # completely empty lines, as can be returned by drbd8.0+ + continue + lresult = self._VALID_LINE_RE.match(line) + if lresult is not None: + if old_minor is not None: + minors.append(old_minor) + results[old_minor] = old_line + old_minor = int(lresult.group(1)) + old_line = line + else: + if old_minor is not None: + old_line += " " + line.strip() + # add last line + if old_minor is not None: + minors.append(old_minor) + results[old_minor] = old_line + return minors, results + + @staticmethod + def CreateFromLines(lines): + return DRBD8Info(lines) + + @staticmethod + def CreateFromFile(filename=constants.DRBD_STATUS_FILE): + try: + lines = utils.ReadFile(filename).splitlines() + except EnvironmentError, err: + if err.errno == errno.ENOENT: + base.ThrowError("The file %s cannot be opened, check if the module" + " is loaded (%s)", filename, str(err)) + else: + base.ThrowError("Can't read the DRBD proc file %s: %s", + filename, str(err)) + if not lines: + base.ThrowError("Can't read any data from %s", filename) + 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 diff --git a/test/py/ganeti.block.bdev_unittest.py b/test/py/ganeti.block.bdev_unittest.py index bb6d8a82ff6e4dfca27dd197187509249bec4125..8955a9f014020162f2e47b58853ad3dfd4ed5276 100755 --- a/test/py/ganeti.block.bdev_unittest.py +++ b/test/py/ganeti.block.bdev_unittest.py @@ -33,6 +33,7 @@ from ganeti import objects from ganeti import utils from ganeti.block import bdev from ganeti.block import drbd +from ganeti.block import drbd_info import testutils @@ -104,12 +105,12 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParserCreation(self): """Test drbdsetup show parser creation""" - drbd.DRBD8ShowInfo._GetShowParser() + drbd_info.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.DRBD8ShowInfo.GetDevInfo(data) + result = drbd_info.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(self._has_disk(result, "/dev/xenvg/test.data", "/dev/xenvg/test.meta"), "Wrong local disk info") @@ -120,7 +121,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.DRBD8ShowInfo.GetDevInfo(data) + result = drbd_info.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(self._has_disk(result, "/dev/xenvg/test.data", "/dev/xenvg/test.meta"), "Wrong local disk info") @@ -131,7 +132,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.DRBD8ShowInfo.GetDevInfo(data) + result = drbd_info.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(("local_dev" not in result and "meta_dev" not in result and "meta_index" not in result), @@ -143,7 +144,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.DRBD8ShowInfo.GetDevInfo(data) + result = drbd_info.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(("local_dev" not in result and "meta_dev" not in result and "meta_index" not in result), @@ -155,7 +156,7 @@ class TestDRBD8Runner(testutils.GanetiTestCase): def testParserDisk(self): """Test drbdsetup show parser for disk""" data = testutils.ReadTestData("bdev-drbd-disk.txt") - result = drbd.DRBD8ShowInfo.GetDevInfo(data) + result = drbd_info.DRBD8ShowInfo.GetDevInfo(data) self.failUnless(self._has_disk(result, "/dev/xenvg/test.data", "/dev/xenvg/test.meta"), "Wrong local disk info") @@ -289,8 +290,8 @@ class TestDRBD8Status(testutils.GanetiTestCase): self.failUnless(not self.drbd_info80e.HasMinorStatus(3)) def testLineNotMatch(self): - """Test wrong line passed to drbd.DRBD8Status""" - self.assertRaises(errors.BlockDeviceError, drbd.DRBD8Status, "foo") + """Test wrong line passed to drbd_info.DRBD8Status""" + self.assertRaises(errors.BlockDeviceError, drbd_info.DRBD8Status, "foo") def testMinor0(self): """Test connected, primary device"""