Package ganeti :: Module bdev
[hide private]
[frames] | no frames]

Source Code for Module ganeti.bdev

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007 Google Inc. 
   5  # 
   6  # This program is free software; you can redistribute it and/or modify 
   7  # it under the terms of the GNU General Public License as published by 
   8  # the Free Software Foundation; either version 2 of the License, or 
   9  # (at your option) any later version. 
  10  # 
  11  # This program is distributed in the hope that it will be useful, but 
  12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  14  # General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
  19  # 02110-1301, USA. 
  20   
  21   
  22  """Block device abstraction""" 
  23   
  24  import re 
  25  import time 
  26  import errno 
  27  import pyparsing as pyp 
  28  import os 
  29  import logging 
  30   
  31  from ganeti import utils 
  32  from ganeti import errors 
  33  from ganeti import constants 
34 35 36 -def _IgnoreError(fn, *args, **kwargs):
37 """Executes the given function, ignoring BlockDeviceErrors. 38 39 This is used in order to simplify the execution of cleanup or 40 rollback functions. 41 42 @rtype: boolean 43 @return: True when fn didn't raise an exception, False otherwise 44 45 """ 46 try: 47 fn(*args, **kwargs) 48 return True 49 except errors.BlockDeviceError, err: 50 logging.warning("Caught BlockDeviceError but ignoring: %s" % str(err)) 51 return False
52
53 54 -def _ThrowError(msg, *args):
55 """Log an error to the node daemon and the raise an exception. 56 57 @type msg: string 58 @param msg: the text of the exception 59 @raise errors.BlockDeviceError 60 61 """ 62 if args: 63 msg = msg % args 64 logging.error(msg) 65 raise errors.BlockDeviceError(msg)
66
67 68 -class BlockDev(object):
69 """Block device abstract class. 70 71 A block device can be in the following states: 72 - not existing on the system, and by `Create()` it goes into: 73 - existing but not setup/not active, and by `Assemble()` goes into: 74 - active read-write and by `Open()` it goes into 75 - online (=used, or ready for use) 76 77 A device can also be online but read-only, however we are not using 78 the readonly state (LV has it, if needed in the future) and we are 79 usually looking at this like at a stack, so it's easier to 80 conceptualise the transition from not-existing to online and back 81 like a linear one. 82 83 The many different states of the device are due to the fact that we 84 need to cover many device types: 85 - logical volumes are created, lvchange -a y $lv, and used 86 - drbd devices are attached to a local disk/remote peer and made primary 87 88 A block device is identified by three items: 89 - the /dev path of the device (dynamic) 90 - a unique ID of the device (static) 91 - it's major/minor pair (dynamic) 92 93 Not all devices implement both the first two as distinct items. LVM 94 logical volumes have their unique ID (the pair volume group, logical 95 volume name) in a 1-to-1 relation to the dev path. For DRBD devices, 96 the /dev path is again dynamic and the unique id is the pair (host1, 97 dev1), (host2, dev2). 98 99 You can get to a device in two ways: 100 - creating the (real) device, which returns you 101 an attached instance (lvcreate) 102 - attaching of a python instance to an existing (real) device 103 104 The second point, the attachement to a device, is different 105 depending on whether the device is assembled or not. At init() time, 106 we search for a device with the same unique_id as us. If found, 107 good. It also means that the device is already assembled. If not, 108 after assembly we'll have our correct major/minor. 109 110 """
111 - def __init__(self, unique_id, children, size):
112 self._children = children 113 self.dev_path = None 114 self.unique_id = unique_id 115 self.major = None 116 self.minor = None 117 self.attached = False 118 self.size = size
119
120 - def Assemble(self):
121 """Assemble the device from its components. 122 123 Implementations of this method by child classes must ensure that: 124 - after the device has been assembled, it knows its major/minor 125 numbers; this allows other devices (usually parents) to probe 126 correctly for their children 127 - calling this method on an existing, in-use device is safe 128 - if the device is already configured (and in an OK state), 129 this method is idempotent 130 131 """ 132 pass
133
134 - def Attach(self):
135 """Find a device which matches our config and attach to it. 136 137 """ 138 raise NotImplementedError
139
140 - def Close(self):
141 """Notifies that the device will no longer be used for I/O. 142 143 """ 144 raise NotImplementedError
145 146 @classmethod
147 - def Create(cls, unique_id, children, size):
148 """Create the device. 149 150 If the device cannot be created, it will return None 151 instead. Error messages go to the logging system. 152 153 Note that for some devices, the unique_id is used, and for other, 154 the children. The idea is that these two, taken together, are 155 enough for both creation and assembly (later). 156 157 """ 158 raise NotImplementedError
159
160 - def Remove(self):
161 """Remove this device. 162 163 This makes sense only for some of the device types: LV and file 164 storage. Also note that if the device can't attach, the removal 165 can't be completed. 166 167 """ 168 raise NotImplementedError
169
170 - def Rename(self, new_id):
171 """Rename this device. 172 173 This may or may not make sense for a given device type. 174 175 """ 176 raise NotImplementedError
177
178 - def Open(self, force=False):
179 """Make the device ready for use. 180 181 This makes the device ready for I/O. For now, just the DRBD 182 devices need this. 183 184 The force parameter signifies that if the device has any kind of 185 --force thing, it should be used, we know what we are doing. 186 187 """ 188 raise NotImplementedError
189
190 - def Shutdown(self):
191 """Shut down the device, freeing its children. 192 193 This undoes the `Assemble()` work, except for the child 194 assembling; as such, the children on the device are still 195 assembled after this call. 196 197 """ 198 raise NotImplementedError
199
200 - def SetSyncSpeed(self, speed):
201 """Adjust the sync speed of the mirror. 202 203 In case this is not a mirroring device, this is no-op. 204 205 """ 206 result = True 207 if self._children: 208 for child in self._children: 209 result = result and child.SetSyncSpeed(speed) 210 return result
211
212 - def GetSyncStatus(self):
213 """Returns the sync status of the device. 214 215 If this device is a mirroring device, this function returns the 216 status of the mirror. 217 218 If sync_percent is None, it means the device is not syncing. 219 220 If estimated_time is None, it means we can't estimate 221 the time needed, otherwise it's the time left in seconds. 222 223 If is_degraded is True, it means the device is missing 224 redundancy. This is usually a sign that something went wrong in 225 the device setup, if sync_percent is None. 226 227 The ldisk parameter represents the degradation of the local 228 data. This is only valid for some devices, the rest will always 229 return False (not degraded). 230 231 @rtype: tuple 232 @return: (sync_percent, estimated_time, is_degraded, ldisk) 233 234 """ 235 return None, None, False, False
236 237
238 - def CombinedSyncStatus(self):
239 """Calculate the mirror status recursively for our children. 240 241 The return value is the same as for `GetSyncStatus()` except the 242 minimum percent and maximum time are calculated across our 243 children. 244 245 """ 246 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus() 247 if self._children: 248 for child in self._children: 249 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus() 250 if min_percent is None: 251 min_percent = c_percent 252 elif c_percent is not None: 253 min_percent = min(min_percent, c_percent) 254 if max_time is None: 255 max_time = c_time 256 elif c_time is not None: 257 max_time = max(max_time, c_time) 258 is_degraded = is_degraded or c_degraded 259 ldisk = ldisk or c_ldisk 260 return min_percent, max_time, is_degraded, ldisk
261 262
263 - def SetInfo(self, text):
264 """Update metadata with info text. 265 266 Only supported for some device types. 267 268 """ 269 for child in self._children: 270 child.SetInfo(text)
271
272 - def Grow(self, amount):
273 """Grow the block device. 274 275 @param amount: the amount (in mebibytes) to grow with 276 277 """ 278 raise NotImplementedError
279
280 - def GetActualSize(self):
281 """Return the actual disk size. 282 283 @note: the device needs to be active when this is called 284 285 """ 286 assert self.attached, "BlockDevice not attached in GetActualSize()" 287 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path]) 288 if result.failed: 289 _ThrowError("blockdev failed (%s): %s", 290 result.fail_reason, result.output) 291 try: 292 sz = int(result.output.strip()) 293 except (ValueError, TypeError), err: 294 _ThrowError("Failed to parse blockdev output: %s", str(err)) 295 return sz
296
297 - def __repr__(self):
298 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" % 299 (self.__class__, self.unique_id, self._children, 300 self.major, self.minor, self.dev_path))
301
302 303 -class LogicalVolume(BlockDev):
304 """Logical Volume block device. 305 306 """
307 - def __init__(self, unique_id, children, size):
308 """Attaches to a LV device. 309 310 The unique_id is a tuple (vg_name, lv_name) 311 312 """ 313 super(LogicalVolume, self).__init__(unique_id, children, size) 314 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 315 raise ValueError("Invalid configuration data %s" % str(unique_id)) 316 self._vg_name, self._lv_name = unique_id 317 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name) 318 self._degraded = True 319 self.major = self.minor = self.pe_size = self.stripe_count = None 320 self.Attach()
321 322 @classmethod
323 - def Create(cls, unique_id, children, size):
324 """Create a new logical volume. 325 326 """ 327 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 328 raise errors.ProgrammerError("Invalid configuration data %s" % 329 str(unique_id)) 330 vg_name, lv_name = unique_id 331 pvs_info = cls.GetPVInfo(vg_name) 332 if not pvs_info: 333 _ThrowError("Can't compute PV info for vg %s", vg_name) 334 pvs_info.sort() 335 pvs_info.reverse() 336 337 pvlist = [ pv[1] for pv in pvs_info ] 338 free_size = sum([ pv[0] for pv in pvs_info ]) 339 current_pvs = len(pvlist) 340 stripes = min(current_pvs, constants.LVM_STRIPECOUNT) 341 342 # The size constraint should have been checked from the master before 343 # calling the create function. 344 if free_size < size: 345 _ThrowError("Not enough free space: required %s," 346 " available %s", size, free_size) 347 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name] 348 # If the free space is not well distributed, we won't be able to 349 # create an optimally-striped volume; in that case, we want to try 350 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of 351 # stripes 352 for stripes_arg in range(stripes, 0, -1): 353 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist) 354 if not result.failed: 355 break 356 if result.failed: 357 _ThrowError("LV create failed (%s): %s", 358 result.fail_reason, result.output) 359 return LogicalVolume(unique_id, children, size)
360 361 @staticmethod
362 - def GetPVInfo(vg_name):
363 """Get the free space info for PVs in a volume group. 364 365 @param vg_name: the volume group name 366 367 @rtype: list 368 @return: list of tuples (free_space, name) with free_space in mebibytes 369 370 """ 371 command = ["pvs", "--noheadings", "--nosuffix", "--units=m", 372 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered", 373 "--separator=:"] 374 result = utils.RunCmd(command) 375 if result.failed: 376 logging.error("Can't get the PV information: %s - %s", 377 result.fail_reason, result.output) 378 return None 379 data = [] 380 for line in result.stdout.splitlines(): 381 fields = line.strip().split(':') 382 if len(fields) != 4: 383 logging.error("Can't parse pvs output: line '%s'", line) 384 return None 385 # skip over pvs from another vg or ones which are not allocatable 386 if fields[1] != vg_name or fields[3][0] != 'a': 387 continue 388 data.append((float(fields[2]), fields[0])) 389 390 return data
391
392 - def Remove(self):
393 """Remove this logical volume. 394 395 """ 396 if not self.minor and not self.Attach(): 397 # the LV does not exist 398 return 399 result = utils.RunCmd(["lvremove", "-f", "%s/%s" % 400 (self._vg_name, self._lv_name)]) 401 if result.failed: 402 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
403
404 - def Rename(self, new_id):
405 """Rename this logical volume. 406 407 """ 408 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2: 409 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id) 410 new_vg, new_name = new_id 411 if new_vg != self._vg_name: 412 raise errors.ProgrammerError("Can't move a logical volume across" 413 " volume groups (from %s to to %s)" % 414 (self._vg_name, new_vg)) 415 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name]) 416 if result.failed: 417 _ThrowError("Failed to rename the logical volume: %s", result.output) 418 self._lv_name = new_name 419 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
420
421 - def Attach(self):
422 """Attach to an existing LV. 423 424 This method will try to see if an existing and active LV exists 425 which matches our name. If so, its major/minor will be 426 recorded. 427 428 """ 429 self.attached = False 430 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,", 431 "--units=m", "--nosuffix", 432 "-olv_attr,lv_kernel_major,lv_kernel_minor," 433 "vg_extent_size,stripes", self.dev_path]) 434 if result.failed: 435 logging.error("Can't find LV %s: %s, %s", 436 self.dev_path, result.fail_reason, result.output) 437 return False 438 # the output can (and will) have multiple lines for multi-segment 439 # LVs, as the 'stripes' parameter is a segment one, so we take 440 # only the last entry, which is the one we're interested in; note 441 # that with LVM2 anyway the 'stripes' value must be constant 442 # across segments, so this is a no-op actually 443 out = result.stdout.splitlines() 444 if not out: # totally empty result? splitlines() returns at least 445 # one line for any non-empty string 446 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out)) 447 return False 448 out = out[-1].strip().rstrip(',') 449 out = out.split(",") 450 if len(out) != 5: 451 logging.error("Can't parse LVS output, len(%s) != 5", str(out)) 452 return False 453 454 status, major, minor, pe_size, stripes = out 455 if len(status) != 6: 456 logging.error("lvs lv_attr is not 6 characters (%s)", status) 457 return False 458 459 try: 460 major = int(major) 461 minor = int(minor) 462 except (TypeError, ValueError), err: 463 logging.error("lvs major/minor cannot be parsed: %s", str(err)) 464 465 try: 466 pe_size = int(float(pe_size)) 467 except (TypeError, ValueError), err: 468 logging.error("Can't parse vg extent size: %s", err) 469 return False 470 471 try: 472 stripes = int(stripes) 473 except (TypeError, ValueError), err: 474 logging.error("Can't parse the number of stripes: %s", err) 475 return False 476 477 self.major = major 478 self.minor = minor 479 self.pe_size = pe_size 480 self.stripe_count = stripes 481 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing 482 # storage 483 self.attached = True 484 return True
485
486 - def Assemble(self):
487 """Assemble the device. 488 489 We always run `lvchange -ay` on the LV to ensure it's active before 490 use, as there were cases when xenvg was not active after boot 491 (also possibly after disk issues). 492 493 """ 494 result = utils.RunCmd(["lvchange", "-ay", self.dev_path]) 495 if result.failed: 496 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
497
498 - def Shutdown(self):
499 """Shutdown the device. 500 501 This is a no-op for the LV device type, as we don't deactivate the 502 volumes on shutdown. 503 504 """ 505 pass
506
507 - def GetSyncStatus(self):
508 """Returns the sync status of the device. 509 510 If this device is a mirroring device, this function returns the 511 status of the mirror. 512 513 For logical volumes, sync_percent and estimated_time are always 514 None (no recovery in progress, as we don't handle the mirrored LV 515 case). The is_degraded parameter is the inverse of the ldisk 516 parameter. 517 518 For the ldisk parameter, we check if the logical volume has the 519 'virtual' type, which means it's not backed by existing storage 520 anymore (read from it return I/O error). This happens after a 521 physical disk failure and subsequent 'vgreduce --removemissing' on 522 the volume group. 523 524 The status was already read in Attach, so we just return it. 525 526 @rtype: tuple 527 @return: (sync_percent, estimated_time, is_degraded, ldisk) 528 529 """ 530 return None, None, self._degraded, self._degraded
531
532 - def Open(self, force=False):
533 """Make the device ready for I/O. 534 535 This is a no-op for the LV device type. 536 537 """ 538 pass
539
540 - def Close(self):
541 """Notifies that the device will no longer be used for I/O. 542 543 This is a no-op for the LV device type. 544 545 """ 546 pass
547
548 - def Snapshot(self, size):
549 """Create a snapshot copy of an lvm block device. 550 551 """ 552 snap_name = self._lv_name + ".snap" 553 554 # remove existing snapshot if found 555 snap = LogicalVolume((self._vg_name, snap_name), None, size) 556 _IgnoreError(snap.Remove) 557 558 pvs_info = self.GetPVInfo(self._vg_name) 559 if not pvs_info: 560 _ThrowError("Can't compute PV info for vg %s", self._vg_name) 561 pvs_info.sort() 562 pvs_info.reverse() 563 free_size, pv_name = pvs_info[0] 564 if free_size < size: 565 _ThrowError("Not enough free space: required %s," 566 " available %s", size, free_size) 567 568 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s", 569 "-n%s" % snap_name, self.dev_path]) 570 if result.failed: 571 _ThrowError("command: %s error: %s - %s", 572 result.cmd, result.fail_reason, result.output) 573 574 return snap_name
575
576 - def SetInfo(self, text):
577 """Update metadata with info text. 578 579 """ 580 BlockDev.SetInfo(self, text) 581 582 # Replace invalid characters 583 text = re.sub('^[^A-Za-z0-9_+.]', '_', text) 584 text = re.sub('[^-A-Za-z0-9_+.]', '_', text) 585 586 # Only up to 128 characters are allowed 587 text = text[:128] 588 589 result = utils.RunCmd(["lvchange", "--addtag", text, 590 self.dev_path]) 591 if result.failed: 592 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason, 593 result.output)
594
595 - def Grow(self, amount):
596 """Grow the logical volume. 597 598 """ 599 if self.pe_size is None or self.stripe_count is None: 600 if not self.Attach(): 601 _ThrowError("Can't attach to LV during Grow()") 602 full_stripe_size = self.pe_size * self.stripe_count 603 rest = amount % full_stripe_size 604 if rest != 0: 605 amount += full_stripe_size - rest 606 # we try multiple algorithms since the 'best' ones might not have 607 # space available in the right place, but later ones might (since 608 # they have less constraints); also note that only recent LVM 609 # supports 'cling' 610 for alloc_policy in "contiguous", "cling", "normal": 611 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy, 612 "-L", "+%dm" % amount, self.dev_path]) 613 if not result.failed: 614 return 615 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
616
617 618 -class DRBD8Status(object):
619 """A DRBD status representation class. 620 621 Note that this doesn't support unconfigured devices (cs:Unconfigured). 622 623 """ 624 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$") 625 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)" 626 "\s+ds:([^/]+)/(\S+)\s+.*$") 627 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*" 628 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") 629 630 CS_UNCONFIGURED = "Unconfigured" 631 CS_STANDALONE = "StandAlone" 632 CS_WFCONNECTION = "WFConnection" 633 CS_WFREPORTPARAMS = "WFReportParams" 634 CS_CONNECTED = "Connected" 635 CS_STARTINGSYNCS = "StartingSyncS" 636 CS_STARTINGSYNCT = "StartingSyncT" 637 CS_WFBITMAPS = "WFBitMapS" 638 CS_WFBITMAPT = "WFBitMapT" 639 CS_WFSYNCUUID = "WFSyncUUID" 640 CS_SYNCSOURCE = "SyncSource" 641 CS_SYNCTARGET = "SyncTarget" 642 CS_PAUSEDSYNCS = "PausedSyncS" 643 CS_PAUSEDSYNCT = "PausedSyncT" 644 CSET_SYNC = frozenset([ 645 CS_WFREPORTPARAMS, 646 CS_STARTINGSYNCS, 647 CS_STARTINGSYNCT, 648 CS_WFBITMAPS, 649 CS_WFBITMAPT, 650 CS_WFSYNCUUID, 651 CS_SYNCSOURCE, 652 CS_SYNCTARGET, 653 CS_PAUSEDSYNCS, 654 CS_PAUSEDSYNCT, 655 ]) 656 657 DS_DISKLESS = "Diskless" 658 DS_ATTACHING = "Attaching" # transient state 659 DS_FAILED = "Failed" # transient state, next: diskless 660 DS_NEGOTIATING = "Negotiating" # transient state 661 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation 662 DS_OUTDATED = "Outdated" 663 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected 664 DS_CONSISTENT = "Consistent" 665 DS_UPTODATE = "UpToDate" # normal state 666 667 RO_PRIMARY = "Primary" 668 RO_SECONDARY = "Secondary" 669 RO_UNKNOWN = "Unknown" 670
671 - def __init__(self, procline):
672 u = self.UNCONF_RE.match(procline) 673 if u: 674 self.cstatus = self.CS_UNCONFIGURED 675 self.lrole = self.rrole = self.ldisk = self.rdisk = None 676 else: 677 m = self.LINE_RE.match(procline) 678 if not m: 679 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline) 680 self.cstatus = m.group(1) 681 self.lrole = m.group(2) 682 self.rrole = m.group(3) 683 self.ldisk = m.group(4) 684 self.rdisk = m.group(5) 685 686 # end reading of data from the LINE_RE or UNCONF_RE 687 688 self.is_standalone = self.cstatus == self.CS_STANDALONE 689 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION 690 self.is_connected = self.cstatus == self.CS_CONNECTED 691 self.is_primary = self.lrole == self.RO_PRIMARY 692 self.is_secondary = self.lrole == self.RO_SECONDARY 693 self.peer_primary = self.rrole == self.RO_PRIMARY 694 self.peer_secondary = self.rrole == self.RO_SECONDARY 695 self.both_primary = self.is_primary and self.peer_primary 696 self.both_secondary = self.is_secondary and self.peer_secondary 697 698 self.is_diskless = self.ldisk == self.DS_DISKLESS 699 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE 700 701 self.is_in_resync = self.cstatus in self.CSET_SYNC 702 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED 703 704 m = self.SYNC_RE.match(procline) 705 if m: 706 self.sync_percent = float(m.group(1)) 707 hours = int(m.group(2)) 708 minutes = int(m.group(3)) 709 seconds = int(m.group(4)) 710 self.est_time = hours * 3600 + minutes * 60 + seconds 711 else: 712 # we have (in this if branch) no percent information, but if 713 # we're resyncing we need to 'fake' a sync percent information, 714 # as this is how cmdlib determines if it makes sense to wait for 715 # resyncing or not 716 if self.is_in_resync: 717 self.sync_percent = 0 718 else: 719 self.sync_percent = None 720 self.est_time = None
721
722 723 -class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
724 """Base DRBD class. 725 726 This class contains a few bits of common functionality between the 727 0.7 and 8.x versions of DRBD. 728 729 """ 730 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)" 731 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)") 732 733 _DRBD_MAJOR = 147 734 _ST_UNCONFIGURED = "Unconfigured" 735 _ST_WFCONNECTION = "WFConnection" 736 _ST_CONNECTED = "Connected" 737 738 _STATUS_FILE = "/proc/drbd" 739 740 @staticmethod
741 - def _GetProcData(filename=_STATUS_FILE):
742 """Return data from /proc/drbd. 743 744 """ 745 try: 746 stat = open(filename, "r") 747 try: 748 data = stat.read().splitlines() 749 finally: 750 stat.close() 751 except EnvironmentError, err: 752 if err.errno == errno.ENOENT: 753 _ThrowError("The file %s cannot be opened, check if the module" 754 " is loaded (%s)", filename, str(err)) 755 else: 756 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err)) 757 if not data: 758 _ThrowError("Can't read any data from %s", filename) 759 return data
760 761 @staticmethod
762 - def _MassageProcData(data):
763 """Transform the output of _GetProdData into a nicer form. 764 765 @return: a dictionary of minor: joined lines from /proc/drbd 766 for that minor 767 768 """ 769 lmatch = re.compile("^ *([0-9]+):.*$") 770 results = {} 771 old_minor = old_line = None 772 for line in data: 773 if not line: # completely empty lines, as can be returned by drbd8.0+ 774 continue 775 lresult = lmatch.match(line) 776 if lresult is not None: 777 if old_minor is not None: 778 results[old_minor] = old_line 779 old_minor = int(lresult.group(1)) 780 old_line = line 781 else: 782 if old_minor is not None: 783 old_line += " " + line.strip() 784 # add last line 785 if old_minor is not None: 786 results[old_minor] = old_line 787 return results
788 789 @classmethod
790 - def _GetVersion(cls):
791 """Return the DRBD version. 792 793 This will return a dict with keys: 794 - k_major 795 - k_minor 796 - k_point 797 - api 798 - proto 799 - proto2 (only on drbd > 8.2.X) 800 801 """ 802 proc_data = cls._GetProcData() 803 first_line = proc_data[0].strip() 804 version = cls._VERSION_RE.match(first_line) 805 if not version: 806 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" % 807 first_line) 808 809 values = version.groups() 810 retval = {'k_major': int(values[0]), 811 'k_minor': int(values[1]), 812 'k_point': int(values[2]), 813 'api': int(values[3]), 814 'proto': int(values[4]), 815 } 816 if values[5] is not None: 817 retval['proto2'] = values[5] 818 819 return retval
820 821 @staticmethod
822 - def _DevPath(minor):
823 """Return the path to a drbd device for a given minor. 824 825 """ 826 return "/dev/drbd%d" % minor
827 828 @classmethod
829 - def GetUsedDevs(cls):
830 """Compute the list of used DRBD devices. 831 832 """ 833 data = cls._GetProcData() 834 835 used_devs = {} 836 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$") 837 for line in data: 838 match = valid_line.match(line) 839 if not match: 840 continue 841 minor = int(match.group(1)) 842 state = match.group(2) 843 if state == cls._ST_UNCONFIGURED: 844 continue 845 used_devs[minor] = state, line 846 847 return used_devs
848
849 - def _SetFromMinor(self, minor):
850 """Set our parameters based on the given minor. 851 852 This sets our minor variable and our dev_path. 853 854 """ 855 if minor is None: 856 self.minor = self.dev_path = None 857 self.attached = False 858 else: 859 self.minor = minor 860 self.dev_path = self._DevPath(minor) 861 self.attached = True
862 863 @staticmethod
864 - def _CheckMetaSize(meta_device):
865 """Check if the given meta device looks like a valid one. 866 867 This currently only check the size, which must be around 868 128MiB. 869 870 """ 871 result = utils.RunCmd(["blockdev", "--getsize", meta_device]) 872 if result.failed: 873 _ThrowError("Failed to get device size: %s - %s", 874 result.fail_reason, result.output) 875 try: 876 sectors = int(result.stdout) 877 except (TypeError, ValueError): 878 _ThrowError("Invalid output from blockdev: '%s'", result.stdout) 879 bytes = sectors * 512 880 if bytes < 128 * 1024 * 1024: # less than 128MiB 881 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024)) 882 # the maximum *valid* size of the meta device when living on top 883 # of LVM is hard to compute: it depends on the number of stripes 884 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB 885 # (normal size), but an eight-stripe 128MB PE will result in a 1GB 886 # size meta device; as such, we restrict it to 1GB (a little bit 887 # too generous, but making assumptions about PE size is hard) 888 if bytes > 1024 * 1024 * 1024: 889 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
890
891 - def Rename(self, new_id):
892 """Rename a device. 893 894 This is not supported for drbd devices. 895 896 """ 897 raise errors.ProgrammerError("Can't rename a drbd device")
898
899 900 -class DRBD8(BaseDRBD):
901 """DRBD v8.x block device. 902 903 This implements the local host part of the DRBD device, i.e. it 904 doesn't do anything to the supposed peer. If you need a fully 905 connected DRBD pair, you need to use this class on both hosts. 906 907 The unique_id for the drbd device is the (local_ip, local_port, 908 remote_ip, remote_port) tuple, and it must have two children: the 909 data device and the meta_device. The meta device is checked for 910 valid size and is zeroed on create. 911 912 """ 913 _MAX_MINORS = 255 914 _PARSE_SHOW = None 915 916 # timeout constants 917 _NET_RECONFIG_TIMEOUT = 60 918
919 - def __init__(self, unique_id, children, size):
920 if children and children.count(None) > 0: 921 children = [] 922 super(DRBD8, self).__init__(unique_id, children, size) 923 self.major = self._DRBD_MAJOR 924 version = self._GetVersion() 925 if version['k_major'] != 8 : 926 _ThrowError("Mismatch in DRBD kernel version and requested ganeti" 927 " usage: kernel is %s.%s, ganeti wants 8.x", 928 version['k_major'], version['k_minor']) 929 930 if len(children) not in (0, 2): 931 raise ValueError("Invalid configuration data %s" % str(children)) 932 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6: 933 raise ValueError("Invalid configuration data %s" % str(unique_id)) 934 (self._lhost, self._lport, 935 self._rhost, self._rport, 936 self._aminor, self._secret) = unique_id 937 if (self._lhost is not None and self._lhost == self._rhost and 938 self._lport == self._rport): 939 raise ValueError("Invalid configuration data, same local/remote %s" % 940 (unique_id,)) 941 self.Attach()
942 943 @classmethod
944 - def _InitMeta(cls, minor, dev_path):
945 """Initialize a meta device. 946 947 This will not work if the given minor is in use. 948 949 """ 950 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor), 951 "v08", dev_path, "0", "create-md"]) 952 if result.failed: 953 _ThrowError("Can't initialize meta device: %s", result.output)
954 955 @classmethod
956 - def _FindUnusedMinor(cls):
957 """Find an unused DRBD device. 958 959 This is specific to 8.x as the minors are allocated dynamically, 960 so non-existing numbers up to a max minor count are actually free. 961 962 """ 963 data = cls._GetProcData() 964 965 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$") 966 used_line = re.compile("^ *([0-9]+): cs:") 967 highest = None 968 for line in data: 969 match = unused_line.match(line) 970 if match: 971 return int(match.group(1)) 972 match = used_line.match(line) 973 if match: 974 minor = int(match.group(1)) 975 highest = max(highest, minor) 976 if highest is None: # there are no minors in use at all 977 return 0 978 if highest >= cls._MAX_MINORS: 979 logging.error("Error: no free drbd minors!") 980 raise errors.BlockDeviceError("Can't find a free DRBD minor") 981 return highest + 1
982 983 @classmethod
984 - def _GetShowParser(cls):
985 """Return a parser for `drbd show` output. 986 987 This will either create or return an already-create parser for the 988 output of the command `drbd show`. 989 990 """ 991 if cls._PARSE_SHOW is not None: 992 return cls._PARSE_SHOW 993 994 # pyparsing setup 995 lbrace = pyp.Literal("{").suppress() 996 rbrace = pyp.Literal("}").suppress() 997 semi = pyp.Literal(";").suppress() 998 # this also converts the value to an int 999 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0])) 1000 1001 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine) 1002 defa = pyp.Literal("_is_default").suppress() 1003 dbl_quote = pyp.Literal('"').suppress() 1004 1005 keyword = pyp.Word(pyp.alphanums + '-') 1006 1007 # value types 1008 value = pyp.Word(pyp.alphanums + '_-/.:') 1009 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote 1010 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() + 1011 pyp.Optional(pyp.Literal("ipv6")).suppress()) 1012 addr_port = (addr_type + pyp.Word(pyp.nums + '.') + 1013 pyp.Literal(':').suppress() + number) 1014 # meta device, extended syntax 1015 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() + 1016 number + pyp.Word(']').suppress()) 1017 # device name, extended syntax 1018 device_value = pyp.Literal("minor").suppress() + number 1019 1020 # a statement 1021 stmt = (~rbrace + keyword + ~lbrace + 1022 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^ 1023 device_value) + 1024 pyp.Optional(defa) + semi + 1025 pyp.Optional(pyp.restOfLine).suppress()) 1026 1027 # an entire section 1028 section_name = pyp.Word(pyp.alphas + '_') 1029 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace 1030 1031 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt)) 1032 bnf.ignore(comment) 1033 1034 cls._PARSE_SHOW = bnf 1035 1036 return bnf
1037 1038 @classmethod
1039 - def _GetShowData(cls, minor):
1040 """Return the `drbdsetup show` data for a minor. 1041 1042 """ 1043 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"]) 1044 if result.failed: 1045 logging.error("Can't display the drbd config: %s - %s", 1046 result.fail_reason, result.output) 1047 return None 1048 return result.stdout
1049 1050 @classmethod
1051 - def _GetDevInfo(cls, out):
1052 """Parse details about a given DRBD minor. 1053 1054 This return, if available, the local backing device (as a path) 1055 and the local and remote (ip, port) information from a string 1056 containing the output of the `drbdsetup show` command as returned 1057 by _GetShowData. 1058 1059 """ 1060 data = {} 1061 if not out: 1062 return data 1063 1064 bnf = cls._GetShowParser() 1065 # run pyparse 1066 1067 try: 1068 results = bnf.parseString(out) 1069 except pyp.ParseException, err: 1070 _ThrowError("Can't parse drbdsetup show output: %s", str(err)) 1071 1072 # and massage the results into our desired format 1073 for section in results: 1074 sname = section[0] 1075 if sname == "_this_host": 1076 for lst in section[1:]: 1077 if lst[0] == "disk": 1078 data["local_dev"] = lst[1] 1079 elif lst[0] == "meta-disk": 1080 data["meta_dev"] = lst[1] 1081 data["meta_index"] = lst[2] 1082 elif lst[0] == "address": 1083 data["local_addr"] = tuple(lst[1:]) 1084 elif sname == "_remote_host": 1085 for lst in section[1:]: 1086 if lst[0] == "address": 1087 data["remote_addr"] = tuple(lst[1:]) 1088 return data
1089
1090 - def _MatchesLocal(self, info):
1091 """Test if our local config matches with an existing device. 1092 1093 The parameter should be as returned from `_GetDevInfo()`. This 1094 method tests if our local backing device is the same as the one in 1095 the info parameter, in effect testing if we look like the given 1096 device. 1097 1098 """ 1099 if self._children: 1100 backend, meta = self._children 1101 else: 1102 backend = meta = None 1103 1104 if backend is not None: 1105 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path) 1106 else: 1107 retval = ("local_dev" not in info) 1108 1109 if meta is not None: 1110 retval = retval and ("meta_dev" in info and 1111 info["meta_dev"] == meta.dev_path) 1112 retval = retval and ("meta_index" in info and 1113 info["meta_index"] == 0) 1114 else: 1115 retval = retval and ("meta_dev" not in info and 1116 "meta_index" not in info) 1117 return retval
1118
1119 - def _MatchesNet(self, info):
1120 """Test if our network config matches with an existing device. 1121 1122 The parameter should be as returned from `_GetDevInfo()`. This 1123 method tests if our network configuration is the same as the one 1124 in the info parameter, in effect testing if we look like the given 1125 device. 1126 1127 """ 1128 if (((self._lhost is None and not ("local_addr" in info)) and 1129 (self._rhost is None and not ("remote_addr" in info)))): 1130 return True 1131 1132 if self._lhost is None: 1133 return False 1134 1135 if not ("local_addr" in info and 1136 "remote_addr" in info): 1137 return False 1138 1139 retval = (info["local_addr"] == (self._lhost, self._lport)) 1140 retval = (retval and 1141 info["remote_addr"] == (self._rhost, self._rport)) 1142 return retval
1143 1144 @classmethod
1145 - def _AssembleLocal(cls, minor, backend, meta, size):
1146 """Configure the local part of a DRBD device. 1147 1148 """ 1149 args = ["drbdsetup", cls._DevPath(minor), "disk", 1150 backend, meta, "0", 1151 "-e", "detach", 1152 "--create-device"] 1153 if size: 1154 args.extend(["-d", "%sm" % size]) 1155 result = utils.RunCmd(args) 1156 if result.failed: 1157 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1158 1159 @classmethod
1160 - def _AssembleNet(cls, minor, net_info, protocol, 1161 dual_pri=False, hmac=None, secret=None):
1162 """Configure the network part of the device. 1163 1164 """ 1165 lhost, lport, rhost, rport = net_info 1166 if None in net_info: 1167 # we don't want network connection and actually want to make 1168 # sure its shutdown 1169 cls._ShutdownNet(minor) 1170 return 1171 1172 # Workaround for a race condition. When DRBD is doing its dance to 1173 # establish a connection with its peer, it also sends the 1174 # synchronization speed over the wire. In some cases setting the 1175 # sync speed only after setting up both sides can race with DRBD 1176 # connecting, hence we set it here before telling DRBD anything 1177 # about its peer. 1178 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED) 1179 1180 args = ["drbdsetup", cls._DevPath(minor), "net", 1181 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol, 1182 "-A", "discard-zero-changes", 1183 "-B", "consensus", 1184 "--create-device", 1185 ] 1186 if dual_pri: 1187 args.append("-m") 1188 if hmac and secret: 1189 args.extend(["-a", hmac, "-x", secret]) 1190 result = utils.RunCmd(args) 1191 if result.failed: 1192 _ThrowError("drbd%d: can't setup network: %s - %s", 1193 minor, result.fail_reason, result.output) 1194 1195 timeout = time.time() + 10 1196 ok = False 1197 while time.time() < timeout: 1198 info = cls._GetDevInfo(cls._GetShowData(minor)) 1199 if not "local_addr" in info or not "remote_addr" in info: 1200 time.sleep(1) 1201 continue 1202 if (info["local_addr"] != (lhost, lport) or 1203 info["remote_addr"] != (rhost, rport)): 1204 time.sleep(1) 1205 continue 1206 ok = True 1207 break 1208 if not ok: 1209 _ThrowError("drbd%d: timeout while configuring network", minor)
1210
1211 - def AddChildren(self, devices):
1212 """Add a disk to the DRBD device. 1213 1214 """ 1215 if self.minor is None: 1216 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren", 1217 self._aminor) 1218 if len(devices) != 2: 1219 _ThrowError("drbd%d: need two devices for AddChildren", self.minor) 1220 info = self._GetDevInfo(self._GetShowData(self.minor)) 1221 if "local_dev" in info: 1222 _ThrowError("drbd%d: already attached to a local disk", self.minor) 1223 backend, meta = devices 1224 if backend.dev_path is None or meta.dev_path is None: 1225 _ThrowError("drbd%d: children not ready during AddChildren", self.minor) 1226 backend.Open() 1227 meta.Open() 1228 self._CheckMetaSize(meta.dev_path) 1229 self._InitMeta(self._FindUnusedMinor(), meta.dev_path) 1230 1231 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size) 1232 self._children = devices
1233
1234 - def RemoveChildren(self, devices):
1235 """Detach the drbd device from local storage. 1236 1237 """ 1238 if self.minor is None: 1239 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren", 1240 self._aminor) 1241 # early return if we don't actually have backing storage 1242 info = self._GetDevInfo(self._GetShowData(self.minor)) 1243 if "local_dev" not in info: 1244 return 1245 if len(self._children) != 2: 1246 _ThrowError("drbd%d: we don't have two children: %s", self.minor, 1247 self._children) 1248 if self._children.count(None) == 2: # we don't actually have children :) 1249 logging.warning("drbd%d: requested detach while detached", self.minor) 1250 return 1251 if len(devices) != 2: 1252 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor) 1253 for child, dev in zip(self._children, devices): 1254 if dev != child.dev_path: 1255 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in" 1256 " RemoveChildren", self.minor, dev, child.dev_path) 1257 1258 self._ShutdownLocal(self.minor) 1259 self._children = []
1260 1261 @classmethod
1262 - def _SetMinorSyncSpeed(cls, minor, kbytes):
1263 """Set the speed of the DRBD syncer. 1264 1265 This is the low-level implementation. 1266 1267 @type minor: int 1268 @param minor: the drbd minor whose settings we change 1269 @type kbytes: int 1270 @param kbytes: the speed in kbytes/second 1271 @rtype: boolean 1272 @return: the success of the operation 1273 1274 """ 1275 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer", 1276 "-r", "%d" % kbytes, "--create-device"]) 1277 if result.failed: 1278 logging.error("Can't change syncer rate: %s - %s", 1279 result.fail_reason, result.output) 1280 return not result.failed
1281
1282 - def SetSyncSpeed(self, kbytes):
1283 """Set the speed of the DRBD syncer. 1284 1285 @type kbytes: int 1286 @param kbytes: the speed in kbytes/second 1287 @rtype: boolean 1288 @return: the success of the operation 1289 1290 """ 1291 if self.minor is None: 1292 logging.info("Not attached during SetSyncSpeed") 1293 return False 1294 children_result = super(DRBD8, self).SetSyncSpeed(kbytes) 1295 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1296
1297 - def GetProcStatus(self):
1298 """Return device data from /proc. 1299 1300 """ 1301 if self.minor is None: 1302 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor) 1303 proc_info = self._MassageProcData(self._GetProcData()) 1304 if self.minor not in proc_info: 1305 _ThrowError("drbd%d: can't find myself in /proc", self.minor) 1306 return DRBD8Status(proc_info[self.minor])
1307
1308 - def GetSyncStatus(self):
1309 """Returns the sync status of the device. 1310 1311 1312 If sync_percent is None, it means all is ok 1313 If estimated_time is None, it means we can't estimate 1314 the time needed, otherwise it's the time left in seconds. 1315 1316 1317 We set the is_degraded parameter to True on two conditions: 1318 network not connected or local disk missing. 1319 1320 We compute the ldisk parameter based on whether we have a local 1321 disk or not. 1322 1323 @rtype: tuple 1324 @return: (sync_percent, estimated_time, is_degraded, ldisk) 1325 1326 """ 1327 if self.minor is None and not self.Attach(): 1328 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor) 1329 stats = self.GetProcStatus() 1330 ldisk = not stats.is_disk_uptodate 1331 is_degraded = not stats.is_connected 1332 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1333
1334 - def Open(self, force=False):
1335 """Make the local state primary. 1336 1337 If the 'force' parameter is given, the '-o' option is passed to 1338 drbdsetup. Since this is a potentially dangerous operation, the 1339 force flag should be only given after creation, when it actually 1340 is mandatory. 1341 1342 """ 1343 if self.minor is None and not self.Attach(): 1344 logging.error("DRBD cannot attach to a device during open") 1345 return False 1346 cmd = ["drbdsetup", self.dev_path, "primary"] 1347 if force: 1348 cmd.append("-o") 1349 result = utils.RunCmd(cmd) 1350 if result.failed: 1351 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor, 1352 result.output)
1353
1354 - def Close(self):
1355 """Make the local state secondary. 1356 1357 This will, of course, fail if the device is in use. 1358 1359 """ 1360 if self.minor is None and not self.Attach(): 1361 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor) 1362 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"]) 1363 if result.failed: 1364 _ThrowError("drbd%d: can't switch drbd device to secondary: %s", 1365 self.minor, result.output)
1366
1367 - def DisconnectNet(self):
1368 """Removes network configuration. 1369 1370 This method shutdowns the network side of the device. 1371 1372 The method will wait up to a hardcoded timeout for the device to 1373 go into standalone after the 'disconnect' command before 1374 re-configuring it, as sometimes it takes a while for the 1375 disconnect to actually propagate and thus we might issue a 'net' 1376 command while the device is still connected. If the device will 1377 still be attached to the network and we time out, we raise an 1378 exception. 1379 1380 """ 1381 if self.minor is None: 1382 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor) 1383 1384 if None in (self._lhost, self._lport, self._rhost, self._rport): 1385 _ThrowError("drbd%d: DRBD disk missing network info in" 1386 " DisconnectNet()", self.minor) 1387 1388 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) 1389 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT 1390 sleep_time = 0.100 # we start the retry time at 100 milliseconds 1391 while time.time() < timeout_limit: 1392 status = self.GetProcStatus() 1393 if status.is_standalone: 1394 break 1395 # retry the disconnect, it seems possible that due to a 1396 # well-time disconnect on the peer, my disconnect command might 1397 # be ignored and forgotten 1398 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \ 1399 ever_disconnected 1400 time.sleep(sleep_time) 1401 sleep_time = min(2, sleep_time * 1.5) 1402 1403 if not status.is_standalone: 1404 if ever_disconnected: 1405 msg = ("drbd%d: device did not react to the" 1406 " 'disconnect' command in a timely manner") 1407 else: 1408 msg = "drbd%d: can't shutdown network, even after multiple retries" 1409 _ThrowError(msg, self.minor) 1410 1411 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT 1412 if reconfig_time > 15: # hardcoded alert limit 1413 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds", 1414 self.minor, reconfig_time)
1415
1416 - def AttachNet(self, multimaster):
1417 """Reconnects the network. 1418 1419 This method connects the network side of the device with a 1420 specified multi-master flag. The device needs to be 'Standalone' 1421 but have valid network configuration data. 1422 1423 Args: 1424 - multimaster: init the network in dual-primary mode 1425 1426 """ 1427 if self.minor is None: 1428 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor) 1429 1430 if None in (self._lhost, self._lport, self._rhost, self._rport): 1431 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor) 1432 1433 status = self.GetProcStatus() 1434 1435 if not status.is_standalone: 1436 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor) 1437 1438 self._AssembleNet(self.minor, 1439 (self._lhost, self._lport, self._rhost, self._rport), 1440 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster, 1441 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1442
1443 - def Attach(self):
1444 """Check if our minor is configured. 1445 1446 This doesn't do any device configurations - it only checks if the 1447 minor is in a state different from Unconfigured. 1448 1449 Note that this function will not change the state of the system in 1450 any way (except in case of side-effects caused by reading from 1451 /proc). 1452 1453 """ 1454 used_devs = self.GetUsedDevs() 1455 if self._aminor in used_devs: 1456 minor = self._aminor 1457 else: 1458 minor = None 1459 1460 self._SetFromMinor(minor) 1461 return minor is not None
1462
1463 - def Assemble(self):
1464 """Assemble the drbd. 1465 1466 Method: 1467 - if we have a configured device, we try to ensure that it matches 1468 our config 1469 - if not, we create it from zero 1470 1471 """ 1472 super(DRBD8, self).Assemble() 1473 1474 self.Attach() 1475 if self.minor is None: 1476 # local device completely unconfigured 1477 self._FastAssemble() 1478 else: 1479 # we have to recheck the local and network status and try to fix 1480 # the device 1481 self._SlowAssemble()
1482
1483 - def _SlowAssemble(self):
1484 """Assembles the DRBD device from a (partially) configured device. 1485 1486 In case of partially attached (local device matches but no network 1487 setup), we perform the network attach. If successful, we re-test 1488 the attach if can return success. 1489 1490 """ 1491 net_data = (self._lhost, self._lport, self._rhost, self._rport) 1492 for minor in (self._aminor,): 1493 info = self._GetDevInfo(self._GetShowData(minor)) 1494 match_l = self._MatchesLocal(info) 1495 match_r = self._MatchesNet(info) 1496 1497 if match_l and match_r: 1498 # everything matches 1499 break 1500 1501 if match_l and not match_r and "local_addr" not in info: 1502 # disk matches, but not attached to network, attach and recheck 1503 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL, 1504 hmac=constants.DRBD_HMAC_ALG, secret=self._secret) 1505 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): 1506 break 1507 else: 1508 _ThrowError("drbd%d: network attach successful, but 'drbdsetup" 1509 " show' disagrees", minor) 1510 1511 if match_r and "local_dev" not in info: 1512 # no local disk, but network attached and it matches 1513 self._AssembleLocal(minor, self._children[0].dev_path, 1514 self._children[1].dev_path, self.size) 1515 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): 1516 break 1517 else: 1518 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup" 1519 " show' disagrees", minor) 1520 1521 # this case must be considered only if we actually have local 1522 # storage, i.e. not in diskless mode, because all diskless 1523 # devices are equal from the point of view of local 1524 # configuration 1525 if (match_l and "local_dev" in info and 1526 not match_r and "local_addr" in info): 1527 # strange case - the device network part points to somewhere 1528 # else, even though its local storage is ours; as we own the 1529 # drbd space, we try to disconnect from the remote peer and 1530 # reconnect to our correct one 1531 try: 1532 self._ShutdownNet(minor) 1533 except errors.BlockDeviceError, err: 1534 _ThrowError("drbd%d: device has correct local storage, wrong" 1535 " remote peer and is unable to disconnect in order" 1536 " to attach to the correct peer: %s", minor, str(err)) 1537 # note: _AssembleNet also handles the case when we don't want 1538 # local storage (i.e. one or more of the _[lr](host|port) is 1539 # None) 1540 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL, 1541 hmac=constants.DRBD_HMAC_ALG, secret=self._secret) 1542 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): 1543 break 1544 else: 1545 _ThrowError("drbd%d: network attach successful, but 'drbdsetup" 1546 " show' disagrees", minor) 1547 1548 else: 1549 minor = None 1550 1551 self._SetFromMinor(minor) 1552 if minor is None: 1553 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason", 1554 self._aminor)
1555
1556 - def _FastAssemble(self):
1557 """Assemble the drbd device from zero. 1558 1559 This is run when in Assemble we detect our minor is unused. 1560 1561 """ 1562 minor = self._aminor 1563 if self._children and self._children[0] and self._children[1]: 1564 self._AssembleLocal(minor, self._children[0].dev_path, 1565 self._children[1].dev_path, self.size) 1566 if self._lhost and self._lport and self._rhost and self._rport: 1567 self._AssembleNet(minor, 1568 (self._lhost, self._lport, self._rhost, self._rport), 1569 constants.DRBD_NET_PROTOCOL, 1570 hmac=constants.DRBD_HMAC_ALG, secret=self._secret) 1571 self._SetFromMinor(minor)
1572 1573 @classmethod
1574 - def _ShutdownLocal(cls, minor):
1575 """Detach from the local device. 1576 1577 I/Os will continue to be served from the remote device. If we 1578 don't have a remote device, this operation will fail. 1579 1580 """ 1581 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"]) 1582 if result.failed: 1583 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1584 1585 @classmethod
1586 - def _ShutdownNet(cls, minor):
1587 """Disconnect from the remote peer. 1588 1589 This fails if we don't have a local device. 1590 1591 """ 1592 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"]) 1593 if result.failed: 1594 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1595 1596 @classmethod
1597 - def _ShutdownAll(cls, minor):
1598 """Deactivate the device. 1599 1600 This will, of course, fail if the device is in use. 1601 1602 """ 1603 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"]) 1604 if result.failed: 1605 _ThrowError("drbd%d: can't shutdown drbd device: %s", 1606 minor, result.output)
1607
1608 - def Shutdown(self):
1609 """Shutdown the DRBD device. 1610 1611 """ 1612 if self.minor is None and not self.Attach(): 1613 logging.info("drbd%d: not attached during Shutdown()", self._aminor) 1614 return 1615 minor = self.minor 1616 self.minor = None 1617 self.dev_path = None 1618 self._ShutdownAll(minor)
1619
1620 - def Remove(self):
1621 """Stub remove for DRBD devices. 1622 1623 """ 1624 self.Shutdown()
1625 1626 @classmethod
1627 - def Create(cls, unique_id, children, size):
1628 """Create a new DRBD8 device. 1629 1630 Since DRBD devices are not created per se, just assembled, this 1631 function only initializes the metadata. 1632 1633 """ 1634 if len(children) != 2: 1635 raise errors.ProgrammerError("Invalid setup for the drbd device") 1636 # check that the minor is unused 1637 aminor = unique_id[4] 1638 proc_info = cls._MassageProcData(cls._GetProcData()) 1639 if aminor in proc_info: 1640 status = DRBD8Status(proc_info[aminor]) 1641 in_use = status.is_in_use 1642 else: 1643 in_use = False 1644 if in_use: 1645 _ThrowError("drbd%d: minor is already in use at Create() time", aminor) 1646 meta = children[1] 1647 meta.Assemble() 1648 if not meta.Attach(): 1649 _ThrowError("drbd%d: can't attach to meta device '%s'", 1650 aminor, meta) 1651 cls._CheckMetaSize(meta.dev_path) 1652 cls._InitMeta(aminor, meta.dev_path) 1653 return cls(unique_id, children, size)
1654
1655 - def Grow(self, amount):
1656 """Resize the DRBD device and its backing storage. 1657 1658 """ 1659 if self.minor is None: 1660 _ThrowError("drbd%d: Grow called while not attached", self._aminor) 1661 if len(self._children) != 2 or None in self._children: 1662 _ThrowError("drbd%d: cannot grow diskless device", self.minor) 1663 self._children[0].Grow(amount) 1664 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s", 1665 "%dm" % (self.size + amount)]) 1666 if result.failed: 1667 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1668
1669 1670 -class FileStorage(BlockDev):
1671 """File device. 1672 1673 This class represents the a file storage backend device. 1674 1675 The unique_id for the file device is a (file_driver, file_path) tuple. 1676 1677 """
1678 - def __init__(self, unique_id, children, size):
1679 """Initalizes a file device backend. 1680 1681 """ 1682 if children: 1683 raise errors.BlockDeviceError("Invalid setup for file device") 1684 super(FileStorage, self).__init__(unique_id, children, size) 1685 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 1686 raise ValueError("Invalid configuration data %s" % str(unique_id)) 1687 self.driver = unique_id[0] 1688 self.dev_path = unique_id[1] 1689 self.Attach()
1690
1691 - def Assemble(self):
1692 """Assemble the device. 1693 1694 Checks whether the file device exists, raises BlockDeviceError otherwise. 1695 1696 """ 1697 if not os.path.exists(self.dev_path): 1698 _ThrowError("File device '%s' does not exist" % self.dev_path)
1699
1700 - def Shutdown(self):
1701 """Shutdown the device. 1702 1703 This is a no-op for the file type, as we don't deactivate 1704 the file on shutdown. 1705 1706 """ 1707 pass
1708
1709 - def Open(self, force=False):
1710 """Make the device ready for I/O. 1711 1712 This is a no-op for the file type. 1713 1714 """ 1715 pass
1716
1717 - def Close(self):
1718 """Notifies that the device will no longer be used for I/O. 1719 1720 This is a no-op for the file type. 1721 1722 """ 1723 pass
1724
1725 - def Remove(self):
1726 """Remove the file backing the block device. 1727 1728 @rtype: boolean 1729 @return: True if the removal was successful 1730 1731 """ 1732 try: 1733 os.remove(self.dev_path) 1734 except OSError, err: 1735 if err.errno != errno.ENOENT: 1736 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1737
1738 - def Attach(self):
1739 """Attach to an existing file. 1740 1741 Check if this file already exists. 1742 1743 @rtype: boolean 1744 @return: True if file exists 1745 1746 """ 1747 self.attached = os.path.exists(self.dev_path) 1748 return self.attached
1749
1750 - def GetActualSize(self):
1751 """Return the actual disk size. 1752 1753 @note: the device needs to be active when this is called 1754 1755 """ 1756 assert self.attached, "BlockDevice not attached in GetActualSize()" 1757 try: 1758 st = os.stat(self.dev_path) 1759 return st.st_size 1760 except OSError, err: 1761 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1762 1763 @classmethod
1764 - def Create(cls, unique_id, children, size):
1765 """Create a new file. 1766 1767 @param size: the size of file in MiB 1768 1769 @rtype: L{bdev.FileStorage} 1770 @return: an instance of FileStorage 1771 1772 """ 1773 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 1774 raise ValueError("Invalid configuration data %s" % str(unique_id)) 1775 dev_path = unique_id[1] 1776 if os.path.exists(dev_path): 1777 _ThrowError("File already existing: %s", dev_path) 1778 try: 1779 f = open(dev_path, 'w') 1780 f.truncate(size * 1024 * 1024) 1781 f.close() 1782 except IOError, err: 1783 _ThrowError("Error in file creation: %", str(err)) 1784 1785 return FileStorage(unique_id, children, size)
1786 1787 1788 DEV_MAP = { 1789 constants.LD_LV: LogicalVolume, 1790 constants.LD_DRBD8: DRBD8, 1791 constants.LD_FILE: FileStorage, 1792 }
1793 1794 1795 -def FindDevice(dev_type, unique_id, children, size):
1796 """Search for an existing, assembled device. 1797 1798 This will succeed only if the device exists and is assembled, but it 1799 does not do any actions in order to activate the device. 1800 1801 """ 1802 if dev_type not in DEV_MAP: 1803 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type) 1804 device = DEV_MAP[dev_type](unique_id, children, size) 1805 if not device.attached: 1806 return None 1807 return device
1808
1809 1810 -def Assemble(dev_type, unique_id, children, size):
1811 """Try to attach or assemble an existing device. 1812 1813 This will attach to assemble the device, as needed, to bring it 1814 fully up. It must be safe to run on already-assembled devices. 1815 1816 """ 1817 if dev_type not in DEV_MAP: 1818 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type) 1819 device = DEV_MAP[dev_type](unique_id, children, size) 1820 device.Assemble() 1821 return device
1822
1823 1824 -def Create(dev_type, unique_id, children, size):
1825 """Create a device. 1826 1827 """ 1828 if dev_type not in DEV_MAP: 1829 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type) 1830 device = DEV_MAP[dev_type].Create(unique_id, children, size) 1831 return device
1832