# # Parse metastat output. This is hairy and annoying, because Sun can't # be bothered to create machine-parseable output that contains all of # the information. # # We deliberately only parse information for mirrors and submirrors. # Anything else is an error. import sys, re # The various exceptions we throw when we hit data that we don't # understand. class DataProblem(Exception): pass class IncompleteData(DataProblem): pass class DupMirrorDevices(DataProblem): pass class NoMirrorDevices(DataProblem): pass class MultipleStripes(DataProblem): pass class NoRealDevice(DataProblem): pass class NoSubmirrorState(DataProblem): pass class MultipleMirror(DataProblem): pass class SeenSubmirror(DataProblem): pass class DuplicateMirror(DataProblem): pass class NewSubmirror(DataProblem): pass class NoMirror(DataProblem): pass # # Our data structures for mirrors and submirrors. (With a bit of code.) class Device: def __init__(self, dev): self.dev = dev def __str__(self): return self.dev class SubMirror(Device): def __init__(self, dev): Device.__init__(self, dev) self.realdev = [] self.state = None class Mirror(Device): def __init__(self, dev): Device.__init__(self, dev) self.submirrors = [] self.type = "mirror" self.state = None def all_okay(self): for smd in self.submirrors: if smd.state != "okay": return False return True def is_consistent(self): if len(self.submirrors) == 0: return False for smd in self.submirrors: if smd.state == None: return False return True def is_running(self): for smd in self.submirrors: if smd.state == "okay": return True return False def broken_subs(self): return [x for x in self.submirrors if x.state != "okay"] # We can offline half of the mirror if a) we are consistent # and b) we are OK and c) we have more than one mirror. def can_offline(self): return self.is_consistent() and self.all_okay() and \ len(self.submirrors) > 1 # Parse a mirror definition. submirrorre = re.compile("\s+Submirror (\d+): ((?:fs\d+/)?d\d+)$") def parse_mirror(md, lines, devs): while lines: line = lines.pop(0).rstrip() # Blank line finishes. if not line: break # Skip all unrecognized lines. mo = submirrorre.match(line) if not mo: continue mnum = int(mo.group(1)) mdev = mo.group(2) if mdev in devs: raise DupMirrorDevices, "parsing %s: submirror device %s has already been seen" % (md.dev, mdev) # We don't check that submirrors are numbered consecutively # and start from 0, because some at least don't start from # zero. smd = SubMirror(mdev) md.submirrors.append(smd) devs[mdev] = smd # Check overall device consistency. if not md.submirrors: raise NoMirrorDevices, "parsing %s: no submirrors recognized" statere = re.compile(r"\s+State: (.+)$") stripere = re.compile(r"\s+Stripe (\d+):.*$") devre = re.compile(r"\s+(c\d+t\d+d\d+s\d+)\s+\d+\s+\S+\s+\S+") statemap = {"Okay": "okay", "Needs maintenance": "needsmaint", } def parse_submirror(smd, md, lines): stripeCount = 0 inStripe = False while lines: line = lines.pop(0).rstrip() # Blank line ends it. if not line: break mo = statere.match(line) if mo: st = mo.group(1) if st in statemap: smd.state = statemap[st] else: smd.state = mo.group(1).lower() continue mo = stripere.match(line) if mo: # Crude approach to handling this: just treat them # as non-striped. #if stripeCount != 0: # raise MultipleStripes, "parsing submirror %s: has more than one stripe, which metaparse doesn't handle right now." % (smd.dev,) stripeCount += 1 inStripe = True devheader = lines.pop(0).strip() if not devheader.startswith("Device "): raise DataProblem, "parsing %s: unrecognized Stripe section header line: '%s'" % (smd.dev, line) elif inStripe: mo = devre.match(line) if not mo: raise DataProblem, "parsing %s: unrecognized device line '%s'" % (smd.dev, line) smd.realdev.append(mo.group(1)) # Check overall submirror consistency, to insure that we got all # of the data from it that we should have. if not smd.state: raise NoSubmirrorState, "parsing %s: no state parsed" % smd.dev if not smd.realdev: raise NoRealDevice, "parsing %s: no real device parsed" % smd.dev # Parse a block of text that is a complete and self-contained set of # metastat device definitions. Return a list of the top-level ones # that we recognize. startre = re.compile(r"^((?:fs\d+/)?d\d+): (Mirror|Submirror of (?:fs\d+/)?d\d+)$") def parse(txt): devs = {} res = [] lines = txt.split("\n") while lines: line = lines.pop(0) line = line.rstrip() # Skip blanks. if not line: continue # Skip unrecognized blocks. if line[0] in (' ', '\t'): continue # Try to match something that we understand. mo = startre.match(line) if not mo: continue dev = mo.group(1) what = mo.group(2) # Something goes here. if what == "Mirror": if dev in devs: raise MultipleMirror, "parsing %s: device already known. Either a repeated mirror, or this was seen as a submirror earlier." % dev md = Mirror(dev) devs[dev] = md res.append(md) parse_mirror(md, lines, devs) elif what.startswith("Submirror of "): topdev = what.split()[2] #if '/' in topdev: # topdev = topdev.split('/')[1] if topdev not in devs: raise NewSubmirror, "parsing %s: previously unknown submirror being specified for mirror %s" % (topdev, dev) # I believe that this is a genuine cannot happen # case, because I cannot see any way that this does # not error out earlier. Since I cannot test it, I # am making it an assert just in case. if dev not in devs or \ not isinstance(devs[dev], SubMirror): raise NoMirror, "parsing %s: parsing a submirror for an unknown mirror %s" % (topdev, dev) md = devs[topdev] smd = devs[dev] if smd.state != None: raise DuplicateMirror, "parsing %s for %s: this device has already been previously specified as a submirror for something else, or as a mirror" % (topdev, dev) parse_submirror(smd, md, lines) for r in res: if not r.is_consistent(): raise IncompleteData, "parsing %s: incomplete data (no submirror data found?)" % r.dev return res