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+)" 168 r"(?:\.(\d+))?(?:-(\d+))?" 169 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)") 170 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$") 171
172 - def __init__(self, lines):
173 self._version = self._ParseVersion(lines) 174 self._minors, self._line_per_minor = self._JoinLinesPerMinor(lines)
175
176 - def GetVersion(self):
177 """Return the DRBD version. 178 179 This will return a dict with keys: 180 - k_major 181 - k_minor 182 - k_point 183 - k_fix (only on some drbd versions) 184 - k_release 185 - api 186 - proto 187 - proto2 (only on drbd > 8.2.X) 188 189 """ 190 return self._version
191
192 - def GetVersionString(self):
193 """Return the DRBD version as a single string. 194 195 """ 196 version = self.GetVersion() 197 retval = "%d.%d.%d" % \ 198 (version["k_major"], version["k_minor"], version["k_point"]) 199 if "k_fix" in version: 200 retval += ".%s" % version["k_fix"] 201 if "k_release" in version: 202 retval += "-%s" % version["k_release"] 203 204 retval += " (api:%d/proto:%d" % (version["api"], version["proto"]) 205 if "proto2" in version: 206 retval += "-%s" % version["proto2"] 207 retval += ")" 208 return retval
209
210 - def GetMinors(self):
211 """Return a list of minor for which information is available. 212 213 This list is ordered in exactly the order which was found in the underlying 214 data. 215 216 """ 217 return self._minors
218
219 - def HasMinorStatus(self, minor):
220 return minor in self._line_per_minor
221
222 - def GetMinorStatus(self, minor):
223 return DRBD8Status(self._line_per_minor[minor])
224
225 - def _ParseVersion(self, lines):
226 first_line = lines[0].strip() 227 version = self._VERSION_RE.match(first_line) 228 if not version: 229 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" % 230 first_line) 231 232 values = version.groups() 233 retval = { 234 "k_major": int(values[0]), 235 "k_minor": int(values[1]), 236 "k_point": int(values[2]), 237 "api": int(values[5]), 238 "proto": int(values[6]), 239 } 240 if values[3] is not None: 241 retval["k_fix"] = values[3] 242 if values[4] is not None: 243 retval["k_release"] = values[4] 244 if values[7] is not None: 245 retval["proto2"] = values[7] 246 247 return retval
248
249 - def _JoinLinesPerMinor(self, lines):
250 """Transform the raw lines into a dictionary based on the minor. 251 252 @return: a dictionary of minor: joined lines from /proc/drbd 253 for that minor 254 255 """ 256 minors = [] 257 results = {} 258 old_minor = old_line = None 259 for line in lines: 260 if not line: # completely empty lines, as can be returned by drbd8.0+ 261 continue 262 lresult = self._VALID_LINE_RE.match(line) 263 if lresult is not None: 264 if old_minor is not None: 265 minors.append(old_minor) 266 results[old_minor] = old_line 267 old_minor = int(lresult.group(1)) 268 old_line = line 269 else: 270 if old_minor is not None: 271 old_line += " " + line.strip() 272 # add last line 273 if old_minor is not None: 274 minors.append(old_minor) 275 results[old_minor] = old_line 276 return minors, results
277 278 @staticmethod
279 - def CreateFromLines(lines):
280 return DRBD8Info(lines)
281 282 @staticmethod
284 try: 285 lines = utils.ReadFile(filename).splitlines() 286 except EnvironmentError, err: 287 if err.errno == errno.ENOENT: 288 base.ThrowError("The file %s cannot be opened, check if the module" 289 " is loaded (%s)", filename, str(err)) 290 else: 291 base.ThrowError("Can't read the DRBD proc file %s: %s", 292 filename, str(err)) 293 if not lines: 294 base.ThrowError("Can't read any data from %s", filename) 295 return DRBD8Info.CreateFromLines(lines)
296
297 298 -class BaseShowInfo(object):
299 """Base class for parsing the `drbdsetup show` output. 300 301 Holds various common pyparsing expressions which are used by subclasses. Also 302 provides caching of the constructed parser. 303 304 """ 305 _PARSE_SHOW = None 306 307 # pyparsing setup 308 _lbrace = pyp.Literal("{").suppress() 309 _rbrace = pyp.Literal("}").suppress() 310 _lbracket = pyp.Literal("[").suppress() 311 _rbracket = pyp.Literal("]").suppress() 312 _semi = pyp.Literal(";").suppress() 313 _colon = pyp.Literal(":").suppress() 314 # this also converts the value to an int 315 _number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0])) 316 317 _comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine) 318 _defa = pyp.Literal("_is_default").suppress() 319 _dbl_quote = pyp.Literal('"').suppress() 320 321 _keyword = pyp.Word(pyp.alphanums + "-") 322 323 # value types 324 _value = pyp.Word(pyp.alphanums + "_-/.:") 325 _quoted = _dbl_quote + pyp.CharsNotIn('"') + _dbl_quote 326 _ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() + 327 pyp.Word(pyp.nums + ".") + _colon + _number) 328 _ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() + 329 pyp.Optional(_lbracket) + pyp.Word(pyp.hexnums + ":") + 330 pyp.Optional(_rbracket) + _colon + _number) 331 # meta device, extended syntax 332 _meta_value = ((_value ^ _quoted) + _lbracket + _number + _rbracket) 333 # device name, extended syntax 334 _device_value = pyp.Literal("minor").suppress() + _number 335 336 # a statement 337 _stmt = (~_rbrace + _keyword + ~_lbrace + 338 pyp.Optional(_ipv4_addr ^ _ipv6_addr ^ _value ^ _quoted ^ 339 _meta_value ^ _device_value) + 340 pyp.Optional(_defa) + _semi + 341 pyp.Optional(pyp.restOfLine).suppress()) 342 343 @classmethod
344 - def GetDevInfo(cls, show_data):
345 """Parse details about a given DRBD minor. 346 347 This returns, if available, the local backing device (as a path) 348 and the local and remote (ip, port) information from a string 349 containing the output of the `drbdsetup show` command as returned 350 by DRBD8Dev._GetShowData. 351 352 This will return a dict with keys: 353 - local_dev 354 - meta_dev 355 - meta_index 356 - local_addr 357 - remote_addr 358 359 """ 360 if not show_data: 361 return {} 362 363 try: 364 # run pyparse 365 results = (cls._GetShowParser()).parseString(show_data) 366 except pyp.ParseException, err: 367 base.ThrowError("Can't parse drbdsetup show output: %s", str(err)) 368 369 return cls._TransformParseResult(results)
370 371 @classmethod
372 - def _TransformParseResult(cls, parse_result):
373 raise NotImplementedError
374 375 @classmethod
376 - def _GetShowParser(cls):
377 """Return a parser for `drbd show` output. 378 379 This will either create or return an already-created parser for the 380 output of the command `drbd show`. 381 382 """ 383 if cls._PARSE_SHOW is None: 384 cls._PARSE_SHOW = cls._ConstructShowParser() 385 386 return cls._PARSE_SHOW
387 388 @classmethod
389 - def _ConstructShowParser(cls):
390 raise NotImplementedError
391
392 393 -class DRBD83ShowInfo(BaseShowInfo):
394 @classmethod
395 - def _ConstructShowParser(cls):
396 # an entire section 397 section_name = pyp.Word(pyp.alphas + "_") 398 section = section_name + \ 399 cls._lbrace + \ 400 pyp.ZeroOrMore(pyp.Group(cls._stmt)) + \ 401 cls._rbrace 402 403 bnf = pyp.ZeroOrMore(pyp.Group(section ^ cls._stmt)) 404 bnf.ignore(cls._comment) 405 406 return bnf
407 408 @classmethod
409 - def _TransformParseResult(cls, parse_result):
410 retval = {} 411 for section in parse_result: 412 sname = section[0] 413 if sname == "_this_host": 414 for lst in section[1:]: 415 if lst[0] == "disk": 416 retval["local_dev"] = lst[1] 417 elif lst[0] == "meta-disk": 418 retval["meta_dev"] = lst[1] 419 retval["meta_index"] = lst[2] 420 elif lst[0] == "address": 421 retval["local_addr"] = tuple(lst[1:]) 422 elif sname == "_remote_host": 423 for lst in section[1:]: 424 if lst[0] == "address": 425 retval["remote_addr"] = tuple(lst[1:]) 426 return retval
427
428 429 -class DRBD84ShowInfo(BaseShowInfo):
430 @classmethod
431 - def _ConstructShowParser(cls):
432 # an entire section (sections can be nested in DRBD 8.4, and there exist 433 # sections like "volume 0") 434 section_name = pyp.Word(pyp.alphas + "_") + \ 435 pyp.Optional(pyp.Word(pyp.nums)).suppress() # skip volume idx 436 section = pyp.Forward() 437 # pylint: disable=W0106 438 section << (section_name + 439 cls._lbrace + 440 pyp.ZeroOrMore(pyp.Group(cls._stmt ^ section)) + 441 cls._rbrace) 442 443 resource_name = pyp.Word(pyp.alphanums + "_-.") 444 resource = (pyp.Literal("resource") + resource_name).suppress() + \ 445 cls._lbrace + \ 446 pyp.ZeroOrMore(pyp.Group(section)) + \ 447 cls._rbrace 448 449 resource.ignore(cls._comment) 450 451 return resource
452 453 @classmethod
454 - def _TransformVolumeSection(cls, vol_content, retval):
455 for entry in vol_content: 456 if entry[0] == "disk" and len(entry) == 2 and \ 457 isinstance(entry[1], basestring): 458 retval["local_dev"] = entry[1] 459 elif entry[0] == "meta-disk": 460 if len(entry) > 1: 461 retval["meta_dev"] = entry[1] 462 if len(entry) > 2: 463 retval["meta_index"] = entry[2]
464 465 @classmethod
466 - def _TransformParseResult(cls, parse_result):
467 retval = {} 468 for section in parse_result: 469 sname = section[0] 470 if sname == "_this_host": 471 for lst in section[1:]: 472 if lst[0] == "address": 473 retval["local_addr"] = tuple(lst[1:]) 474 elif lst[0] == "volume": 475 cls._TransformVolumeSection(lst[1:], retval) 476 elif sname == "_remote_host": 477 for lst in section[1:]: 478 if lst[0] == "address": 479 retval["remote_addr"] = tuple(lst[1:]) 480 return retval
481