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

Source Code for Module ganeti.bdev

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