4 # Copyright (C) 2006, 2007, 2010, 2011 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
41 # Size of reads in _CanReadDevice
42 _DEVICE_READ_SIZE = 128 * 1024
45 def _IgnoreError(fn, *args, **kwargs):
46 """Executes the given function, ignoring BlockDeviceErrors.
48 This is used in order to simplify the execution of cleanup or
52 @return: True when fn didn't raise an exception, False otherwise
58 except errors.BlockDeviceError, err:
59 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
63 def _ThrowError(msg, *args):
64 """Log an error to the node daemon and the raise an exception.
67 @param msg: the text of the exception
68 @raise errors.BlockDeviceError
74 raise errors.BlockDeviceError(msg)
77 def _CanReadDevice(path):
78 """Check if we can read from the given device.
80 This tries to read the first 128k of the device.
84 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
86 except EnvironmentError:
87 logging.warning("Can't read from device %s", path, exc_info=True)
91 class BlockDev(object):
92 """Block device abstract class.
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)
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
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
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)
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).
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
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.
134 def __init__(self, unique_id, children, size, params):
135 self._children = children
137 self.unique_id = unique_id
140 self.attached = False
145 """Assemble the device from its components.
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
159 """Find a device which matches our config and attach to it.
162 raise NotImplementedError
165 """Notifies that the device will no longer be used for I/O.
168 raise NotImplementedError
171 def Create(cls, unique_id, children, size, params):
172 """Create the device.
174 If the device cannot be created, it will return None
175 instead. Error messages go to the logging system.
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).
182 raise NotImplementedError
185 """Remove this device.
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
192 raise NotImplementedError
194 def Rename(self, new_id):
195 """Rename this device.
197 This may or may not make sense for a given device type.
200 raise NotImplementedError
202 def Open(self, force=False):
203 """Make the device ready for use.
205 This makes the device ready for I/O. For now, just the DRBD
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.
212 raise NotImplementedError
215 """Shut down the device, freeing its children.
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.
222 raise NotImplementedError
224 def SetSyncParams(self, params):
225 """Adjust the synchronization parameters of the mirror.
227 In case this is not a mirroring device, this is no-op.
229 @param params: dictionary of LD level disk parameters related to the
232 @return: a list of error messages, emitted both by the current node and by
233 children. An empty list means no errors.
238 for child in self._children:
239 result.extend(child.SetSyncParams(params))
242 def PauseResumeSync(self, pause):
243 """Pause/Resume the sync of the mirror.
245 In case this is not a mirroring device, this is no-op.
247 @param pause: Whether to pause or resume
252 for child in self._children:
253 result = result and child.PauseResumeSync(pause)
256 def GetSyncStatus(self):
257 """Returns the sync status of the device.
259 If this device is a mirroring device, this function returns the
260 status of the mirror.
262 If sync_percent is None, it means the device is not syncing.
264 If estimated_time is None, it means we can't estimate
265 the time needed, otherwise it's the time left in seconds.
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.
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).
275 @rtype: objects.BlockDevStatus
278 return objects.BlockDevStatus(dev_path=self.dev_path,
284 ldisk_status=constants.LDS_OKAY)
286 def CombinedSyncStatus(self):
287 """Calculate the mirror status recursively for our children.
289 The return value is the same as for `GetSyncStatus()` except the
290 minimum percent and maximum time are calculated across our
293 @rtype: objects.BlockDevStatus
296 status = self.GetSyncStatus()
298 min_percent = status.sync_percent
299 max_time = status.estimated_time
300 is_degraded = status.is_degraded
301 ldisk_status = status.ldisk_status
304 for child in self._children:
305 child_status = child.GetSyncStatus()
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)
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)
317 is_degraded = is_degraded or child_status.is_degraded
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)
324 return objects.BlockDevStatus(dev_path=self.dev_path,
327 sync_percent=min_percent,
328 estimated_time=max_time,
329 is_degraded=is_degraded,
330 ldisk_status=ldisk_status)
332 def SetInfo(self, text):
333 """Update metadata with info text.
335 Only supported for some device types.
338 for child in self._children:
341 def Grow(self, amount, dryrun):
342 """Grow the block device.
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
351 raise NotImplementedError
353 def GetActualSize(self):
354 """Return the actual disk size.
356 @note: the device needs to be active when this is called
359 assert self.attached, "BlockDevice not attached in GetActualSize()"
360 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
362 _ThrowError("blockdev failed (%s): %s",
363 result.fail_reason, result.output)
365 sz = int(result.output.strip())
366 except (ValueError, TypeError), err:
367 _ThrowError("Failed to parse blockdev output: %s", str(err))
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))
376 class LogicalVolume(BlockDev):
377 """Logical Volume block device.
380 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
381 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
382 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
384 def __init__(self, unique_id, children, size, params):
385 """Attaches to a LV device.
387 The unique_id is a tuple (vg_name, lv_name)
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
402 def Create(cls, unique_id, children, size, params):
403 """Create a new logical volume.
406 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
407 raise errors.ProgrammerError("Invalid configuration data %s" %
409 vg_name, lv_name = unique_id
410 cls._ValidateName(vg_name)
411 cls._ValidateName(lv_name)
412 pvs_info = cls.GetPVInfo([vg_name])
414 _ThrowError("Can't compute PV info for vg %s", vg_name)
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)
431 # The size constraint should have been checked from the master before
432 # calling the create function.
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
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:
446 _ThrowError("LV create failed (%s): %s",
447 result.fail_reason, result.output)
448 return LogicalVolume(unique_id, children, size, params)
451 def _GetVolumeInfo(lvm_cmd, fields):
452 """Returns LVM Volumen infos using lvm_cmd
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
460 raise errors.ProgrammerError("No fields specified")
463 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
464 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
466 result = utils.RunCmd(cmd)
468 raise errors.CommandError("Can't get the volume information: %s - %s" %
469 (result.fail_reason, result.output))
472 for line in result.stdout.splitlines():
473 splitted_fields = line.strip().split(sep)
475 if len(fields) != len(splitted_fields):
476 raise errors.CommandError("Can't parse %s output: line '%s'" %
479 data.append(splitted_fields)
484 def GetPVInfo(cls, vg_names, filter_allocatable=True):
485 """Get the free space info for PVs in a volume group.
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
491 @return: list of tuples (free_space, name) with free_space in mebibytes
495 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
497 except errors.GenericError, err:
498 logging.error("Can't get PV information: %s", err)
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":
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:
509 data.append((float(pv_free), pv_name, vg_name))
514 def GetVGInfo(cls, vg_names, filter_readonly=True):
515 """Get the free space info for specific VGs.
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
521 @return: list of tuples (free_space, total_size, name) with free_space in
526 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
528 except errors.GenericError, err:
529 logging.error("Can't get VG information: %s", err)
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":
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:
540 data.append((float(vg_free), float(vg_size), vg_name))
545 def _ValidateName(cls, name):
546 """Validates that a given name is valid as VG or LV name.
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.
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)
559 """Remove this logical volume.
562 if not self.minor and not self.Attach():
563 # the LV does not exist
565 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
566 (self._vg_name, self._lv_name)])
568 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
570 def Rename(self, new_id):
571 """Rename this logical volume.
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])
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)
588 """Attach to an existing LV.
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
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])
601 logging.error("Can't find LV %s: %s, %s",
602 self.dev_path, result.fail_reason, result.output)
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))
614 out = out[-1].strip().rstrip(",")
617 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
620 status, major, minor, pe_size, stripes = out
622 logging.error("lvs lv_attr is not 6 characters (%s)", status)
628 except (TypeError, ValueError), err:
629 logging.error("lvs major/minor cannot be parsed: %s", str(err))
632 pe_size = int(float(pe_size))
633 except (TypeError, ValueError), err:
634 logging.error("Can't parse vg extent size: %s", err)
638 stripes = int(stripes)
639 except (TypeError, ValueError), err:
640 logging.error("Can't parse the number of stripes: %s", err)
645 self.pe_size = pe_size
646 self.stripe_count = stripes
647 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
653 """Assemble the device.
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).
660 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
662 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
665 """Shutdown the device.
667 This is a no-op for the LV device type, as we don't deactivate the
673 def GetSyncStatus(self):
674 """Returns the sync status of the device.
676 If this device is a mirroring device, this function returns the
677 status of the mirror.
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
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
690 The status was already read in Attach, so we just return it.
692 @rtype: objects.BlockDevStatus
696 ldisk_status = constants.LDS_FAULTY
698 ldisk_status = constants.LDS_OKAY
700 return objects.BlockDevStatus(dev_path=self.dev_path,
705 is_degraded=self._degraded,
706 ldisk_status=ldisk_status)
708 def Open(self, force=False):
709 """Make the device ready for I/O.
711 This is a no-op for the LV device type.
717 """Notifies that the device will no longer be used for I/O.
719 This is a no-op for the LV device type.
724 def Snapshot(self, size):
725 """Create a snapshot copy of an lvm block device.
727 @returns: tuple (vg, lv)
730 snap_name = self._lv_name + ".snap"
732 # remove existing snapshot if found
733 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
734 _IgnoreError(snap.Remove)
736 vg_info = self.GetVGInfo([self._vg_name])
738 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
739 free_size, _, _ = vg_info[0]
741 _ThrowError("Not enough free space: required %s,"
742 " available %s", size, free_size)
744 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
745 "-n%s" % snap_name, self.dev_path])
747 _ThrowError("command: %s error: %s - %s",
748 result.cmd, result.fail_reason, result.output)
750 return (self._vg_name, snap_name)
752 def SetInfo(self, text):
753 """Update metadata with info text.
756 BlockDev.SetInfo(self, text)
758 # Replace invalid characters
759 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
760 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
762 # Only up to 128 characters are allowed
765 result = utils.RunCmd(["lvchange", "--addtag", text,
768 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
771 def Grow(self, amount, dryrun):
772 """Grow the logical volume.
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
781 amount += full_stripe_size - rest
782 cmd = ["lvextend", "-L", "+%dm" % amount]
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
789 for alloc_policy in "contiguous", "cling", "normal":
790 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
791 if not result.failed:
793 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
796 class DRBD8Status(object):
797 """A DRBD status representation class.
799 Note that this doesn't support unconfigured devices (cs:Unconfigured).
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)
809 "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
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([
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
848 RO_PRIMARY = "Primary"
849 RO_SECONDARY = "Secondary"
850 RO_UNKNOWN = "Unknown"
852 def __init__(self, procline):
853 u = self.UNCONF_RE.match(procline)
855 self.cstatus = self.CS_UNCONFIGURED
856 self.lrole = self.rrole = self.ldisk = self.rdisk = None
858 m = self.LINE_RE.match(procline)
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)
867 # end reading of data from the LINE_RE or UNCONF_RE
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
879 self.is_diskless = self.ldisk == self.DS_DISKLESS
880 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
882 self.is_in_resync = self.cstatus in self.CSET_SYNC
883 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
885 m = self.SYNC_RE.match(procline)
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
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
897 if self.is_in_resync:
898 self.sync_percent = 0
900 self.sync_percent = None
904 class BaseDRBD(BlockDev): # pylint: disable=W0223
907 This class contains a few bits of common functionality between the
908 0.7 and 8.x versions of DRBD.
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$")
917 _ST_UNCONFIGURED = "Unconfigured"
918 _ST_WFCONNECTION = "WFConnection"
919 _ST_CONNECTED = "Connected"
921 _STATUS_FILE = "/proc/drbd"
922 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
925 def _GetProcData(filename=_STATUS_FILE):
926 """Return data from /proc/drbd.
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))
936 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
938 _ThrowError("Can't read any data from %s", filename)
942 def _MassageProcData(cls, data):
943 """Transform the output of _GetProdData into a nicer form.
945 @return: a dictionary of minor: joined lines from /proc/drbd
950 old_minor = old_line = None
952 if not line: # completely empty lines, as can be returned by drbd8.0+
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))
961 if old_minor is not None:
962 old_line += " " + line.strip()
964 if old_minor is not None:
965 results[old_minor] = old_line
969 def _GetVersion(cls, proc_data):
970 """Return the DRBD version.
972 This will return a dict with keys:
978 - proto2 (only on drbd > 8.2.X)
981 first_line = proc_data[0].strip()
982 version = cls._VERSION_RE.match(first_line)
984 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
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]),
994 if values[5] is not None:
995 retval["proto2"] = values[5]
1000 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
1001 """Returns DRBD usermode_helper currently set.
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))
1011 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1013 _ThrowError("Can't read any data from %s", filename)
1017 def _DevPath(minor):
1018 """Return the path to a drbd device for a given minor.
1021 return "/dev/drbd%d" % minor
1024 def GetUsedDevs(cls):
1025 """Compute the list of used DRBD devices.
1028 data = cls._GetProcData()
1032 match = cls._VALID_LINE_RE.match(line)
1035 minor = int(match.group(1))
1036 state = match.group(2)
1037 if state == cls._ST_UNCONFIGURED:
1039 used_devs[minor] = state, line
1043 def _SetFromMinor(self, minor):
1044 """Set our parameters based on the given minor.
1046 This sets our minor variable and our dev_path.
1050 self.minor = self.dev_path = None
1051 self.attached = False
1054 self.dev_path = self._DevPath(minor)
1055 self.attached = True
1058 def _CheckMetaSize(meta_device):
1059 """Check if the given meta device looks like a valid one.
1061 This currently only check the size, which must be around
1065 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1067 _ThrowError("Failed to get device size: %s - %s",
1068 result.fail_reason, result.output)
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))
1085 def Rename(self, new_id):
1088 This is not supported for drbd devices.
1091 raise errors.ProgrammerError("Can't rename a drbd device")
1094 class DRBD8(BaseDRBD):
1095 """DRBD v8.x block device.
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.
1101 The unique_id for the drbd device is the (local_ip, local_port,
1102 remote_ip, remote_port) tuple, and it must have two children: the
1103 data device and the meta_device. The meta device is checked for
1104 valid size and is zeroed on create.
1111 _NET_RECONFIG_TIMEOUT = 60
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
1119 def __init__(self, unique_id, children, size, params):
1120 if children and children.count(None) > 0:
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
1130 if not _CanReadDevice(children[1].dev_path):
1131 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
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"])
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" %
1148 def _InitMeta(cls, minor, dev_path):
1149 """Initialize a meta device.
1151 This will not work if the given minor is in use.
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
1159 result = utils.RunCmd([constants.DD_CMD,
1160 "if=/dev/zero", "of=%s" % dev_path,
1161 "bs=1048576", "count=128", "oflag=direct"])
1163 _ThrowError("Can't wipe the meta device: %s", result.output)
1165 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1166 "v08", dev_path, "0", "create-md"])
1168 _ThrowError("Can't initialize meta device: %s", result.output)
1171 def _FindUnusedMinor(cls):
1172 """Find an unused DRBD device.
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.
1178 data = cls._GetProcData()
1182 match = cls._UNUSED_LINE_RE.match(line)
1184 return int(match.group(1))
1185 match = cls._VALID_LINE_RE.match(line)
1187 minor = int(match.group(1))
1188 highest = max(highest, minor)
1189 if highest is None: # there are no minors in use at all
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")
1197 def _GetShowParser(cls):
1198 """Return a parser for `drbd show` output.
1200 This will either create or return an already-create parser for the
1201 output of the command `drbd show`.
1204 if cls._PARSE_SHOW is not None:
1205 return cls._PARSE_SHOW
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]))
1217 comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1218 defa = pyp.Literal("_is_default").suppress()
1219 dbl_quote = pyp.Literal('"').suppress()
1221 keyword = pyp.Word(pyp.alphanums + '-')
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
1237 stmt = (~rbrace + keyword + ~lbrace +
1238 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1240 pyp.Optional(defa) + semi +
1241 pyp.Optional(pyp.restOfLine).suppress())
1244 section_name = pyp.Word(pyp.alphas + "_")
1245 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1247 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1250 cls._PARSE_SHOW = bnf
1255 def _GetShowData(cls, minor):
1256 """Return the `drbdsetup show` data for a minor.
1259 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1261 logging.error("Can't display the drbd config: %s - %s",
1262 result.fail_reason, result.output)
1264 return result.stdout
1267 def _GetDevInfo(cls, out):
1268 """Parse details about a given DRBD minor.
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
1280 bnf = cls._GetShowParser()
1284 results = bnf.parseString(out)
1285 except pyp.ParseException, err:
1286 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1288 # and massage the results into our desired format
1289 for section in results:
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:])
1306 def _MatchesLocal(self, info):
1307 """Test if our local config matches with an existing device.
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
1316 backend, meta = self._children
1318 backend = meta = None
1320 if backend is not None:
1321 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1323 retval = ("local_dev" not in info)
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)
1331 retval = retval and ("meta_dev" not in info and
1332 "meta_index" not in info)
1335 def _MatchesNet(self, info):
1336 """Test if our network config matches with an existing device.
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
1344 if (((self._lhost is None and not ("local_addr" in info)) and
1345 (self._rhost is None and not ("remote_addr" in info)))):
1348 if self._lhost is None:
1351 if not ("local_addr" in info and
1352 "remote_addr" in info):
1355 retval = (info["local_addr"] == (self._lhost, self._lport))
1356 retval = (retval and
1357 info["remote_addr"] == (self._rhost, self._rport))
1360 def _AssembleLocal(self, minor, backend, meta, size):
1361 """Configure the local part of a DRBD device.
1364 args = ["drbdsetup", self._DevPath(minor), "disk",
1369 args.extend(["-d", "%sm" % size])
1371 version = self._GetVersion(self._GetProcData())
1372 vmaj = version["k_major"]
1373 vmin = version["k_minor"]
1374 vrel = version["k_point"]
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)
1382 if self.params[constants.LDP_DISK_CUSTOM]:
1383 args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
1385 result = utils.RunCmd(args)
1387 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1390 def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
1391 disable_meta_flush):
1392 """Compute the DRBD command line parameters for disk barriers
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
1398 If the desired option is unsupported, raises errors.BlockDeviceError.
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)
1408 # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
1410 if not vmaj == 8 and vmin in (0, 2, 3):
1411 raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
1414 def _AppendOrRaise(option, min_version):
1415 """Helper for DRBD options"""
1416 if min_version is not None and vrel >= min_version:
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))
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
1426 meta_flush_supported = disk_flush_supported = {
1432 disk_drain_supported = {
1437 disk_barriers_supported = {
1442 if disable_meta_flush:
1443 _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
1444 meta_flush_supported.get(vmin, None))
1447 if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
1448 _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
1449 disk_flush_supported.get(vmin, None))
1452 if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
1453 _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
1454 disk_drain_supported.get(vmin, None))
1457 if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
1458 _AppendOrRaise(cls._DISABLE_DISK_OPTION,
1459 disk_barriers_supported.get(vmin, None))
1463 def _AssembleNet(self, minor, net_info, protocol,
1464 dual_pri=False, hmac=None, secret=None):
1465 """Configure the network part of the device.
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
1472 self._ShutdownNet(minor)
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
1481 sync_errors = self._SetMinorSyncParams(minor, self.params)
1483 _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1484 (minor, utils.CommaJoin(sync_errors)))
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))
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))
1497 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
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",
1509 args.extend(["-a", hmac, "-x", secret])
1511 if self.params[constants.LDP_NET_CUSTOM]:
1512 args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
1514 result = utils.RunCmd(args)
1516 _ThrowError("drbd%d: can't setup network: %s - %s",
1517 minor, result.fail_reason, result.output)
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()
1524 if (info["local_addr"] != (lhost, lport) or
1525 info["remote_addr"] != (rhost, rport)):
1526 raise utils.RetryAgain()
1529 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1530 except utils.RetryTimeout:
1531 _ThrowError("drbd%d: timeout while configuring network", minor)
1533 def AddChildren(self, devices):
1534 """Add a disk to the DRBD device.
1537 if self.minor is None:
1538 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
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)
1550 self._CheckMetaSize(meta.dev_path)
1551 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1553 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1554 self._children = devices
1556 def RemoveChildren(self, devices):
1557 """Detach the drbd device from local storage.
1560 if self.minor is None:
1561 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
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:
1567 if len(self._children) != 2:
1568 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1570 if self._children.count(None) == 2: # we don't actually have children :)
1571 logging.warning("drbd%d: requested detach while detached", self.minor)
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)
1580 self._ShutdownLocal(self.minor)
1584 def _SetMinorSyncParams(cls, minor, params):
1585 """Set the parameters of the DRBD syncer.
1587 This is the low-level implementation.
1590 @param minor: the drbd minor whose settings we change
1592 @param params: LD level disk parameters related to the synchronization
1594 @return: a list of error messages
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"]
1604 # By definition we are using 8.x, so just check the rest of the version
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))
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.")
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],
1628 args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
1630 args.append("--create-device")
1631 result = utils.RunCmd(args)
1633 msg = ("Can't change syncer rate: %s - %s" %
1634 (result.fail_reason, result.output))
1640 def SetSyncParams(self, params):
1641 """Set the synchronization parameters of the DRBD syncer.
1644 @param params: LD level disk parameters related to the synchronization
1646 @return: a list of error messages, emitted both by the current node and by
1647 children. An empty list means no errors
1650 if self.minor is None:
1651 err = "Not attached during SetSyncParams"
1655 children_result = super(DRBD8, self).SetSyncParams(params)
1656 children_result.extend(self._SetMinorSyncParams(self.minor, params))
1657 return children_result
1659 def PauseResumeSync(self, pause):
1660 """Pauses or resumes the sync of a DRBD device.
1662 @param pause: Wether to pause or resume
1663 @return: the success of the operation
1666 if self.minor is None:
1667 logging.info("Not attached during PauseSync")
1670 children_result = super(DRBD8, self).PauseResumeSync(pause)
1677 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1679 logging.error("Can't %s: %s - %s", cmd,
1680 result.fail_reason, result.output)
1681 return not result.failed and children_result
1683 def GetProcStatus(self):
1684 """Return device data from /proc.
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])
1694 def GetSyncStatus(self):
1695 """Returns the sync status of the device.
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.
1703 We set the is_degraded parameter to True on two conditions:
1704 network not connected or local disk missing.
1706 We compute the ldisk parameter based on whether we have a local
1709 @rtype: objects.BlockDevStatus
1712 if self.minor is None and not self.Attach():
1713 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1715 stats = self.GetProcStatus()
1716 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1718 if stats.is_disk_uptodate:
1719 ldisk_status = constants.LDS_OKAY
1720 elif stats.is_diskless:
1721 ldisk_status = constants.LDS_FAULTY
1723 ldisk_status = constants.LDS_UNKNOWN
1725 return objects.BlockDevStatus(dev_path=self.dev_path,
1728 sync_percent=stats.sync_percent,
1729 estimated_time=stats.est_time,
1730 is_degraded=is_degraded,
1731 ldisk_status=ldisk_status)
1733 def Open(self, force=False):
1734 """Make the local state primary.
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
1742 if self.minor is None and not self.Attach():
1743 logging.error("DRBD cannot attach to a device during open")
1745 cmd = ["drbdsetup", self.dev_path, "primary"]
1748 result = utils.RunCmd(cmd)
1750 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1754 """Make the local state secondary.
1756 This will, of course, fail if the device is in use.
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"])
1763 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1764 self.minor, result.output)
1766 def DisconnectNet(self):
1767 """Removes network configuration.
1769 This method shutdowns the network side of the device.
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
1780 if self.minor is None:
1781 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
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)
1787 class _DisconnectStatus:
1788 def __init__(self, ever_disconnected):
1789 self.ever_disconnected = ever_disconnected
1791 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1793 def _WaitForDisconnect():
1794 if self.GetProcStatus().is_standalone:
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
1800 dstatus.ever_disconnected = \
1801 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1803 raise utils.RetryAgain()
1806 start_time = time.time()
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")
1817 msg = "drbd%d: can't shutdown network, even after multiple retries"
1819 _ThrowError(msg, self.minor)
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)
1826 def AttachNet(self, multimaster):
1827 """Reconnects the network.
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.
1834 - multimaster: init the network in dual-primary mode
1837 if self.minor is None:
1838 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1840 if None in (self._lhost, self._lport, self._rhost, self._rport):
1841 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1843 status = self.GetProcStatus()
1845 if not status.is_standalone:
1846 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
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)
1854 """Check if our minor is configured.
1856 This doesn't do any device configurations - it only checks if the
1857 minor is in a state different from Unconfigured.
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
1864 used_devs = self.GetUsedDevs()
1865 if self._aminor in used_devs:
1866 minor = self._aminor
1870 self._SetFromMinor(minor)
1871 return minor is not None
1874 """Assemble the drbd.
1877 - if we have a configured device, we try to ensure that it matches
1879 - if not, we create it from zero
1880 - anyway, set the device parameters
1883 super(DRBD8, self).Assemble()
1886 if self.minor is None:
1887 # local device completely unconfigured
1888 self._FastAssemble()
1890 # we have to recheck the local and network status and try to fix
1892 self._SlowAssemble()
1894 sync_errors = self.SetSyncParams(self.params)
1896 _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1897 (self.minor, utils.CommaJoin(sync_errors)))
1899 def _SlowAssemble(self):
1900 """Assembles the DRBD device from a (partially) configured device.
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.
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)
1915 if match_l and match_r:
1916 # everything matches
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))):
1926 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1927 " show' disagrees", minor)
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))):
1936 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1937 " show' disagrees", minor)
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
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
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
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))):
1963 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1964 " show' disagrees", minor)
1969 self._SetFromMinor(minor)
1971 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1974 def _FastAssemble(self):
1975 """Assemble the drbd device from zero.
1977 This is run when in Assemble we detect our minor is unused.
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)
1992 def _ShutdownLocal(cls, minor):
1993 """Detach from the local device.
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.
1999 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2001 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
2004 def _ShutdownNet(cls, minor):
2005 """Disconnect from the remote peer.
2007 This fails if we don't have a local device.
2010 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2012 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
2015 def _ShutdownAll(cls, minor):
2016 """Deactivate the device.
2018 This will, of course, fail if the device is in use.
2021 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2023 _ThrowError("drbd%d: can't shutdown drbd device: %s",
2024 minor, result.output)
2027 """Shutdown the DRBD device.
2030 if self.minor is None and not self.Attach():
2031 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
2035 self.dev_path = None
2036 self._ShutdownAll(minor)
2039 """Stub remove for DRBD devices.
2045 def Create(cls, unique_id, children, size, params):
2046 """Create a new DRBD8 device.
2048 Since DRBD devices are not created per se, just assembled, this
2049 function only initializes the metadata.
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
2063 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
2066 if not meta.Attach():
2067 _ThrowError("drbd%d: can't attach to meta device '%s'",
2069 cls._CheckMetaSize(meta.dev_path)
2070 cls._InitMeta(aminor, meta.dev_path)
2071 return cls(unique_id, children, size, params)
2073 def Grow(self, amount, dryrun):
2074 """Resize the DRBD device and its backing storage.
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)
2083 # DRBD does not support dry-run mode, so we'll return here
2085 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
2086 "%dm" % (self.size + amount)])
2088 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
2091 class FileStorage(BlockDev):
2094 This class represents the a file storage backend device.
2096 The unique_id for the file device is a (file_driver, file_path) tuple.
2099 def __init__(self, unique_id, children, size, params):
2100 """Initalizes a file device backend.
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]
2113 """Assemble the device.
2115 Checks whether the file device exists, raises BlockDeviceError otherwise.
2118 if not os.path.exists(self.dev_path):
2119 _ThrowError("File device '%s' does not exist" % self.dev_path)
2122 """Shutdown the device.
2124 This is a no-op for the file type, as we don't deactivate
2125 the file on shutdown.
2130 def Open(self, force=False):
2131 """Make the device ready for I/O.
2133 This is a no-op for the file type.
2139 """Notifies that the device will no longer be used for I/O.
2141 This is a no-op for the file type.
2147 """Remove the file backing the block device.
2150 @return: True if the removal was successful
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)
2159 def Rename(self, new_id):
2160 """Renames the file.
2163 # TODO: implement rename for file-based storage
2164 _ThrowError("Rename is not supported for file-based storage")
2166 def Grow(self, amount, dryrun):
2169 @param amount: the amount (in mebibytes) to grow with
2172 # Check that the file exists
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
2181 f = open(self.dev_path, "a+")
2182 f.truncate(new_size)
2184 except EnvironmentError, err:
2185 _ThrowError("Error in file growth: %", str(err))
2188 """Attach to an existing file.
2190 Check if this file already exists.
2193 @return: True if file exists
2196 self.attached = os.path.exists(self.dev_path)
2197 return self.attached
2199 def GetActualSize(self):
2200 """Return the actual disk size.
2202 @note: the device needs to be active when this is called
2205 assert self.attached, "BlockDevice not attached in GetActualSize()"
2207 st = os.stat(self.dev_path)
2209 except OSError, err:
2210 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2213 def Create(cls, unique_id, children, size, params):
2214 """Create a new file.
2216 @param size: the size of file in MiB
2218 @rtype: L{bdev.FileStorage}
2219 @return: an instance of FileStorage
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]
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)
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))
2235 return FileStorage(unique_id, children, size, params)
2238 class PersistentBlockDevice(BlockDev):
2239 """A block device with persistent node
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
2245 For the time being, pathnames are required to lie under /dev.
2248 def __init__(self, unique_id, children, size, params):
2249 """Attaches to a static block device.
2251 The unique_id is a path under /dev.
2254 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
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" %
2270 self.major = self.minor = None
2274 def Create(cls, unique_id, children, size, params):
2275 """Create a new device
2277 This is a noop, we only return a PersistentBlockDevice instance
2280 return PersistentBlockDevice(unique_id, children, 0, params)
2290 def Rename(self, new_id):
2291 """Rename this device.
2294 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2297 """Attach to an existing block device.
2301 self.attached = False
2303 st = os.stat(self.dev_path)
2304 except OSError, err:
2305 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2308 if not stat.S_ISBLK(st.st_mode):
2309 logging.error("%s is not a block device", self.dev_path)
2312 self.major = os.major(st.st_rdev)
2313 self.minor = os.minor(st.st_rdev)
2314 self.attached = True
2319 """Assemble the device.
2325 """Shutdown the device.
2330 def Open(self, force=False):
2331 """Make the device ready for I/O.
2337 """Notifies that the device will no longer be used for I/O.
2342 def Grow(self, amount, dryrun):
2343 """Grow the logical volume.
2346 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2349 class RADOSBlockDevice(BlockDev):
2350 """A RADOS Block Device (rbd).
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.
2357 def __init__(self, unique_id, children, size, params):
2358 """Attaches to an rbd device.
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))
2365 self.driver, self.rbd_name = unique_id
2367 self.major = self.minor = None
2371 def Create(cls, unique_id, children, size, params):
2372 """Create a new rbd device.
2374 Provision a new rbd volume inside a RADOS pool.
2377 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2378 raise errors.ProgrammerError("Invalid configuration data %s" %
2380 rbd_pool = params[constants.LDP_POOL]
2381 rbd_name = unique_id[1]
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)
2388 _ThrowError("rbd creation failed (%s): %s",
2389 result.fail_reason, result.output)
2391 return RADOSBlockDevice(unique_id, children, size, params)
2394 """Remove the rbd device.
2397 rbd_pool = self.params[constants.LDP_POOL]
2398 rbd_name = self.unique_id[1]
2400 if not self.minor and not self.Attach():
2401 # The rbd device doesn't exist.
2404 # First shutdown the device (remove mappings).
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)
2411 _ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
2412 result.fail_reason, result.output)
2414 def Rename(self, new_id):
2415 """Rename this device.
2421 """Attach to an existing rbd device.
2423 This method maps the rbd volume that matches our name with
2424 an rbd device and then attaches to this device.
2427 self.attached = False
2429 # Map the rbd volume to a block device under /dev
2430 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
2433 st = os.stat(self.dev_path)
2434 except OSError, err:
2435 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2438 if not stat.S_ISBLK(st.st_mode):
2439 logging.error("%s is not a block device", self.dev_path)
2442 self.major = os.major(st.st_rdev)
2443 self.minor = os.minor(st.st_rdev)
2444 self.attached = True
2448 def _MapVolumeToBlockdev(self, unique_id):
2449 """Maps existing rbd volumes to block devices.
2451 This method should be idempotent if the mapping already exists.
2454 @return: the block device path that corresponds to the volume
2457 pool = self.params[constants.LDP_POOL]
2460 # Check if the mapping already exists.
2461 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2462 result = utils.RunCmd(showmap_cmd)
2464 _ThrowError("rbd showmapped failed (%s): %s",
2465 result.fail_reason, result.output)
2467 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2470 # The mapping exists. Return it.
2473 # The mapping doesn't exist. Create it.
2474 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
2475 result = utils.RunCmd(map_cmd)
2477 _ThrowError("rbd map failed (%s): %s",
2478 result.fail_reason, result.output)
2480 # Find the corresponding rbd device.
2481 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2482 result = utils.RunCmd(showmap_cmd)
2484 _ThrowError("rbd map succeeded, but showmapped failed (%s): %s",
2485 result.fail_reason, result.output)
2487 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2490 _ThrowError("rbd map succeeded, but could not find the rbd block"
2491 " device in output of showmapped, for volume: %s", name)
2493 # The device was successfully mapped. Return it.
2497 def _ParseRbdShowmappedOutput(output, volume_name):
2498 """Parse the output of `rbd showmapped'.
2500 This method parses the output of `rbd showmapped' and returns
2501 the rbd block device path (e.g. /dev/rbd0) that matches the
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
2518 lines = output.splitlines()
2519 splitted_lines = map(lambda l: l.split(field_sep), lines)
2521 # Check empty output.
2522 if not splitted_lines:
2523 _ThrowError("rbd showmapped returned empty output")
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)
2533 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
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)
2542 # rbd block device found. Return it.
2543 rbd_dev = matched_lines[0][devicefield]
2546 # The given volume is not mapped.
2550 """Assemble the device.
2556 """Shutdown the device.
2559 if not self.minor and not self.Attach():
2560 # The rbd device doesn't exist.
2563 # Unmap the block device from the Volume.
2564 self._UnmapVolumeFromBlockdev(self.unique_id)
2567 self.dev_path = None
2569 def _UnmapVolumeFromBlockdev(self, unique_id):
2570 """Unmaps the rbd device from the Volume it is mapped.
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.
2576 pool = self.params[constants.LDP_POOL]
2579 # Check if the mapping already exists.
2580 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2581 result = utils.RunCmd(showmap_cmd)
2583 _ThrowError("rbd showmapped failed [during unmap](%s): %s",
2584 result.fail_reason, result.output)
2586 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2589 # The mapping exists. Unmap the rbd device.
2590 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
2591 result = utils.RunCmd(unmap_cmd)
2593 _ThrowError("rbd unmap failed (%s): %s",
2594 result.fail_reason, result.output)
2596 def Open(self, force=False):
2597 """Make the device ready for I/O.
2603 """Notifies that the device will no longer be used for I/O.
2608 def Grow(self, amount, dryrun):
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
2618 if not self.Attach():
2619 _ThrowError("Can't attach to rbd device during Grow()")
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.
2627 rbd_pool = self.params[constants.LDP_POOL]
2628 rbd_name = self.unique_id[1]
2629 new_size = self.size + amount
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)
2636 _ThrowError("rbd resize failed (%s): %s",
2637 result.fail_reason, result.output)
2641 constants.LD_LV: LogicalVolume,
2642 constants.LD_DRBD8: DRBD8,
2643 constants.LD_BLOCKDEV: PersistentBlockDevice,
2644 constants.LD_RBD: RADOSBlockDevice,
2647 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2648 DEV_MAP[constants.LD_FILE] = FileStorage
2651 def _VerifyDiskType(dev_type):
2652 if dev_type not in DEV_MAP:
2653 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2656 def FindDevice(disk, children):
2657 """Search for an existing, assembled device.
2659 This will succeed only if the device exists and is assembled, but it
2660 does not do any actions in order to activate the device.
2662 @type disk: L{objects.Disk}
2663 @param disk: the disk object to find
2664 @type children: list of L{bdev.BlockDev}
2665 @param children: the list of block devices that are children of the device
2666 represented by the disk parameter
2669 _VerifyDiskType(disk.dev_type)
2670 dev_params = objects.FillDict(constants.DISK_LD_DEFAULTS[disk.dev_type],
2672 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2674 if not device.attached:
2679 def Assemble(disk, children):
2680 """Try to attach or assemble an existing device.
2682 This will attach to assemble the device, as needed, to bring it
2683 fully up. It must be safe to run on already-assembled devices.
2685 @type disk: L{objects.Disk}
2686 @param disk: the disk object to assemble
2687 @type children: list of L{bdev.BlockDev}
2688 @param children: the list of block devices that are children of the device
2689 represented by the disk parameter
2692 _VerifyDiskType(disk.dev_type)
2693 dev_params = objects.FillDict(constants.DISK_LD_DEFAULTS[disk.dev_type],
2695 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2701 def Create(disk, children):
2704 @type disk: L{objects.Disk}
2705 @param disk: the disk object to create
2706 @type children: list of L{bdev.BlockDev}
2707 @param children: the list of block devices that are children of the device
2708 represented by the disk parameter
2711 _VerifyDiskType(disk.dev_type)
2712 dev_params = objects.FillDict(constants.DISK_LD_DEFAULTS[disk.dev_type],
2714 device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,