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