4 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
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.
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.
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
22 """Block device abstraction"""
29 import pyparsing as pyp
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 from ganeti import pathutils
42 # Size of reads in _CanReadDevice
43 _DEVICE_READ_SIZE = 128 * 1024
46 def _IgnoreError(fn, *args, **kwargs):
47 """Executes the given function, ignoring BlockDeviceErrors.
49 This is used in order to simplify the execution of cleanup or
53 @return: True when fn didn't raise an exception, False otherwise
59 except errors.BlockDeviceError, err:
60 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
64 def _ThrowError(msg, *args):
65 """Log an error to the node daemon and the raise an exception.
68 @param msg: the text of the exception
69 @raise errors.BlockDeviceError
75 raise errors.BlockDeviceError(msg)
78 def _CanReadDevice(path):
79 """Check if we can read from the given device.
81 This tries to read the first 128k of the device.
85 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
87 except EnvironmentError:
88 logging.warning("Can't read from device %s", path, exc_info=True)
92 def _CheckFileStoragePath(path, allowed):
93 """Checks if a path is in a list of allowed paths for file storage.
96 @param path: Path to check
98 @param allowed: List of allowed paths
99 @raise errors.FileStoragePathError: If the path is not allowed
102 if not os.path.isabs(path):
103 raise errors.FileStoragePathError("File storage path must be absolute,"
107 if not os.path.isabs(i):
108 logging.info("Ignoring relative path '%s' for file storage", i)
111 if utils.IsBelowDir(i, path):
114 raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
118 def LoadAllowedFileStoragePaths(filename):
119 """Loads file containing allowed file storage paths.
122 @return: List of allowed paths (can be an empty list)
126 contents = utils.ReadFile(filename)
127 except EnvironmentError:
130 return utils.FilterEmptyLinesAndComments(contents)
133 def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
134 """Checks if a path is allowed for file storage.
137 @param path: Path to check
138 @raise errors.FileStoragePathError: If the path is not allowed
141 _CheckFileStoragePath(path, LoadAllowedFileStoragePaths(_filename))
144 class BlockDev(object):
145 """Block device abstract class.
147 A block device can be in the following states:
148 - not existing on the system, and by `Create()` it goes into:
149 - existing but not setup/not active, and by `Assemble()` goes into:
150 - active read-write and by `Open()` it goes into
151 - online (=used, or ready for use)
153 A device can also be online but read-only, however we are not using
154 the readonly state (LV has it, if needed in the future) and we are
155 usually looking at this like at a stack, so it's easier to
156 conceptualise the transition from not-existing to online and back
159 The many different states of the device are due to the fact that we
160 need to cover many device types:
161 - logical volumes are created, lvchange -a y $lv, and used
162 - drbd devices are attached to a local disk/remote peer and made primary
164 A block device is identified by three items:
165 - the /dev path of the device (dynamic)
166 - a unique ID of the device (static)
167 - it's major/minor pair (dynamic)
169 Not all devices implement both the first two as distinct items. LVM
170 logical volumes have their unique ID (the pair volume group, logical
171 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
172 the /dev path is again dynamic and the unique id is the pair (host1,
173 dev1), (host2, dev2).
175 You can get to a device in two ways:
176 - creating the (real) device, which returns you
177 an attached instance (lvcreate)
178 - attaching of a python instance to an existing (real) device
180 The second point, the attachement to a device, is different
181 depending on whether the device is assembled or not. At init() time,
182 we search for a device with the same unique_id as us. If found,
183 good. It also means that the device is already assembled. If not,
184 after assembly we'll have our correct major/minor.
187 def __init__(self, unique_id, children, size, params):
188 self._children = children
190 self.unique_id = unique_id
193 self.attached = False
198 """Assemble the device from its components.
200 Implementations of this method by child classes must ensure that:
201 - after the device has been assembled, it knows its major/minor
202 numbers; this allows other devices (usually parents) to probe
203 correctly for their children
204 - calling this method on an existing, in-use device is safe
205 - if the device is already configured (and in an OK state),
206 this method is idempotent
212 """Find a device which matches our config and attach to it.
215 raise NotImplementedError
218 """Notifies that the device will no longer be used for I/O.
221 raise NotImplementedError
224 def Create(cls, unique_id, children, size, params):
225 """Create the device.
227 If the device cannot be created, it will return None
228 instead. Error messages go to the logging system.
230 Note that for some devices, the unique_id is used, and for other,
231 the children. The idea is that these two, taken together, are
232 enough for both creation and assembly (later).
235 raise NotImplementedError
238 """Remove this device.
240 This makes sense only for some of the device types: LV and file
241 storage. Also note that if the device can't attach, the removal
245 raise NotImplementedError
247 def Rename(self, new_id):
248 """Rename this device.
250 This may or may not make sense for a given device type.
253 raise NotImplementedError
255 def Open(self, force=False):
256 """Make the device ready for use.
258 This makes the device ready for I/O. For now, just the DRBD
261 The force parameter signifies that if the device has any kind of
262 --force thing, it should be used, we know what we are doing.
265 raise NotImplementedError
268 """Shut down the device, freeing its children.
270 This undoes the `Assemble()` work, except for the child
271 assembling; as such, the children on the device are still
272 assembled after this call.
275 raise NotImplementedError
277 def SetSyncParams(self, params):
278 """Adjust the synchronization parameters of the mirror.
280 In case this is not a mirroring device, this is no-op.
282 @param params: dictionary of LD level disk parameters related to the
285 @return: a list of error messages, emitted both by the current node and by
286 children. An empty list means no errors.
291 for child in self._children:
292 result.extend(child.SetSyncParams(params))
295 def PauseResumeSync(self, pause):
296 """Pause/Resume the sync of the mirror.
298 In case this is not a mirroring device, this is no-op.
300 @param pause: Whether to pause or resume
305 for child in self._children:
306 result = result and child.PauseResumeSync(pause)
309 def GetSyncStatus(self):
310 """Returns the sync status of the device.
312 If this device is a mirroring device, this function returns the
313 status of the mirror.
315 If sync_percent is None, it means the device is not syncing.
317 If estimated_time is None, it means we can't estimate
318 the time needed, otherwise it's the time left in seconds.
320 If is_degraded is True, it means the device is missing
321 redundancy. This is usually a sign that something went wrong in
322 the device setup, if sync_percent is None.
324 The ldisk parameter represents the degradation of the local
325 data. This is only valid for some devices, the rest will always
326 return False (not degraded).
328 @rtype: objects.BlockDevStatus
331 return objects.BlockDevStatus(dev_path=self.dev_path,
337 ldisk_status=constants.LDS_OKAY)
339 def CombinedSyncStatus(self):
340 """Calculate the mirror status recursively for our children.
342 The return value is the same as for `GetSyncStatus()` except the
343 minimum percent and maximum time are calculated across our
346 @rtype: objects.BlockDevStatus
349 status = self.GetSyncStatus()
351 min_percent = status.sync_percent
352 max_time = status.estimated_time
353 is_degraded = status.is_degraded
354 ldisk_status = status.ldisk_status
357 for child in self._children:
358 child_status = child.GetSyncStatus()
360 if min_percent is None:
361 min_percent = child_status.sync_percent
362 elif child_status.sync_percent is not None:
363 min_percent = min(min_percent, child_status.sync_percent)
366 max_time = child_status.estimated_time
367 elif child_status.estimated_time is not None:
368 max_time = max(max_time, child_status.estimated_time)
370 is_degraded = is_degraded or child_status.is_degraded
372 if ldisk_status is None:
373 ldisk_status = child_status.ldisk_status
374 elif child_status.ldisk_status is not None:
375 ldisk_status = max(ldisk_status, child_status.ldisk_status)
377 return objects.BlockDevStatus(dev_path=self.dev_path,
380 sync_percent=min_percent,
381 estimated_time=max_time,
382 is_degraded=is_degraded,
383 ldisk_status=ldisk_status)
385 def SetInfo(self, text):
386 """Update metadata with info text.
388 Only supported for some device types.
391 for child in self._children:
394 def Grow(self, amount, dryrun, backingstore):
395 """Grow the block device.
397 @type amount: integer
398 @param amount: the amount (in mebibytes) to grow with
399 @type dryrun: boolean
400 @param dryrun: whether to execute the operation in simulation mode
401 only, without actually increasing the size
402 @param backingstore: whether to execute the operation on backing storage
403 only, or on "logical" storage only; e.g. DRBD is logical storage,
404 whereas LVM, file, RBD are backing storage
407 raise NotImplementedError
409 def GetActualSize(self):
410 """Return the actual disk size.
412 @note: the device needs to be active when this is called
415 assert self.attached, "BlockDevice not attached in GetActualSize()"
416 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
418 _ThrowError("blockdev failed (%s): %s",
419 result.fail_reason, result.output)
421 sz = int(result.output.strip())
422 except (ValueError, TypeError), err:
423 _ThrowError("Failed to parse blockdev output: %s", str(err))
427 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
428 (self.__class__, self.unique_id, self._children,
429 self.major, self.minor, self.dev_path))
432 class LogicalVolume(BlockDev):
433 """Logical Volume block device.
436 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
437 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
438 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
440 def __init__(self, unique_id, children, size, params):
441 """Attaches to a LV device.
443 The unique_id is a tuple (vg_name, lv_name)
446 super(LogicalVolume, self).__init__(unique_id, children, size, params)
447 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
448 raise ValueError("Invalid configuration data %s" % str(unique_id))
449 self._vg_name, self._lv_name = unique_id
450 self._ValidateName(self._vg_name)
451 self._ValidateName(self._lv_name)
452 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
453 self._degraded = True
454 self.major = self.minor = self.pe_size = self.stripe_count = None
458 def Create(cls, unique_id, children, size, params):
459 """Create a new logical volume.
462 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
463 raise errors.ProgrammerError("Invalid configuration data %s" %
465 vg_name, lv_name = unique_id
466 cls._ValidateName(vg_name)
467 cls._ValidateName(lv_name)
468 pvs_info = cls.GetPVInfo([vg_name])
470 _ThrowError("Can't compute PV info for vg %s", vg_name)
474 pvlist = [pv[1] for pv in pvs_info]
475 if compat.any(":" in v for v in pvlist):
476 _ThrowError("Some of your PVs have the invalid character ':' in their"
477 " name, this is not supported - please filter them out"
478 " in lvm.conf using either 'filter' or 'preferred_names'")
479 free_size = sum([pv[0] for pv in pvs_info])
480 current_pvs = len(pvlist)
481 desired_stripes = params[constants.LDP_STRIPES]
482 stripes = min(current_pvs, desired_stripes)
483 if stripes < desired_stripes:
484 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
485 " available.", desired_stripes, vg_name, current_pvs)
487 # The size constraint should have been checked from the master before
488 # calling the create function.
490 _ThrowError("Not enough free space: required %s,"
491 " available %s", size, free_size)
492 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
493 # If the free space is not well distributed, we won't be able to
494 # create an optimally-striped volume; in that case, we want to try
495 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
497 for stripes_arg in range(stripes, 0, -1):
498 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
499 if not result.failed:
502 _ThrowError("LV create failed (%s): %s",
503 result.fail_reason, result.output)
504 return LogicalVolume(unique_id, children, size, params)
507 def _GetVolumeInfo(lvm_cmd, fields):
508 """Returns LVM Volumen infos using lvm_cmd
510 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
511 @param fields: Fields to return
512 @return: A list of dicts each with the parsed fields
516 raise errors.ProgrammerError("No fields specified")
519 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
520 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
522 result = utils.RunCmd(cmd)
524 raise errors.CommandError("Can't get the volume information: %s - %s" %
525 (result.fail_reason, result.output))
528 for line in result.stdout.splitlines():
529 splitted_fields = line.strip().split(sep)
531 if len(fields) != len(splitted_fields):
532 raise errors.CommandError("Can't parse %s output: line '%s'" %
535 data.append(splitted_fields)
540 def GetPVInfo(cls, vg_names, filter_allocatable=True):
541 """Get the free space info for PVs in a volume group.
543 @param vg_names: list of volume group names, if empty all will be returned
544 @param filter_allocatable: whether to skip over unallocatable PVs
547 @return: list of tuples (free_space, name) with free_space in mebibytes
551 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
553 except errors.GenericError, err:
554 logging.error("Can't get PV information: %s", err)
558 for pv_name, vg_name, pv_free, pv_attr in info:
559 # (possibly) skip over pvs which are not allocatable
560 if filter_allocatable and pv_attr[0] != "a":
562 # (possibly) skip over pvs which are not in the right volume group(s)
563 if vg_names and vg_name not in vg_names:
565 data.append((float(pv_free), pv_name, vg_name))
570 def GetVGInfo(cls, vg_names, filter_readonly=True):
571 """Get the free space info for specific VGs.
573 @param vg_names: list of volume group names, if empty all will be returned
574 @param filter_readonly: whether to skip over readonly VGs
577 @return: list of tuples (free_space, total_size, name) with free_space in
582 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
584 except errors.GenericError, err:
585 logging.error("Can't get VG information: %s", err)
589 for vg_name, vg_free, vg_attr, vg_size in info:
590 # (possibly) skip over vgs which are not writable
591 if filter_readonly and vg_attr[0] == "r":
593 # (possibly) skip over vgs which are not in the right volume group(s)
594 if vg_names and vg_name not in vg_names:
596 data.append((float(vg_free), float(vg_size), vg_name))
601 def _ValidateName(cls, name):
602 """Validates that a given name is valid as VG or LV name.
604 The list of valid characters and restricted names is taken out of
605 the lvm(8) manpage, with the simplification that we enforce both
606 VG and LV restrictions on the names.
609 if (not cls._VALID_NAME_RE.match(name) or
610 name in cls._INVALID_NAMES or
611 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
612 _ThrowError("Invalid LVM name '%s'", name)
615 """Remove this logical volume.
618 if not self.minor and not self.Attach():
619 # the LV does not exist
621 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
622 (self._vg_name, self._lv_name)])
624 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
626 def Rename(self, new_id):
627 """Rename this logical volume.
630 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
631 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
632 new_vg, new_name = new_id
633 if new_vg != self._vg_name:
634 raise errors.ProgrammerError("Can't move a logical volume across"
635 " volume groups (from %s to to %s)" %
636 (self._vg_name, new_vg))
637 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
639 _ThrowError("Failed to rename the logical volume: %s", result.output)
640 self._lv_name = new_name
641 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
644 """Attach to an existing LV.
646 This method will try to see if an existing and active LV exists
647 which matches our name. If so, its major/minor will be
651 self.attached = False
652 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
653 "--units=m", "--nosuffix",
654 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
655 "vg_extent_size,stripes", self.dev_path])
657 logging.error("Can't find LV %s: %s, %s",
658 self.dev_path, result.fail_reason, result.output)
660 # the output can (and will) have multiple lines for multi-segment
661 # LVs, as the 'stripes' parameter is a segment one, so we take
662 # only the last entry, which is the one we're interested in; note
663 # that with LVM2 anyway the 'stripes' value must be constant
664 # across segments, so this is a no-op actually
665 out = result.stdout.splitlines()
666 if not out: # totally empty result? splitlines() returns at least
667 # one line for any non-empty string
668 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
670 out = out[-1].strip().rstrip(",")
673 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
676 status, major, minor, pe_size, stripes = out
678 logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
684 except (TypeError, ValueError), err:
685 logging.error("lvs major/minor cannot be parsed: %s", str(err))
688 pe_size = int(float(pe_size))
689 except (TypeError, ValueError), err:
690 logging.error("Can't parse vg extent size: %s", err)
694 stripes = int(stripes)
695 except (TypeError, ValueError), err:
696 logging.error("Can't parse the number of stripes: %s", err)
701 self.pe_size = pe_size
702 self.stripe_count = stripes
703 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
709 """Assemble the device.
711 We always run `lvchange -ay` on the LV to ensure it's active before
712 use, as there were cases when xenvg was not active after boot
713 (also possibly after disk issues).
716 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
718 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
721 """Shutdown the device.
723 This is a no-op for the LV device type, as we don't deactivate the
729 def GetSyncStatus(self):
730 """Returns the sync status of the device.
732 If this device is a mirroring device, this function returns the
733 status of the mirror.
735 For logical volumes, sync_percent and estimated_time are always
736 None (no recovery in progress, as we don't handle the mirrored LV
737 case). The is_degraded parameter is the inverse of the ldisk
740 For the ldisk parameter, we check if the logical volume has the
741 'virtual' type, which means it's not backed by existing storage
742 anymore (read from it return I/O error). This happens after a
743 physical disk failure and subsequent 'vgreduce --removemissing' on
746 The status was already read in Attach, so we just return it.
748 @rtype: objects.BlockDevStatus
752 ldisk_status = constants.LDS_FAULTY
754 ldisk_status = constants.LDS_OKAY
756 return objects.BlockDevStatus(dev_path=self.dev_path,
761 is_degraded=self._degraded,
762 ldisk_status=ldisk_status)
764 def Open(self, force=False):
765 """Make the device ready for I/O.
767 This is a no-op for the LV device type.
773 """Notifies that the device will no longer be used for I/O.
775 This is a no-op for the LV device type.
780 def Snapshot(self, size):
781 """Create a snapshot copy of an lvm block device.
783 @returns: tuple (vg, lv)
786 snap_name = self._lv_name + ".snap"
788 # remove existing snapshot if found
789 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
790 _IgnoreError(snap.Remove)
792 vg_info = self.GetVGInfo([self._vg_name])
794 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
795 free_size, _, _ = vg_info[0]
797 _ThrowError("Not enough free space: required %s,"
798 " available %s", size, free_size)
800 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
801 "-n%s" % snap_name, self.dev_path])
803 _ThrowError("command: %s error: %s - %s",
804 result.cmd, result.fail_reason, result.output)
806 return (self._vg_name, snap_name)
808 def SetInfo(self, text):
809 """Update metadata with info text.
812 BlockDev.SetInfo(self, text)
814 # Replace invalid characters
815 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
816 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
818 # Only up to 128 characters are allowed
821 result = utils.RunCmd(["lvchange", "--addtag", text,
824 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
827 def Grow(self, amount, dryrun, backingstore):
828 """Grow the logical volume.
833 if self.pe_size is None or self.stripe_count is None:
834 if not self.Attach():
835 _ThrowError("Can't attach to LV during Grow()")
836 full_stripe_size = self.pe_size * self.stripe_count
837 rest = amount % full_stripe_size
839 amount += full_stripe_size - rest
840 cmd = ["lvextend", "-L", "+%dm" % amount]
843 # we try multiple algorithms since the 'best' ones might not have
844 # space available in the right place, but later ones might (since
845 # they have less constraints); also note that only recent LVM
847 for alloc_policy in "contiguous", "cling", "normal":
848 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
849 if not result.failed:
851 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
854 class DRBD8Status(object):
855 """A DRBD status representation class.
857 Note that this doesn't support unconfigured devices (cs:Unconfigured).
860 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
861 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
862 "\s+ds:([^/]+)/(\S+)\s+.*$")
863 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
864 # Due to a bug in drbd in the kernel, introduced in
865 # commit 4b0715f096 (still unfixed as of 2011-08-22)
867 "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
869 CS_UNCONFIGURED = "Unconfigured"
870 CS_STANDALONE = "StandAlone"
871 CS_WFCONNECTION = "WFConnection"
872 CS_WFREPORTPARAMS = "WFReportParams"
873 CS_CONNECTED = "Connected"
874 CS_STARTINGSYNCS = "StartingSyncS"
875 CS_STARTINGSYNCT = "StartingSyncT"
876 CS_WFBITMAPS = "WFBitMapS"
877 CS_WFBITMAPT = "WFBitMapT"
878 CS_WFSYNCUUID = "WFSyncUUID"
879 CS_SYNCSOURCE = "SyncSource"
880 CS_SYNCTARGET = "SyncTarget"
881 CS_PAUSEDSYNCS = "PausedSyncS"
882 CS_PAUSEDSYNCT = "PausedSyncT"
883 CSET_SYNC = frozenset([
896 DS_DISKLESS = "Diskless"
897 DS_ATTACHING = "Attaching" # transient state
898 DS_FAILED = "Failed" # transient state, next: diskless
899 DS_NEGOTIATING = "Negotiating" # transient state
900 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
901 DS_OUTDATED = "Outdated"
902 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
903 DS_CONSISTENT = "Consistent"
904 DS_UPTODATE = "UpToDate" # normal state
906 RO_PRIMARY = "Primary"
907 RO_SECONDARY = "Secondary"
908 RO_UNKNOWN = "Unknown"
910 def __init__(self, procline):
911 u = self.UNCONF_RE.match(procline)
913 self.cstatus = self.CS_UNCONFIGURED
914 self.lrole = self.rrole = self.ldisk = self.rdisk = None
916 m = self.LINE_RE.match(procline)
918 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
919 self.cstatus = m.group(1)
920 self.lrole = m.group(2)
921 self.rrole = m.group(3)
922 self.ldisk = m.group(4)
923 self.rdisk = m.group(5)
925 # end reading of data from the LINE_RE or UNCONF_RE
927 self.is_standalone = self.cstatus == self.CS_STANDALONE
928 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
929 self.is_connected = self.cstatus == self.CS_CONNECTED
930 self.is_primary = self.lrole == self.RO_PRIMARY
931 self.is_secondary = self.lrole == self.RO_SECONDARY
932 self.peer_primary = self.rrole == self.RO_PRIMARY
933 self.peer_secondary = self.rrole == self.RO_SECONDARY
934 self.both_primary = self.is_primary and self.peer_primary
935 self.both_secondary = self.is_secondary and self.peer_secondary
937 self.is_diskless = self.ldisk == self.DS_DISKLESS
938 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
940 self.is_in_resync = self.cstatus in self.CSET_SYNC
941 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
943 m = self.SYNC_RE.match(procline)
945 self.sync_percent = float(m.group(1))
946 hours = int(m.group(2))
947 minutes = int(m.group(3))
948 seconds = int(m.group(4))
949 self.est_time = hours * 3600 + minutes * 60 + seconds
951 # we have (in this if branch) no percent information, but if
952 # we're resyncing we need to 'fake' a sync percent information,
953 # as this is how cmdlib determines if it makes sense to wait for
955 if self.is_in_resync:
956 self.sync_percent = 0
958 self.sync_percent = None
962 class BaseDRBD(BlockDev): # pylint: disable=W0223
965 This class contains a few bits of common functionality between the
966 0.7 and 8.x versions of DRBD.
969 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
970 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
971 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
972 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
975 _ST_UNCONFIGURED = "Unconfigured"
976 _ST_WFCONNECTION = "WFConnection"
977 _ST_CONNECTED = "Connected"
979 _STATUS_FILE = "/proc/drbd"
980 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
983 def _GetProcData(filename=_STATUS_FILE):
984 """Return data from /proc/drbd.
988 data = utils.ReadFile(filename).splitlines()
989 except EnvironmentError, err:
990 if err.errno == errno.ENOENT:
991 _ThrowError("The file %s cannot be opened, check if the module"
992 " is loaded (%s)", filename, str(err))
994 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
996 _ThrowError("Can't read any data from %s", filename)
1000 def _MassageProcData(cls, data):
1001 """Transform the output of _GetProdData into a nicer form.
1003 @return: a dictionary of minor: joined lines from /proc/drbd
1008 old_minor = old_line = None
1010 if not line: # completely empty lines, as can be returned by drbd8.0+
1012 lresult = cls._VALID_LINE_RE.match(line)
1013 if lresult is not None:
1014 if old_minor is not None:
1015 results[old_minor] = old_line
1016 old_minor = int(lresult.group(1))
1019 if old_minor is not None:
1020 old_line += " " + line.strip()
1022 if old_minor is not None:
1023 results[old_minor] = old_line
1027 def _GetVersion(cls, proc_data):
1028 """Return the DRBD version.
1030 This will return a dict with keys:
1036 - proto2 (only on drbd > 8.2.X)
1039 first_line = proc_data[0].strip()
1040 version = cls._VERSION_RE.match(first_line)
1042 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1045 values = version.groups()
1047 "k_major": int(values[0]),
1048 "k_minor": int(values[1]),
1049 "k_point": int(values[2]),
1050 "api": int(values[3]),
1051 "proto": int(values[4]),
1053 if values[5] is not None:
1054 retval["proto2"] = values[5]
1059 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
1060 """Returns DRBD usermode_helper currently set.
1064 helper = utils.ReadFile(filename).splitlines()[0]
1065 except EnvironmentError, err:
1066 if err.errno == errno.ENOENT:
1067 _ThrowError("The file %s cannot be opened, check if the module"
1068 " is loaded (%s)", filename, str(err))
1070 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1072 _ThrowError("Can't read any data from %s", filename)
1076 def _DevPath(minor):
1077 """Return the path to a drbd device for a given minor.
1080 return "/dev/drbd%d" % minor
1083 def GetUsedDevs(cls):
1084 """Compute the list of used DRBD devices.
1087 data = cls._GetProcData()
1091 match = cls._VALID_LINE_RE.match(line)
1094 minor = int(match.group(1))
1095 state = match.group(2)
1096 if state == cls._ST_UNCONFIGURED:
1098 used_devs[minor] = state, line
1102 def _SetFromMinor(self, minor):
1103 """Set our parameters based on the given minor.
1105 This sets our minor variable and our dev_path.
1109 self.minor = self.dev_path = None
1110 self.attached = False
1113 self.dev_path = self._DevPath(minor)
1114 self.attached = True
1117 def _CheckMetaSize(meta_device):
1118 """Check if the given meta device looks like a valid one.
1120 This currently only checks the size, which must be around
1124 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1126 _ThrowError("Failed to get device size: %s - %s",
1127 result.fail_reason, result.output)
1129 sectors = int(result.stdout)
1130 except (TypeError, ValueError):
1131 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1132 num_bytes = sectors * 512
1133 if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1134 _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1135 # the maximum *valid* size of the meta device when living on top
1136 # of LVM is hard to compute: it depends on the number of stripes
1137 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1138 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1139 # size meta device; as such, we restrict it to 1GB (a little bit
1140 # too generous, but making assumptions about PE size is hard)
1141 if num_bytes > 1024 * 1024 * 1024:
1142 _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1144 def Rename(self, new_id):
1147 This is not supported for drbd devices.
1150 raise errors.ProgrammerError("Can't rename a drbd device")
1153 class DRBD8(BaseDRBD):
1154 """DRBD v8.x block device.
1156 This implements the local host part of the DRBD device, i.e. it
1157 doesn't do anything to the supposed peer. If you need a fully
1158 connected DRBD pair, you need to use this class on both hosts.
1160 The unique_id for the drbd device is a (local_ip, local_port,
1161 remote_ip, remote_port, local_minor, secret) tuple, and it must have
1162 two children: the data device and the meta_device. The meta device
1163 is checked for valid size and is zeroed on create.
1170 _NET_RECONFIG_TIMEOUT = 60
1172 # command line options for barriers
1173 _DISABLE_DISK_OPTION = "--no-disk-barrier" # -a
1174 _DISABLE_DRAIN_OPTION = "--no-disk-drain" # -D
1175 _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
1176 _DISABLE_META_FLUSH_OPTION = "--no-md-flushes" # -m
1178 def __init__(self, unique_id, children, size, params):
1179 if children and children.count(None) > 0:
1181 if len(children) not in (0, 2):
1182 raise ValueError("Invalid configuration data %s" % str(children))
1183 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1184 raise ValueError("Invalid configuration data %s" % str(unique_id))
1185 (self._lhost, self._lport,
1186 self._rhost, self._rport,
1187 self._aminor, self._secret) = unique_id
1189 if not _CanReadDevice(children[1].dev_path):
1190 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1192 super(DRBD8, self).__init__(unique_id, children, size, params)
1193 self.major = self._DRBD_MAJOR
1194 version = self._GetVersion(self._GetProcData())
1195 if version["k_major"] != 8:
1196 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1197 " usage: kernel is %s.%s, ganeti wants 8.x",
1198 version["k_major"], version["k_minor"])
1200 if (self._lhost is not None and self._lhost == self._rhost and
1201 self._lport == self._rport):
1202 raise ValueError("Invalid configuration data, same local/remote %s" %
1207 def _InitMeta(cls, minor, dev_path):
1208 """Initialize a meta device.
1210 This will not work if the given minor is in use.
1213 # Zero the metadata first, in order to make sure drbdmeta doesn't
1214 # try to auto-detect existing filesystems or similar (see
1215 # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1216 # care about the first 128MB of data in the device, even though it
1218 result = utils.RunCmd([constants.DD_CMD,
1219 "if=/dev/zero", "of=%s" % dev_path,
1220 "bs=1048576", "count=128", "oflag=direct"])
1222 _ThrowError("Can't wipe the meta device: %s", result.output)
1224 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1225 "v08", dev_path, "0", "create-md"])
1227 _ThrowError("Can't initialize meta device: %s", result.output)
1230 def _FindUnusedMinor(cls):
1231 """Find an unused DRBD device.
1233 This is specific to 8.x as the minors are allocated dynamically,
1234 so non-existing numbers up to a max minor count are actually free.
1237 data = cls._GetProcData()
1241 match = cls._UNUSED_LINE_RE.match(line)
1243 return int(match.group(1))
1244 match = cls._VALID_LINE_RE.match(line)
1246 minor = int(match.group(1))
1247 highest = max(highest, minor)
1248 if highest is None: # there are no minors in use at all
1250 if highest >= cls._MAX_MINORS:
1251 logging.error("Error: no free drbd minors!")
1252 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1256 def _GetShowParser(cls):
1257 """Return a parser for `drbd show` output.
1259 This will either create or return an already-created parser for the
1260 output of the command `drbd show`.
1263 if cls._PARSE_SHOW is not None:
1264 return cls._PARSE_SHOW
1267 lbrace = pyp.Literal("{").suppress()
1268 rbrace = pyp.Literal("}").suppress()
1269 lbracket = pyp.Literal("[").suppress()
1270 rbracket = pyp.Literal("]").suppress()
1271 semi = pyp.Literal(";").suppress()
1272 colon = pyp.Literal(":").suppress()
1273 # this also converts the value to an int
1274 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1276 comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1277 defa = pyp.Literal("_is_default").suppress()
1278 dbl_quote = pyp.Literal('"').suppress()
1280 keyword = pyp.Word(pyp.alphanums + "-")
1283 value = pyp.Word(pyp.alphanums + "_-/.:")
1284 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1285 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1286 pyp.Word(pyp.nums + ".") + colon + number)
1287 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1288 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1289 pyp.Optional(rbracket) + colon + number)
1290 # meta device, extended syntax
1291 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1292 # device name, extended syntax
1293 device_value = pyp.Literal("minor").suppress() + number
1296 stmt = (~rbrace + keyword + ~lbrace +
1297 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1299 pyp.Optional(defa) + semi +
1300 pyp.Optional(pyp.restOfLine).suppress())
1303 section_name = pyp.Word(pyp.alphas + "_")
1304 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1306 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1309 cls._PARSE_SHOW = bnf
1314 def _GetShowData(cls, minor):
1315 """Return the `drbdsetup show` data for a minor.
1318 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1320 logging.error("Can't display the drbd config: %s - %s",
1321 result.fail_reason, result.output)
1323 return result.stdout
1326 def _GetDevInfo(cls, out):
1327 """Parse details about a given DRBD minor.
1329 This return, if available, the local backing device (as a path)
1330 and the local and remote (ip, port) information from a string
1331 containing the output of the `drbdsetup show` command as returned
1339 bnf = cls._GetShowParser()
1343 results = bnf.parseString(out)
1344 except pyp.ParseException, err:
1345 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1347 # and massage the results into our desired format
1348 for section in results:
1350 if sname == "_this_host":
1351 for lst in section[1:]:
1352 if lst[0] == "disk":
1353 data["local_dev"] = lst[1]
1354 elif lst[0] == "meta-disk":
1355 data["meta_dev"] = lst[1]
1356 data["meta_index"] = lst[2]
1357 elif lst[0] == "address":
1358 data["local_addr"] = tuple(lst[1:])
1359 elif sname == "_remote_host":
1360 for lst in section[1:]:
1361 if lst[0] == "address":
1362 data["remote_addr"] = tuple(lst[1:])
1365 def _MatchesLocal(self, info):
1366 """Test if our local config matches with an existing device.
1368 The parameter should be as returned from `_GetDevInfo()`. This
1369 method tests if our local backing device is the same as the one in
1370 the info parameter, in effect testing if we look like the given
1375 backend, meta = self._children
1377 backend = meta = None
1379 if backend is not None:
1380 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1382 retval = ("local_dev" not in info)
1384 if meta is not None:
1385 retval = retval and ("meta_dev" in info and
1386 info["meta_dev"] == meta.dev_path)
1387 retval = retval and ("meta_index" in info and
1388 info["meta_index"] == 0)
1390 retval = retval and ("meta_dev" not in info and
1391 "meta_index" not in info)
1394 def _MatchesNet(self, info):
1395 """Test if our network config matches with an existing device.
1397 The parameter should be as returned from `_GetDevInfo()`. This
1398 method tests if our network configuration is the same as the one
1399 in the info parameter, in effect testing if we look like the given
1403 if (((self._lhost is None and not ("local_addr" in info)) and
1404 (self._rhost is None and not ("remote_addr" in info)))):
1407 if self._lhost is None:
1410 if not ("local_addr" in info and
1411 "remote_addr" in info):
1414 retval = (info["local_addr"] == (self._lhost, self._lport))
1415 retval = (retval and
1416 info["remote_addr"] == (self._rhost, self._rport))
1419 def _AssembleLocal(self, minor, backend, meta, size):
1420 """Configure the local part of a DRBD device.
1423 args = ["drbdsetup", self._DevPath(minor), "disk",
1428 args.extend(["-d", "%sm" % size])
1430 version = self._GetVersion(self._GetProcData())
1431 vmaj = version["k_major"]
1432 vmin = version["k_minor"]
1433 vrel = version["k_point"]
1436 self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
1437 self.params[constants.LDP_BARRIERS],
1438 self.params[constants.LDP_NO_META_FLUSH])
1439 args.extend(barrier_args)
1441 if self.params[constants.LDP_DISK_CUSTOM]:
1442 args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
1444 result = utils.RunCmd(args)
1446 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1449 def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
1450 disable_meta_flush):
1451 """Compute the DRBD command line parameters for disk barriers
1453 Returns a list of the disk barrier parameters as requested via the
1454 disabled_barriers and disable_meta_flush arguments, and according to the
1455 supported ones in the DRBD version vmaj.vmin.vrel
1457 If the desired option is unsupported, raises errors.BlockDeviceError.
1460 disabled_barriers_set = frozenset(disabled_barriers)
1461 if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
1462 raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
1463 " barriers" % disabled_barriers)
1467 # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
1469 if not vmaj == 8 and vmin in (0, 2, 3):
1470 raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
1473 def _AppendOrRaise(option, min_version):
1474 """Helper for DRBD options"""
1475 if min_version is not None and vrel >= min_version:
1478 raise errors.BlockDeviceError("Could not use the option %s as the"
1479 " DRBD version %d.%d.%d does not support"
1480 " it." % (option, vmaj, vmin, vrel))
1482 # the minimum version for each feature is encoded via pairs of (minor
1483 # version -> x) where x is version in which support for the option was
1485 meta_flush_supported = disk_flush_supported = {
1491 disk_drain_supported = {
1496 disk_barriers_supported = {
1501 if disable_meta_flush:
1502 _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
1503 meta_flush_supported.get(vmin, None))
1506 if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
1507 _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
1508 disk_flush_supported.get(vmin, None))
1511 if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
1512 _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
1513 disk_drain_supported.get(vmin, None))
1516 if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
1517 _AppendOrRaise(cls._DISABLE_DISK_OPTION,
1518 disk_barriers_supported.get(vmin, None))
1522 def _AssembleNet(self, minor, net_info, protocol,
1523 dual_pri=False, hmac=None, secret=None):
1524 """Configure the network part of the device.
1527 lhost, lport, rhost, rport = net_info
1528 if None in net_info:
1529 # we don't want network connection and actually want to make
1531 self._ShutdownNet(minor)
1534 # Workaround for a race condition. When DRBD is doing its dance to
1535 # establish a connection with its peer, it also sends the
1536 # synchronization speed over the wire. In some cases setting the
1537 # sync speed only after setting up both sides can race with DRBD
1538 # connecting, hence we set it here before telling DRBD anything
1540 sync_errors = self._SetMinorSyncParams(minor, self.params)
1542 _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1543 (minor, utils.CommaJoin(sync_errors)))
1545 if netutils.IP6Address.IsValid(lhost):
1546 if not netutils.IP6Address.IsValid(rhost):
1547 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1548 (minor, lhost, rhost))
1550 elif netutils.IP4Address.IsValid(lhost):
1551 if not netutils.IP4Address.IsValid(rhost):
1552 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1553 (minor, lhost, rhost))
1556 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1558 args = ["drbdsetup", self._DevPath(minor), "net",
1559 "%s:%s:%s" % (family, lhost, lport),
1560 "%s:%s:%s" % (family, rhost, rport), protocol,
1561 "-A", "discard-zero-changes",
1568 args.extend(["-a", hmac, "-x", secret])
1570 if self.params[constants.LDP_NET_CUSTOM]:
1571 args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
1573 result = utils.RunCmd(args)
1575 _ThrowError("drbd%d: can't setup network: %s - %s",
1576 minor, result.fail_reason, result.output)
1578 def _CheckNetworkConfig():
1579 info = self._GetDevInfo(self._GetShowData(minor))
1580 if not "local_addr" in info or not "remote_addr" in info:
1581 raise utils.RetryAgain()
1583 if (info["local_addr"] != (lhost, lport) or
1584 info["remote_addr"] != (rhost, rport)):
1585 raise utils.RetryAgain()
1588 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1589 except utils.RetryTimeout:
1590 _ThrowError("drbd%d: timeout while configuring network", minor)
1592 def AddChildren(self, devices):
1593 """Add a disk to the DRBD device.
1596 if self.minor is None:
1597 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1599 if len(devices) != 2:
1600 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1601 info = self._GetDevInfo(self._GetShowData(self.minor))
1602 if "local_dev" in info:
1603 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1604 backend, meta = devices
1605 if backend.dev_path is None or meta.dev_path is None:
1606 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1609 self._CheckMetaSize(meta.dev_path)
1610 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1612 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1613 self._children = devices
1615 def RemoveChildren(self, devices):
1616 """Detach the drbd device from local storage.
1619 if self.minor is None:
1620 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1622 # early return if we don't actually have backing storage
1623 info = self._GetDevInfo(self._GetShowData(self.minor))
1624 if "local_dev" not in info:
1626 if len(self._children) != 2:
1627 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1629 if self._children.count(None) == 2: # we don't actually have children :)
1630 logging.warning("drbd%d: requested detach while detached", self.minor)
1632 if len(devices) != 2:
1633 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1634 for child, dev in zip(self._children, devices):
1635 if dev != child.dev_path:
1636 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1637 " RemoveChildren", self.minor, dev, child.dev_path)
1639 self._ShutdownLocal(self.minor)
1643 def _SetMinorSyncParams(cls, minor, params):
1644 """Set the parameters of the DRBD syncer.
1646 This is the low-level implementation.
1649 @param minor: the drbd minor whose settings we change
1651 @param params: LD level disk parameters related to the synchronization
1653 @return: a list of error messages
1657 args = ["drbdsetup", cls._DevPath(minor), "syncer"]
1658 if params[constants.LDP_DYNAMIC_RESYNC]:
1659 version = cls._GetVersion(cls._GetProcData())
1660 vmin = version["k_minor"]
1661 vrel = version["k_point"]
1663 # By definition we are using 8.x, so just check the rest of the version
1665 if vmin != 3 or vrel < 9:
1666 msg = ("The current DRBD version (8.%d.%d) does not support the "
1667 "dynamic resync speed controller" % (vmin, vrel))
1671 if params[constants.LDP_PLAN_AHEAD] == 0:
1672 msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
1673 " controller at DRBD level. If you want to disable it, please"
1674 " set the dynamic-resync disk parameter to False.")
1678 # add the c-* parameters to args
1679 args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
1680 "--c-fill-target", params[constants.LDP_FILL_TARGET],
1681 "--c-delay-target", params[constants.LDP_DELAY_TARGET],
1682 "--c-max-rate", params[constants.LDP_MAX_RATE],
1683 "--c-min-rate", params[constants.LDP_MIN_RATE],
1687 args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
1689 args.append("--create-device")
1690 result = utils.RunCmd(args)
1692 msg = ("Can't change syncer rate: %s - %s" %
1693 (result.fail_reason, result.output))
1699 def SetSyncParams(self, params):
1700 """Set the synchronization parameters of the DRBD syncer.
1703 @param params: LD level disk parameters related to the synchronization
1705 @return: a list of error messages, emitted both by the current node and by
1706 children. An empty list means no errors
1709 if self.minor is None:
1710 err = "Not attached during SetSyncParams"
1714 children_result = super(DRBD8, self).SetSyncParams(params)
1715 children_result.extend(self._SetMinorSyncParams(self.minor, params))
1716 return children_result
1718 def PauseResumeSync(self, pause):
1719 """Pauses or resumes the sync of a DRBD device.
1721 @param pause: Wether to pause or resume
1722 @return: the success of the operation
1725 if self.minor is None:
1726 logging.info("Not attached during PauseSync")
1729 children_result = super(DRBD8, self).PauseResumeSync(pause)
1736 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1738 logging.error("Can't %s: %s - %s", cmd,
1739 result.fail_reason, result.output)
1740 return not result.failed and children_result
1742 def GetProcStatus(self):
1743 """Return device data from /proc.
1746 if self.minor is None:
1747 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1748 proc_info = self._MassageProcData(self._GetProcData())
1749 if self.minor not in proc_info:
1750 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1751 return DRBD8Status(proc_info[self.minor])
1753 def GetSyncStatus(self):
1754 """Returns the sync status of the device.
1757 If sync_percent is None, it means all is ok
1758 If estimated_time is None, it means we can't estimate
1759 the time needed, otherwise it's the time left in seconds.
1762 We set the is_degraded parameter to True on two conditions:
1763 network not connected or local disk missing.
1765 We compute the ldisk parameter based on whether we have a local
1768 @rtype: objects.BlockDevStatus
1771 if self.minor is None and not self.Attach():
1772 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1774 stats = self.GetProcStatus()
1775 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1777 if stats.is_disk_uptodate:
1778 ldisk_status = constants.LDS_OKAY
1779 elif stats.is_diskless:
1780 ldisk_status = constants.LDS_FAULTY
1782 ldisk_status = constants.LDS_UNKNOWN
1784 return objects.BlockDevStatus(dev_path=self.dev_path,
1787 sync_percent=stats.sync_percent,
1788 estimated_time=stats.est_time,
1789 is_degraded=is_degraded,
1790 ldisk_status=ldisk_status)
1792 def Open(self, force=False):
1793 """Make the local state primary.
1795 If the 'force' parameter is given, the '-o' option is passed to
1796 drbdsetup. Since this is a potentially dangerous operation, the
1797 force flag should be only given after creation, when it actually
1801 if self.minor is None and not self.Attach():
1802 logging.error("DRBD cannot attach to a device during open")
1804 cmd = ["drbdsetup", self.dev_path, "primary"]
1807 result = utils.RunCmd(cmd)
1809 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1813 """Make the local state secondary.
1815 This will, of course, fail if the device is in use.
1818 if self.minor is None and not self.Attach():
1819 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1820 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1822 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1823 self.minor, result.output)
1825 def DisconnectNet(self):
1826 """Removes network configuration.
1828 This method shutdowns the network side of the device.
1830 The method will wait up to a hardcoded timeout for the device to
1831 go into standalone after the 'disconnect' command before
1832 re-configuring it, as sometimes it takes a while for the
1833 disconnect to actually propagate and thus we might issue a 'net'
1834 command while the device is still connected. If the device will
1835 still be attached to the network and we time out, we raise an
1839 if self.minor is None:
1840 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1842 if None in (self._lhost, self._lport, self._rhost, self._rport):
1843 _ThrowError("drbd%d: DRBD disk missing network info in"
1844 " DisconnectNet()", self.minor)
1846 class _DisconnectStatus:
1847 def __init__(self, ever_disconnected):
1848 self.ever_disconnected = ever_disconnected
1850 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1852 def _WaitForDisconnect():
1853 if self.GetProcStatus().is_standalone:
1856 # retry the disconnect, it seems possible that due to a well-time
1857 # disconnect on the peer, my disconnect command might be ignored and
1859 dstatus.ever_disconnected = \
1860 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1862 raise utils.RetryAgain()
1865 start_time = time.time()
1868 # Start delay at 100 milliseconds and grow up to 2 seconds
1869 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1870 self._NET_RECONFIG_TIMEOUT)
1871 except utils.RetryTimeout:
1872 if dstatus.ever_disconnected:
1873 msg = ("drbd%d: device did not react to the"
1874 " 'disconnect' command in a timely manner")
1876 msg = "drbd%d: can't shutdown network, even after multiple retries"
1878 _ThrowError(msg, self.minor)
1880 reconfig_time = time.time() - start_time
1881 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1882 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1883 self.minor, reconfig_time)
1885 def AttachNet(self, multimaster):
1886 """Reconnects the network.
1888 This method connects the network side of the device with a
1889 specified multi-master flag. The device needs to be 'Standalone'
1890 but have valid network configuration data.
1893 - multimaster: init the network in dual-primary mode
1896 if self.minor is None:
1897 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1899 if None in (self._lhost, self._lport, self._rhost, self._rport):
1900 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1902 status = self.GetProcStatus()
1904 if not status.is_standalone:
1905 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1907 self._AssembleNet(self.minor,
1908 (self._lhost, self._lport, self._rhost, self._rport),
1909 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1910 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1913 """Check if our minor is configured.
1915 This doesn't do any device configurations - it only checks if the
1916 minor is in a state different from Unconfigured.
1918 Note that this function will not change the state of the system in
1919 any way (except in case of side-effects caused by reading from
1923 used_devs = self.GetUsedDevs()
1924 if self._aminor in used_devs:
1925 minor = self._aminor
1929 self._SetFromMinor(minor)
1930 return minor is not None
1933 """Assemble the drbd.
1936 - if we have a configured device, we try to ensure that it matches
1938 - if not, we create it from zero
1939 - anyway, set the device parameters
1942 super(DRBD8, self).Assemble()
1945 if self.minor is None:
1946 # local device completely unconfigured
1947 self._FastAssemble()
1949 # we have to recheck the local and network status and try to fix
1951 self._SlowAssemble()
1953 sync_errors = self.SetSyncParams(self.params)
1955 _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1956 (self.minor, utils.CommaJoin(sync_errors)))
1958 def _SlowAssemble(self):
1959 """Assembles the DRBD device from a (partially) configured device.
1961 In case of partially attached (local device matches but no network
1962 setup), we perform the network attach. If successful, we re-test
1963 the attach if can return success.
1966 # TODO: Rewrite to not use a for loop just because there is 'break'
1967 # pylint: disable=W0631
1968 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1969 for minor in (self._aminor,):
1970 info = self._GetDevInfo(self._GetShowData(minor))
1971 match_l = self._MatchesLocal(info)
1972 match_r = self._MatchesNet(info)
1974 if match_l and match_r:
1975 # everything matches
1978 if match_l and not match_r and "local_addr" not in info:
1979 # disk matches, but not attached to network, attach and recheck
1980 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1981 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1982 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1985 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1986 " show' disagrees", minor)
1988 if match_r and "local_dev" not in info:
1989 # no local disk, but network attached and it matches
1990 self._AssembleLocal(minor, self._children[0].dev_path,
1991 self._children[1].dev_path, self.size)
1992 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1995 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1996 " show' disagrees", minor)
1998 # this case must be considered only if we actually have local
1999 # storage, i.e. not in diskless mode, because all diskless
2000 # devices are equal from the point of view of local
2002 if (match_l and "local_dev" in info and
2003 not match_r and "local_addr" in info):
2004 # strange case - the device network part points to somewhere
2005 # else, even though its local storage is ours; as we own the
2006 # drbd space, we try to disconnect from the remote peer and
2007 # reconnect to our correct one
2009 self._ShutdownNet(minor)
2010 except errors.BlockDeviceError, err:
2011 _ThrowError("drbd%d: device has correct local storage, wrong"
2012 " remote peer and is unable to disconnect in order"
2013 " to attach to the correct peer: %s", minor, str(err))
2014 # note: _AssembleNet also handles the case when we don't want
2015 # local storage (i.e. one or more of the _[lr](host|port) is
2017 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
2018 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
2019 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2022 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
2023 " show' disagrees", minor)
2028 self._SetFromMinor(minor)
2030 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
2033 def _FastAssemble(self):
2034 """Assemble the drbd device from zero.
2036 This is run when in Assemble we detect our minor is unused.
2039 minor = self._aminor
2040 if self._children and self._children[0] and self._children[1]:
2041 self._AssembleLocal(minor, self._children[0].dev_path,
2042 self._children[1].dev_path, self.size)
2043 if self._lhost and self._lport and self._rhost and self._rport:
2044 self._AssembleNet(minor,
2045 (self._lhost, self._lport, self._rhost, self._rport),
2046 constants.DRBD_NET_PROTOCOL,
2047 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
2048 self._SetFromMinor(minor)
2051 def _ShutdownLocal(cls, minor):
2052 """Detach from the local device.
2054 I/Os will continue to be served from the remote device. If we
2055 don't have a remote device, this operation will fail.
2058 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2060 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
2063 def _ShutdownNet(cls, minor):
2064 """Disconnect from the remote peer.
2066 This fails if we don't have a local device.
2069 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2071 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
2074 def _ShutdownAll(cls, minor):
2075 """Deactivate the device.
2077 This will, of course, fail if the device is in use.
2080 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2082 _ThrowError("drbd%d: can't shutdown drbd device: %s",
2083 minor, result.output)
2086 """Shutdown the DRBD device.
2089 if self.minor is None and not self.Attach():
2090 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
2094 self.dev_path = None
2095 self._ShutdownAll(minor)
2098 """Stub remove for DRBD devices.
2104 def Create(cls, unique_id, children, size, params):
2105 """Create a new DRBD8 device.
2107 Since DRBD devices are not created per se, just assembled, this
2108 function only initializes the metadata.
2111 if len(children) != 2:
2112 raise errors.ProgrammerError("Invalid setup for the drbd device")
2113 # check that the minor is unused
2114 aminor = unique_id[4]
2115 proc_info = cls._MassageProcData(cls._GetProcData())
2116 if aminor in proc_info:
2117 status = DRBD8Status(proc_info[aminor])
2118 in_use = status.is_in_use
2122 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
2125 if not meta.Attach():
2126 _ThrowError("drbd%d: can't attach to meta device '%s'",
2128 cls._CheckMetaSize(meta.dev_path)
2129 cls._InitMeta(aminor, meta.dev_path)
2130 return cls(unique_id, children, size, params)
2132 def Grow(self, amount, dryrun, backingstore):
2133 """Resize the DRBD device and its backing storage.
2136 if self.minor is None:
2137 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
2138 if len(self._children) != 2 or None in self._children:
2139 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
2140 self._children[0].Grow(amount, dryrun, backingstore)
2141 if dryrun or backingstore:
2142 # DRBD does not support dry-run mode and is not backing storage,
2143 # so we'll return here
2145 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
2146 "%dm" % (self.size + amount)])
2148 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
2151 class FileStorage(BlockDev):
2154 This class represents the a file storage backend device.
2156 The unique_id for the file device is a (file_driver, file_path) tuple.
2159 def __init__(self, unique_id, children, size, params):
2160 """Initalizes a file device backend.
2164 raise errors.BlockDeviceError("Invalid setup for file device")
2165 super(FileStorage, self).__init__(unique_id, children, size, params)
2166 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2167 raise ValueError("Invalid configuration data %s" % str(unique_id))
2168 self.driver = unique_id[0]
2169 self.dev_path = unique_id[1]
2173 """Assemble the device.
2175 Checks whether the file device exists, raises BlockDeviceError otherwise.
2178 if not os.path.exists(self.dev_path):
2179 _ThrowError("File device '%s' does not exist" % self.dev_path)
2182 """Shutdown the device.
2184 This is a no-op for the file type, as we don't deactivate
2185 the file on shutdown.
2190 def Open(self, force=False):
2191 """Make the device ready for I/O.
2193 This is a no-op for the file type.
2199 """Notifies that the device will no longer be used for I/O.
2201 This is a no-op for the file type.
2207 """Remove the file backing the block device.
2210 @return: True if the removal was successful
2214 os.remove(self.dev_path)
2215 except OSError, err:
2216 if err.errno != errno.ENOENT:
2217 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2219 def Rename(self, new_id):
2220 """Renames the file.
2223 # TODO: implement rename for file-based storage
2224 _ThrowError("Rename is not supported for file-based storage")
2226 def Grow(self, amount, dryrun, backingstore):
2229 @param amount: the amount (in mebibytes) to grow with
2232 if not backingstore:
2234 # Check that the file exists
2236 current_size = self.GetActualSize()
2237 new_size = current_size + amount * 1024 * 1024
2238 assert new_size > current_size, "Cannot Grow with a negative amount"
2239 # We can't really simulate the growth
2243 f = open(self.dev_path, "a+")
2244 f.truncate(new_size)
2246 except EnvironmentError, err:
2247 _ThrowError("Error in file growth: %", str(err))
2250 """Attach to an existing file.
2252 Check if this file already exists.
2255 @return: True if file exists
2258 self.attached = os.path.exists(self.dev_path)
2259 return self.attached
2261 def GetActualSize(self):
2262 """Return the actual disk size.
2264 @note: the device needs to be active when this is called
2267 assert self.attached, "BlockDevice not attached in GetActualSize()"
2269 st = os.stat(self.dev_path)
2271 except OSError, err:
2272 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2275 def Create(cls, unique_id, children, size, params):
2276 """Create a new file.
2278 @param size: the size of file in MiB
2280 @rtype: L{bdev.FileStorage}
2281 @return: an instance of FileStorage
2284 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2285 raise ValueError("Invalid configuration data %s" % str(unique_id))
2286 dev_path = unique_id[1]
2288 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2289 f = os.fdopen(fd, "w")
2290 f.truncate(size * 1024 * 1024)
2292 except EnvironmentError, err:
2293 if err.errno == errno.EEXIST:
2294 _ThrowError("File already existing: %s", dev_path)
2295 _ThrowError("Error in file creation: %", str(err))
2297 return FileStorage(unique_id, children, size, params)
2300 class PersistentBlockDevice(BlockDev):
2301 """A block device with persistent node
2303 May be either directly attached, or exposed through DM (e.g. dm-multipath).
2304 udev helpers are probably required to give persistent, human-friendly
2307 For the time being, pathnames are required to lie under /dev.
2310 def __init__(self, unique_id, children, size, params):
2311 """Attaches to a static block device.
2313 The unique_id is a path under /dev.
2316 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
2318 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2319 raise ValueError("Invalid configuration data %s" % str(unique_id))
2320 self.dev_path = unique_id[1]
2321 if not os.path.realpath(self.dev_path).startswith("/dev/"):
2322 raise ValueError("Full path '%s' lies outside /dev" %
2323 os.path.realpath(self.dev_path))
2324 # TODO: this is just a safety guard checking that we only deal with devices
2325 # we know how to handle. In the future this will be integrated with
2326 # external storage backends and possible values will probably be collected
2327 # from the cluster configuration.
2328 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2329 raise ValueError("Got persistent block device of invalid type: %s" %
2332 self.major = self.minor = None
2336 def Create(cls, unique_id, children, size, params):
2337 """Create a new device
2339 This is a noop, we only return a PersistentBlockDevice instance
2342 return PersistentBlockDevice(unique_id, children, 0, params)
2352 def Rename(self, new_id):
2353 """Rename this device.
2356 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2359 """Attach to an existing block device.
2363 self.attached = False
2365 st = os.stat(self.dev_path)
2366 except OSError, err:
2367 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2370 if not stat.S_ISBLK(st.st_mode):
2371 logging.error("%s is not a block device", self.dev_path)
2374 self.major = os.major(st.st_rdev)
2375 self.minor = os.minor(st.st_rdev)
2376 self.attached = True
2381 """Assemble the device.
2387 """Shutdown the device.
2392 def Open(self, force=False):
2393 """Make the device ready for I/O.
2399 """Notifies that the device will no longer be used for I/O.
2404 def Grow(self, amount, dryrun, backingstore):
2405 """Grow the logical volume.
2408 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2411 class RADOSBlockDevice(BlockDev):
2412 """A RADOS Block Device (rbd).
2414 This class implements the RADOS Block Device for the backend. You need
2415 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
2416 this to be functional.
2419 def __init__(self, unique_id, children, size, params):
2420 """Attaches to an rbd device.
2423 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
2424 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2425 raise ValueError("Invalid configuration data %s" % str(unique_id))
2427 self.driver, self.rbd_name = unique_id
2429 self.major = self.minor = None
2433 def Create(cls, unique_id, children, size, params):
2434 """Create a new rbd device.
2436 Provision a new rbd volume inside a RADOS pool.
2439 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2440 raise errors.ProgrammerError("Invalid configuration data %s" %
2442 rbd_pool = params[constants.LDP_POOL]
2443 rbd_name = unique_id[1]
2445 # Provision a new rbd volume (Image) inside the RADOS cluster.
2446 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
2447 rbd_name, "--size", "%s" % size]
2448 result = utils.RunCmd(cmd)
2450 _ThrowError("rbd creation failed (%s): %s",
2451 result.fail_reason, result.output)
2453 return RADOSBlockDevice(unique_id, children, size, params)
2456 """Remove the rbd device.
2459 rbd_pool = self.params[constants.LDP_POOL]
2460 rbd_name = self.unique_id[1]
2462 if not self.minor and not self.Attach():
2463 # The rbd device doesn't exist.
2466 # First shutdown the device (remove mappings).
2469 # Remove the actual Volume (Image) from the RADOS cluster.
2470 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
2471 result = utils.RunCmd(cmd)
2473 _ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
2474 result.fail_reason, result.output)
2476 def Rename(self, new_id):
2477 """Rename this device.
2483 """Attach to an existing rbd device.
2485 This method maps the rbd volume that matches our name with
2486 an rbd device and then attaches to this device.
2489 self.attached = False
2491 # Map the rbd volume to a block device under /dev
2492 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
2495 st = os.stat(self.dev_path)
2496 except OSError, err:
2497 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2500 if not stat.S_ISBLK(st.st_mode):
2501 logging.error("%s is not a block device", self.dev_path)
2504 self.major = os.major(st.st_rdev)
2505 self.minor = os.minor(st.st_rdev)
2506 self.attached = True
2510 def _MapVolumeToBlockdev(self, unique_id):
2511 """Maps existing rbd volumes to block devices.
2513 This method should be idempotent if the mapping already exists.
2516 @return: the block device path that corresponds to the volume
2519 pool = self.params[constants.LDP_POOL]
2522 # Check if the mapping already exists.
2523 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2524 result = utils.RunCmd(showmap_cmd)
2526 _ThrowError("rbd showmapped failed (%s): %s",
2527 result.fail_reason, result.output)
2529 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2532 # The mapping exists. Return it.
2535 # The mapping doesn't exist. Create it.
2536 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
2537 result = utils.RunCmd(map_cmd)
2539 _ThrowError("rbd map failed (%s): %s",
2540 result.fail_reason, result.output)
2542 # Find the corresponding rbd device.
2543 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2544 result = utils.RunCmd(showmap_cmd)
2546 _ThrowError("rbd map succeeded, but showmapped failed (%s): %s",
2547 result.fail_reason, result.output)
2549 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2552 _ThrowError("rbd map succeeded, but could not find the rbd block"
2553 " device in output of showmapped, for volume: %s", name)
2555 # The device was successfully mapped. Return it.
2559 def _ParseRbdShowmappedOutput(output, volume_name):
2560 """Parse the output of `rbd showmapped'.
2562 This method parses the output of `rbd showmapped' and returns
2563 the rbd block device path (e.g. /dev/rbd0) that matches the
2566 @type output: string
2567 @param output: the whole output of `rbd showmapped'
2568 @type volume_name: string
2569 @param volume_name: the name of the volume whose device we search for
2570 @rtype: string or None
2571 @return: block device path if the volume is mapped, else None
2580 lines = output.splitlines()
2581 splitted_lines = map(lambda l: l.split(field_sep), lines)
2583 # Check empty output.
2584 if not splitted_lines:
2585 _ThrowError("rbd showmapped returned empty output")
2587 # Check showmapped header line, to determine number of fields.
2588 field_cnt = len(splitted_lines[0])
2589 if field_cnt != allfields:
2590 _ThrowError("Cannot parse rbd showmapped output because its format"
2591 " seems to have changed; expected %s fields, found %s",
2592 allfields, field_cnt)
2595 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
2598 if len(matched_lines) > 1:
2599 _ThrowError("The rbd volume %s is mapped more than once."
2600 " This shouldn't happen, try to unmap the extra"
2601 " devices manually.", volume_name)
2604 # rbd block device found. Return it.
2605 rbd_dev = matched_lines[0][devicefield]
2608 # The given volume is not mapped.
2612 """Assemble the device.
2618 """Shutdown the device.
2621 if not self.minor and not self.Attach():
2622 # The rbd device doesn't exist.
2625 # Unmap the block device from the Volume.
2626 self._UnmapVolumeFromBlockdev(self.unique_id)
2629 self.dev_path = None
2631 def _UnmapVolumeFromBlockdev(self, unique_id):
2632 """Unmaps the rbd device from the Volume it is mapped.
2634 Unmaps the rbd device from the Volume it was previously mapped to.
2635 This method should be idempotent if the Volume isn't mapped.
2638 pool = self.params[constants.LDP_POOL]
2641 # Check if the mapping already exists.
2642 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2643 result = utils.RunCmd(showmap_cmd)
2645 _ThrowError("rbd showmapped failed [during unmap](%s): %s",
2646 result.fail_reason, result.output)
2648 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2651 # The mapping exists. Unmap the rbd device.
2652 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
2653 result = utils.RunCmd(unmap_cmd)
2655 _ThrowError("rbd unmap failed (%s): %s",
2656 result.fail_reason, result.output)
2658 def Open(self, force=False):
2659 """Make the device ready for I/O.
2665 """Notifies that the device will no longer be used for I/O.
2670 def Grow(self, amount, dryrun, backingstore):
2673 @type amount: integer
2674 @param amount: the amount (in mebibytes) to grow with
2675 @type dryrun: boolean
2676 @param dryrun: whether to execute the operation in simulation mode
2677 only, without actually increasing the size
2680 if not backingstore:
2682 if not self.Attach():
2683 _ThrowError("Can't attach to rbd device during Grow()")
2686 # the rbd tool does not support dry runs of resize operations.
2687 # Since rbd volumes are thinly provisioned, we assume
2688 # there is always enough free space for the operation.
2691 rbd_pool = self.params[constants.LDP_POOL]
2692 rbd_name = self.unique_id[1]
2693 new_size = self.size + amount
2695 # Resize the rbd volume (Image) inside the RADOS cluster.
2696 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
2697 rbd_name, "--size", "%s" % new_size]
2698 result = utils.RunCmd(cmd)
2700 _ThrowError("rbd resize failed (%s): %s",
2701 result.fail_reason, result.output)
2705 constants.LD_LV: LogicalVolume,
2706 constants.LD_DRBD8: DRBD8,
2707 constants.LD_BLOCKDEV: PersistentBlockDevice,
2708 constants.LD_RBD: RADOSBlockDevice,
2711 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2712 DEV_MAP[constants.LD_FILE] = FileStorage
2715 def _VerifyDiskType(dev_type):
2716 if dev_type not in DEV_MAP:
2717 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2720 def _VerifyDiskParams(disk):
2721 """Verifies if all disk parameters are set.
2724 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
2726 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
2730 def FindDevice(disk, children):
2731 """Search for an existing, assembled device.
2733 This will succeed only if the device exists and is assembled, but it
2734 does not do any actions in order to activate the device.
2736 @type disk: L{objects.Disk}
2737 @param disk: the disk object to find
2738 @type children: list of L{bdev.BlockDev}
2739 @param children: the list of block devices that are children of the device
2740 represented by the disk parameter
2743 _VerifyDiskType(disk.dev_type)
2744 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2746 if not device.attached:
2751 def Assemble(disk, children):
2752 """Try to attach or assemble an existing device.
2754 This will attach to assemble the device, as needed, to bring it
2755 fully up. It must be safe to run on already-assembled devices.
2757 @type disk: L{objects.Disk}
2758 @param disk: the disk object to assemble
2759 @type children: list of L{bdev.BlockDev}
2760 @param children: the list of block devices that are children of the device
2761 represented by the disk parameter
2764 _VerifyDiskType(disk.dev_type)
2765 _VerifyDiskParams(disk)
2766 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2772 def Create(disk, children):
2775 @type disk: L{objects.Disk}
2776 @param disk: the disk object to create
2777 @type children: list of L{bdev.BlockDev}
2778 @param children: the list of block devices that are children of the device
2779 represented by the disk parameter
2782 _VerifyDiskType(disk.dev_type)
2783 _VerifyDiskParams(disk)
2784 device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,