Package ganeti :: Package storage :: Module drbd_info
[hide private]
[frames] | no frames]

Source Code for Module ganeti.storage.drbd_info

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30   
 31  """DRBD information parsing utilities""" 
 32   
 33  import errno 
 34  import pyparsing as pyp 
 35  import re 
 36   
 37  from ganeti import constants 
 38  from ganeti import utils 
 39  from ganeti import errors 
 40  from ganeti import compat 
 41  from ganeti.storage import base 
42 43 44 -class DRBD8Status(object): # pylint: disable=R0902
45 """A DRBD status representation class. 46 47 Note that this class is meant to be used to parse one of the entries returned 48 from L{DRBD8Info._JoinLinesPerMinor}. 49 50 """ 51 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$") 52 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)" 53 r"\s+ds:([^/]+)/(\S+)\s+.*$") 54 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*" 55 # Due to a bug in drbd in the kernel, introduced in 56 # commit 4b0715f096 (still unfixed as of 2011-08-22) 57 r"(?:\s|M)" 58 r"finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") 59 60 CS_UNCONFIGURED = "Unconfigured" 61 CS_STANDALONE = "StandAlone" 62 CS_WFCONNECTION = "WFConnection" 63 CS_WFREPORTPARAMS = "WFReportParams" 64 CS_CONNECTED = "Connected" 65 CS_STARTINGSYNCS = "StartingSyncS" 66 CS_STARTINGSYNCT = "StartingSyncT" 67 CS_WFBITMAPS = "WFBitMapS" 68 CS_WFBITMAPT = "WFBitMapT" 69 CS_WFSYNCUUID = "WFSyncUUID" 70 CS_SYNCSOURCE = "SyncSource" 71 CS_SYNCTARGET = "SyncTarget" 72 CS_PAUSEDSYNCS = "PausedSyncS" 73 CS_PAUSEDSYNCT = "PausedSyncT" 74 CSET_SYNC = compat.UniqueFrozenset([ 75 CS_WFREPORTPARAMS, 76 CS_STARTINGSYNCS, 77 CS_STARTINGSYNCT, 78 CS_WFBITMAPS, 79 CS_WFBITMAPT, 80 CS_WFSYNCUUID, 81 CS_SYNCSOURCE, 82 CS_SYNCTARGET, 83 CS_PAUSEDSYNCS, 84 CS_PAUSEDSYNCT, 85 ]) 86 87 DS_DISKLESS = "Diskless" 88 DS_ATTACHING = "Attaching" # transient state 89 DS_FAILED = "Failed" # transient state, next: diskless 90 DS_NEGOTIATING = "Negotiating" # transient state 91 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation 92 DS_OUTDATED = "Outdated" 93 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected 94 DS_CONSISTENT = "Consistent" 95 DS_UPTODATE = "UpToDate" # normal state 96 97 RO_PRIMARY = "Primary" 98 RO_SECONDARY = "Secondary" 99 RO_UNKNOWN = "Unknown" 100
101 - def __init__(self, procline):
102 u = self.UNCONF_RE.match(procline) 103 if u: 104 self.cstatus = self.CS_UNCONFIGURED 105 self.lrole = self.rrole = self.ldisk = self.rdisk = None 106 else: 107 m = self.LINE_RE.match(procline) 108 if not m: 109 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline) 110 self.cstatus = m.group(1) 111 self.lrole = m.group(2) 112 self.rrole = m.group(3) 113 self.ldisk = m.group(4) 114 self.rdisk = m.group(5) 115 116 # end reading of data from the LINE_RE or UNCONF_RE 117 118 self.is_standalone = self.cstatus == self.CS_STANDALONE 119 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION 120 self.is_connected = self.cstatus == self.CS_CONNECTED 121 self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED 122 self.is_primary = self.lrole == self.RO_PRIMARY 123 self.is_secondary = self.lrole == self.RO_SECONDARY 124 self.peer_primary = self.rrole == self.RO_PRIMARY 125 self.peer_secondary = self.rrole == self.RO_SECONDARY 126 self.both_primary = self.is_primary and self.peer_primary 127 self.both_secondary = self.is_secondary and self.peer_secondary 128 129 self.is_diskless = self.ldisk == self.DS_DISKLESS 130 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE 131 self.peer_disk_uptodate = self.rdisk == self.DS_UPTODATE 132 133 self.is_in_resync = self.cstatus in self.CSET_SYNC 134 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED 135 136 m = self.SYNC_RE.match(procline) 137 if m: 138 self.sync_percent = float(m.group(1)) 139 hours = int(m.group(2)) 140 minutes = int(m.group(3)) 141 seconds = int(m.group(4)) 142 self.est_time = hours * 3600 + minutes * 60 + seconds 143 else: 144 # we have (in this if branch) no percent information, but if 145 # we're resyncing we need to 'fake' a sync percent information, 146 # as this is how cmdlib determines if it makes sense to wait for 147 # resyncing or not 148 if self.is_in_resync: 149 self.sync_percent = 0 150 else: 151 self.sync_percent = None 152 self.est_time = None
153
154 - def __repr__(self):
155 return ("<%s: cstatus=%s, lrole=%s, rrole=%s, ldisk=%s, rdisk=%s>" % 156 (self.__class__, self.cstatus, self.lrole, self.rrole, 157 self.ldisk, self.rdisk))
158
159 160 -class DRBD8Info(object):
161 """Represents information DRBD exports (usually via /proc/drbd). 162 163 An instance of this class is created by one of the CreateFrom... methods. 164 165 """ 166 167 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.(\d+))?" 168 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)") 169 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$") 170
171 - def __init__(self, lines):
172 self._version = self._ParseVersion(lines) 173 self._minors, self._line_per_minor = self._JoinLinesPerMinor(lines)
174
175 - def GetVersion(self):
176 """Return the DRBD version. 177 178 This will return a dict with keys: 179 - k_major 180 - k_minor 181 - k_point 182 - k_fix (only on some drbd versions) 183 - api 184 - proto 185 - proto2 (only on drbd > 8.2.X) 186 187 """ 188 return self._version
189
190 - def GetVersionString(self):
191 """Return the DRBD version as a single string. 192 193 """ 194 version = self.GetVersion() 195 retval = "%d.%d.%d" % \ 196 (version["k_major"], version["k_minor"], version["k_point"]) 197 if "k_fix" in version: 198 retval += ".%s" % version["k_fix"] 199 200 retval += " (api:%d/proto:%d" % (version["api"], version["proto"]) 201 if "proto2" in version: 202 retval += "-%s" % version["proto2"] 203 retval += ")" 204 return retval
205
206 - def GetMinors(self):
207 """Return a list of minor for which information is available. 208 209 This list is ordered in exactly the order which was found in the underlying 210 data. 211 212 """ 213 return self._minors
214
215 - def HasMinorStatus(self, minor):
216 return minor in self._line_per_minor
217
218 - def GetMinorStatus(self, minor):
219 return DRBD8Status(self._line_per_minor[minor])
220
221 - def _ParseVersion(self, lines):
222 first_line = lines[0].strip() 223 version = self._VERSION_RE.match(first_line) 224 if not version: 225 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" % 226 first_line) 227 228 values = version.groups() 229 retval = { 230 "k_major": int(values[0]), 231 "k_minor": int(values[1]), 232 "k_point": int(values[2]), 233 "api": int(values[4]), 234 "proto": int(values[5]), 235 } 236 if values[3] is not None: 237 retval["k_fix"] = values[3] 238 if values[6] is not None: 239 retval["proto2"] = values[6] 240 241 return retval
242
243 - def _JoinLinesPerMinor(self, lines):
244 """Transform the raw lines into a dictionary based on the minor. 245 246 @return: a dictionary of minor: joined lines from /proc/drbd 247 for that minor 248 249 """ 250 minors = [] 251 results = {} 252 old_minor = old_line = None 253 for line in lines: 254 if not line: # completely empty lines, as can be returned by drbd8.0+ 255 continue 256 lresult = self._VALID_LINE_RE.match(line) 257 if lresult is not None: 258 if old_minor is not None: 259 minors.append(old_minor) 260 results[old_minor] = old_line 261 old_minor = int(lresult.group(1)) 262 old_line = line 263 else: 264 if old_minor is not None: 265 old_line += " " + line.strip() 266 # add last line 267 if old_minor is not None: 268 minors.append(old_minor) 269 results[old_minor] = old_line 270 return minors, results
271 272 @staticmethod
273 - def CreateFromLines(lines):
274 return DRBD8Info(lines)
275 276 @staticmethod
278 try: 279 lines = utils.ReadFile(filename).splitlines() 280 except EnvironmentError, err: 281 if err.errno == errno.ENOENT: 282 base.ThrowError("The file %s cannot be opened, check if the module" 283 " is loaded (%s)", filename, str(err)) 284 else: 285 base.ThrowError("Can't read the DRBD proc file %s: %s", 286 filename, str(err)) 287 if not lines: 288 base.ThrowError("Can't read any data from %s", filename) 289 return DRBD8Info.CreateFromLines(lines)
290
291 292 -class BaseShowInfo(object):
293 """Base class for parsing the `drbdsetup show` output. 294 295 Holds various common pyparsing expressions which are used by subclasses. Also 296 provides caching of the constructed parser. 297 298 """ 299 _PARSE_SHOW = None 300 301 # pyparsing setup 302 _lbrace = pyp.Literal("{").suppress() 303 _rbrace = pyp.Literal("}").suppress() 304 _lbracket = pyp.Literal("[").suppress() 305 _rbracket = pyp.Literal("]").suppress() 306 _semi = pyp.Literal(";").suppress() 307 _colon = pyp.Literal(":").suppress() 308 # this also converts the value to an int 309 _number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0])) 310 311 _comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine) 312 _defa = pyp.Literal("_is_default").suppress() 313 _dbl_quote = pyp.Literal('"').suppress() 314 315 _keyword = pyp.Word(pyp.alphanums + "-") 316 317 # value types 318 _value = pyp.Word(pyp.alphanums + "_-/.:") 319 _quoted = _dbl_quote + pyp.CharsNotIn('"') + _dbl_quote 320 _ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() + 321 pyp.Word(pyp.nums + ".") + _colon + _number) 322 _ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() + 323 pyp.Optional(_lbracket) + pyp.Word(pyp.hexnums + ":") + 324 pyp.Optional(_rbracket) + _colon + _number) 325 # meta device, extended syntax 326 _meta_value = ((_value ^ _quoted) + _lbracket + _number + _rbracket) 327 # device name, extended syntax 328 _device_value = pyp.Literal("minor").suppress() + _number 329 330 # a statement 331 _stmt = (~_rbrace + _keyword + ~_lbrace + 332 pyp.Optional(_ipv4_addr ^ _ipv6_addr ^ _value ^ _quoted ^ 333 _meta_value ^ _device_value) + 334 pyp.Optional(_defa) + _semi + 335 pyp.Optional(pyp.restOfLine).suppress()) 336 337 @classmethod
338 - def GetDevInfo(cls, show_data):
339 """Parse details about a given DRBD minor. 340 341 This returns, if available, the local backing device (as a path) 342 and the local and remote (ip, port) information from a string 343 containing the output of the `drbdsetup show` command as returned 344 by DRBD8Dev._GetShowData. 345 346 This will return a dict with keys: 347 - local_dev 348 - meta_dev 349 - meta_index 350 - local_addr 351 - remote_addr 352 353 """ 354 if not show_data: 355 return {} 356 357 try: 358 # run pyparse 359 results = (cls._GetShowParser()).parseString(show_data) 360 except pyp.ParseException, err: 361 base.ThrowError("Can't parse drbdsetup show output: %s", str(err)) 362 363 return cls._TransformParseResult(results)
364 365 @classmethod
366 - def _TransformParseResult(cls, parse_result):
367 raise NotImplementedError
368 369 @classmethod
370 - def _GetShowParser(cls):
371 """Return a parser for `drbd show` output. 372 373 This will either create or return an already-created parser for the 374 output of the command `drbd show`. 375 376 """ 377 if cls._PARSE_SHOW is None: 378 cls._PARSE_SHOW = cls._ConstructShowParser() 379 380 return cls._PARSE_SHOW
381 382 @classmethod
383 - def _ConstructShowParser(cls):
384 raise NotImplementedError
385
386 387 -class DRBD83ShowInfo(BaseShowInfo):
388 @classmethod
389 - def _ConstructShowParser(cls):
390 # an entire section 391 section_name = pyp.Word(pyp.alphas + "_") 392 section = section_name + \ 393 cls._lbrace + \ 394 pyp.ZeroOrMore(pyp.Group(cls._stmt)) + \ 395 cls._rbrace 396 397 bnf = pyp.ZeroOrMore(pyp.Group(section ^ cls._stmt)) 398 bnf.ignore(cls._comment) 399 400 return bnf
401 402 @classmethod
403 - def _TransformParseResult(cls, parse_result):
404 retval = {} 405 for section in parse_result: 406 sname = section[0] 407 if sname == "_this_host": 408 for lst in section[1:]: 409 if lst[0] == "disk": 410 retval["local_dev"] = lst[1] 411 elif lst[0] == "meta-disk": 412 retval["meta_dev"] = lst[1] 413 retval["meta_index"] = lst[2] 414 elif lst[0] == "address": 415 retval["local_addr"] = tuple(lst[1:]) 416 elif sname == "_remote_host": 417 for lst in section[1:]: 418 if lst[0] == "address": 419 retval["remote_addr"] = tuple(lst[1:]) 420 return retval
421
422 423 -class DRBD84ShowInfo(BaseShowInfo):
424 @classmethod
425 - def _ConstructShowParser(cls):
426 # an entire section (sections can be nested in DRBD 8.4, and there exist 427 # sections like "volume 0") 428 section_name = pyp.Word(pyp.alphas + "_") + \ 429 pyp.Optional(pyp.Word(pyp.nums)).suppress() # skip volume idx 430 section = pyp.Forward() 431 # pylint: disable=W0106 432 section << (section_name + 433 cls._lbrace + 434 pyp.ZeroOrMore(pyp.Group(cls._stmt ^ section)) + 435 cls._rbrace) 436 437 resource_name = pyp.Word(pyp.alphanums + "_-.") 438 resource = (pyp.Literal("resource") + resource_name).suppress() + \ 439 cls._lbrace + \ 440 pyp.ZeroOrMore(pyp.Group(section)) + \ 441 cls._rbrace 442 443 resource.ignore(cls._comment) 444 445 return resource
446 447 @classmethod
448 - def _TransformVolumeSection(cls, vol_content, retval):
449 for entry in vol_content: 450 if entry[0] == "disk" and len(entry) == 2 and \ 451 isinstance(entry[1], basestring): 452 retval["local_dev"] = entry[1] 453 elif entry[0] == "meta-disk": 454 if len(entry) > 1: 455 retval["meta_dev"] = entry[1] 456 if len(entry) > 2: 457 retval["meta_index"] = entry[2]
458 459 @classmethod
460 - def _TransformParseResult(cls, parse_result):
461 retval = {} 462 for section in parse_result: 463 sname = section[0] 464 if sname == "_this_host": 465 for lst in section[1:]: 466 if lst[0] == "address": 467 retval["local_addr"] = tuple(lst[1:]) 468 elif lst[0] == "volume": 469 cls._TransformVolumeSection(lst[1:], retval) 470 elif sname == "_remote_host": 471 for lst in section[1:]: 472 if lst[0] == "address": 473 retval["remote_addr"] = tuple(lst[1:]) 474 return retval
475