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