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

Source Code for Module ganeti.bdev

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