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