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 SetSyncSpeed(self, speed):
225 """Adjust the sync speed of the mirror.
227 In case this is not a mirroring device, this is no-op.
232 for child in self._children:
233 result = result and child.SetSyncSpeed(speed)
236 def PauseResumeSync(self, pause):
237 """Pause/Resume the sync of the mirror.
239 In case this is not a mirroring device, this is no-op.
241 @param pause: Wheater to pause or resume
246 for child in self._children:
247 result = result and child.PauseResumeSync(pause)
250 def GetSyncStatus(self):
251 """Returns the sync status of the device.
253 If this device is a mirroring device, this function returns the
254 status of the mirror.
256 If sync_percent is None, it means the device is not syncing.
258 If estimated_time is None, it means we can't estimate
259 the time needed, otherwise it's the time left in seconds.
261 If is_degraded is True, it means the device is missing
262 redundancy. This is usually a sign that something went wrong in
263 the device setup, if sync_percent is None.
265 The ldisk parameter represents the degradation of the local
266 data. This is only valid for some devices, the rest will always
267 return False (not degraded).
269 @rtype: objects.BlockDevStatus
272 return objects.BlockDevStatus(dev_path=self.dev_path,
278 ldisk_status=constants.LDS_OKAY)
280 def CombinedSyncStatus(self):
281 """Calculate the mirror status recursively for our children.
283 The return value is the same as for `GetSyncStatus()` except the
284 minimum percent and maximum time are calculated across our
287 @rtype: objects.BlockDevStatus
290 status = self.GetSyncStatus()
292 min_percent = status.sync_percent
293 max_time = status.estimated_time
294 is_degraded = status.is_degraded
295 ldisk_status = status.ldisk_status
298 for child in self._children:
299 child_status = child.GetSyncStatus()
301 if min_percent is None:
302 min_percent = child_status.sync_percent
303 elif child_status.sync_percent is not None:
304 min_percent = min(min_percent, child_status.sync_percent)
307 max_time = child_status.estimated_time
308 elif child_status.estimated_time is not None:
309 max_time = max(max_time, child_status.estimated_time)
311 is_degraded = is_degraded or child_status.is_degraded
313 if ldisk_status is None:
314 ldisk_status = child_status.ldisk_status
315 elif child_status.ldisk_status is not None:
316 ldisk_status = max(ldisk_status, child_status.ldisk_status)
318 return objects.BlockDevStatus(dev_path=self.dev_path,
321 sync_percent=min_percent,
322 estimated_time=max_time,
323 is_degraded=is_degraded,
324 ldisk_status=ldisk_status)
326 def SetInfo(self, text):
327 """Update metadata with info text.
329 Only supported for some device types.
332 for child in self._children:
335 def Grow(self, amount, dryrun):
336 """Grow the block device.
338 @type amount: integer
339 @param amount: the amount (in mebibytes) to grow with
340 @type dryrun: boolean
341 @param dryrun: whether to execute the operation in simulation mode
342 only, without actually increasing the size
345 raise NotImplementedError
347 def GetActualSize(self):
348 """Return the actual disk size.
350 @note: the device needs to be active when this is called
353 assert self.attached, "BlockDevice not attached in GetActualSize()"
354 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
356 _ThrowError("blockdev failed (%s): %s",
357 result.fail_reason, result.output)
359 sz = int(result.output.strip())
360 except (ValueError, TypeError), err:
361 _ThrowError("Failed to parse blockdev output: %s", str(err))
365 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
366 (self.__class__, self.unique_id, self._children,
367 self.major, self.minor, self.dev_path))
370 class LogicalVolume(BlockDev):
371 """Logical Volume block device.
374 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
375 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
376 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
378 def __init__(self, unique_id, children, size, params):
379 """Attaches to a LV device.
381 The unique_id is a tuple (vg_name, lv_name)
384 super(LogicalVolume, self).__init__(unique_id, children, size, params)
385 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
386 raise ValueError("Invalid configuration data %s" % str(unique_id))
387 self._vg_name, self._lv_name = unique_id
388 self._ValidateName(self._vg_name)
389 self._ValidateName(self._lv_name)
390 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
391 self._degraded = True
392 self.major = self.minor = self.pe_size = self.stripe_count = None
396 def Create(cls, unique_id, children, size, params):
397 """Create a new logical volume.
400 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
401 raise errors.ProgrammerError("Invalid configuration data %s" %
403 vg_name, lv_name = unique_id
404 cls._ValidateName(vg_name)
405 cls._ValidateName(lv_name)
406 pvs_info = cls.GetPVInfo([vg_name])
408 _ThrowError("Can't compute PV info for vg %s", vg_name)
412 pvlist = [pv[1] for pv in pvs_info]
413 if compat.any(":" in v for v in pvlist):
414 _ThrowError("Some of your PVs have the invalid character ':' in their"
415 " name, this is not supported - please filter them out"
416 " in lvm.conf using either 'filter' or 'preferred_names'")
417 free_size = sum([pv[0] for pv in pvs_info])
418 current_pvs = len(pvlist)
419 desired_stripes = params[constants.LDP_STRIPES]
420 stripes = min(current_pvs, desired_stripes)
421 if stripes < desired_stripes:
422 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
423 " available.", desired_stripes, vg_name, current_pvs)
425 # The size constraint should have been checked from the master before
426 # calling the create function.
428 _ThrowError("Not enough free space: required %s,"
429 " available %s", size, free_size)
430 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
431 # If the free space is not well distributed, we won't be able to
432 # create an optimally-striped volume; in that case, we want to try
433 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
435 for stripes_arg in range(stripes, 0, -1):
436 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
437 if not result.failed:
440 _ThrowError("LV create failed (%s): %s",
441 result.fail_reason, result.output)
442 return LogicalVolume(unique_id, children, size, params)
445 def _GetVolumeInfo(lvm_cmd, fields):
446 """Returns LVM Volumen infos using lvm_cmd
448 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
449 @param fields: Fields to return
450 @return: A list of dicts each with the parsed fields
454 raise errors.ProgrammerError("No fields specified")
457 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
458 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
460 result = utils.RunCmd(cmd)
462 raise errors.CommandError("Can't get the volume information: %s - %s" %
463 (result.fail_reason, result.output))
466 for line in result.stdout.splitlines():
467 splitted_fields = line.strip().split(sep)
469 if len(fields) != len(splitted_fields):
470 raise errors.CommandError("Can't parse %s output: line '%s'" %
473 data.append(splitted_fields)
478 def GetPVInfo(cls, vg_names, filter_allocatable=True):
479 """Get the free space info for PVs in a volume group.
481 @param vg_names: list of volume group names, if empty all will be returned
482 @param filter_allocatable: whether to skip over unallocatable PVs
485 @return: list of tuples (free_space, name) with free_space in mebibytes
489 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
491 except errors.GenericError, err:
492 logging.error("Can't get PV information: %s", err)
496 for pv_name, vg_name, pv_free, pv_attr in info:
497 # (possibly) skip over pvs which are not allocatable
498 if filter_allocatable and pv_attr[0] != "a":
500 # (possibly) skip over pvs which are not in the right volume group(s)
501 if vg_names and vg_name not in vg_names:
503 data.append((float(pv_free), pv_name, vg_name))
508 def GetVGInfo(cls, vg_names, filter_readonly=True):
509 """Get the free space info for specific VGs.
511 @param vg_names: list of volume group names, if empty all will be returned
512 @param filter_readonly: whether to skip over readonly VGs
515 @return: list of tuples (free_space, total_size, name) with free_space in
520 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
522 except errors.GenericError, err:
523 logging.error("Can't get VG information: %s", err)
527 for vg_name, vg_free, vg_attr, vg_size in info:
528 # (possibly) skip over vgs which are not writable
529 if filter_readonly and vg_attr[0] == "r":
531 # (possibly) skip over vgs which are not in the right volume group(s)
532 if vg_names and vg_name not in vg_names:
534 data.append((float(vg_free), float(vg_size), vg_name))
539 def _ValidateName(cls, name):
540 """Validates that a given name is valid as VG or LV name.
542 The list of valid characters and restricted names is taken out of
543 the lvm(8) manpage, with the simplification that we enforce both
544 VG and LV restrictions on the names.
547 if (not cls._VALID_NAME_RE.match(name) or
548 name in cls._INVALID_NAMES or
549 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
550 _ThrowError("Invalid LVM name '%s'", name)
553 """Remove this logical volume.
556 if not self.minor and not self.Attach():
557 # the LV does not exist
559 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
560 (self._vg_name, self._lv_name)])
562 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
564 def Rename(self, new_id):
565 """Rename this logical volume.
568 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
569 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
570 new_vg, new_name = new_id
571 if new_vg != self._vg_name:
572 raise errors.ProgrammerError("Can't move a logical volume across"
573 " volume groups (from %s to to %s)" %
574 (self._vg_name, new_vg))
575 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
577 _ThrowError("Failed to rename the logical volume: %s", result.output)
578 self._lv_name = new_name
579 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
582 """Attach to an existing LV.
584 This method will try to see if an existing and active LV exists
585 which matches our name. If so, its major/minor will be
589 self.attached = False
590 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
591 "--units=m", "--nosuffix",
592 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
593 "vg_extent_size,stripes", self.dev_path])
595 logging.error("Can't find LV %s: %s, %s",
596 self.dev_path, result.fail_reason, result.output)
598 # the output can (and will) have multiple lines for multi-segment
599 # LVs, as the 'stripes' parameter is a segment one, so we take
600 # only the last entry, which is the one we're interested in; note
601 # that with LVM2 anyway the 'stripes' value must be constant
602 # across segments, so this is a no-op actually
603 out = result.stdout.splitlines()
604 if not out: # totally empty result? splitlines() returns at least
605 # one line for any non-empty string
606 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
608 out = out[-1].strip().rstrip(",")
611 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
614 status, major, minor, pe_size, stripes = out
616 logging.error("lvs lv_attr is not 6 characters (%s)", status)
622 except (TypeError, ValueError), err:
623 logging.error("lvs major/minor cannot be parsed: %s", str(err))
626 pe_size = int(float(pe_size))
627 except (TypeError, ValueError), err:
628 logging.error("Can't parse vg extent size: %s", err)
632 stripes = int(stripes)
633 except (TypeError, ValueError), err:
634 logging.error("Can't parse the number of stripes: %s", err)
639 self.pe_size = pe_size
640 self.stripe_count = stripes
641 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
647 """Assemble the device.
649 We always run `lvchange -ay` on the LV to ensure it's active before
650 use, as there were cases when xenvg was not active after boot
651 (also possibly after disk issues).
654 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
656 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
659 """Shutdown the device.
661 This is a no-op for the LV device type, as we don't deactivate the
667 def GetSyncStatus(self):
668 """Returns the sync status of the device.
670 If this device is a mirroring device, this function returns the
671 status of the mirror.
673 For logical volumes, sync_percent and estimated_time are always
674 None (no recovery in progress, as we don't handle the mirrored LV
675 case). The is_degraded parameter is the inverse of the ldisk
678 For the ldisk parameter, we check if the logical volume has the
679 'virtual' type, which means it's not backed by existing storage
680 anymore (read from it return I/O error). This happens after a
681 physical disk failure and subsequent 'vgreduce --removemissing' on
684 The status was already read in Attach, so we just return it.
686 @rtype: objects.BlockDevStatus
690 ldisk_status = constants.LDS_FAULTY
692 ldisk_status = constants.LDS_OKAY
694 return objects.BlockDevStatus(dev_path=self.dev_path,
699 is_degraded=self._degraded,
700 ldisk_status=ldisk_status)
702 def Open(self, force=False):
703 """Make the device ready for I/O.
705 This is a no-op for the LV device type.
711 """Notifies that the device will no longer be used for I/O.
713 This is a no-op for the LV device type.
718 def Snapshot(self, size):
719 """Create a snapshot copy of an lvm block device.
721 @returns: tuple (vg, lv)
724 snap_name = self._lv_name + ".snap"
726 # remove existing snapshot if found
727 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
728 _IgnoreError(snap.Remove)
730 vg_info = self.GetVGInfo([self._vg_name])
732 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
733 free_size, _, _ = vg_info[0]
735 _ThrowError("Not enough free space: required %s,"
736 " available %s", size, free_size)
738 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
739 "-n%s" % snap_name, self.dev_path])
741 _ThrowError("command: %s error: %s - %s",
742 result.cmd, result.fail_reason, result.output)
744 return (self._vg_name, snap_name)
746 def SetInfo(self, text):
747 """Update metadata with info text.
750 BlockDev.SetInfo(self, text)
752 # Replace invalid characters
753 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
754 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
756 # Only up to 128 characters are allowed
759 result = utils.RunCmd(["lvchange", "--addtag", text,
762 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
765 def Grow(self, amount, dryrun):
766 """Grow the logical volume.
769 if self.pe_size is None or self.stripe_count is None:
770 if not self.Attach():
771 _ThrowError("Can't attach to LV during Grow()")
772 full_stripe_size = self.pe_size * self.stripe_count
773 rest = amount % full_stripe_size
775 amount += full_stripe_size - rest
776 cmd = ["lvextend", "-L", "+%dm" % amount]
779 # we try multiple algorithms since the 'best' ones might not have
780 # space available in the right place, but later ones might (since
781 # they have less constraints); also note that only recent LVM
783 for alloc_policy in "contiguous", "cling", "normal":
784 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
785 if not result.failed:
787 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
790 class DRBD8Status(object):
791 """A DRBD status representation class.
793 Note that this doesn't support unconfigured devices (cs:Unconfigured).
796 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
797 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
798 "\s+ds:([^/]+)/(\S+)\s+.*$")
799 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
800 # Due to a bug in drbd in the kernel, introduced in
801 # commit 4b0715f096 (still unfixed as of 2011-08-22)
803 "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
805 CS_UNCONFIGURED = "Unconfigured"
806 CS_STANDALONE = "StandAlone"
807 CS_WFCONNECTION = "WFConnection"
808 CS_WFREPORTPARAMS = "WFReportParams"
809 CS_CONNECTED = "Connected"
810 CS_STARTINGSYNCS = "StartingSyncS"
811 CS_STARTINGSYNCT = "StartingSyncT"
812 CS_WFBITMAPS = "WFBitMapS"
813 CS_WFBITMAPT = "WFBitMapT"
814 CS_WFSYNCUUID = "WFSyncUUID"
815 CS_SYNCSOURCE = "SyncSource"
816 CS_SYNCTARGET = "SyncTarget"
817 CS_PAUSEDSYNCS = "PausedSyncS"
818 CS_PAUSEDSYNCT = "PausedSyncT"
819 CSET_SYNC = frozenset([
832 DS_DISKLESS = "Diskless"
833 DS_ATTACHING = "Attaching" # transient state
834 DS_FAILED = "Failed" # transient state, next: diskless
835 DS_NEGOTIATING = "Negotiating" # transient state
836 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
837 DS_OUTDATED = "Outdated"
838 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
839 DS_CONSISTENT = "Consistent"
840 DS_UPTODATE = "UpToDate" # normal state
842 RO_PRIMARY = "Primary"
843 RO_SECONDARY = "Secondary"
844 RO_UNKNOWN = "Unknown"
846 def __init__(self, procline):
847 u = self.UNCONF_RE.match(procline)
849 self.cstatus = self.CS_UNCONFIGURED
850 self.lrole = self.rrole = self.ldisk = self.rdisk = None
852 m = self.LINE_RE.match(procline)
854 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
855 self.cstatus = m.group(1)
856 self.lrole = m.group(2)
857 self.rrole = m.group(3)
858 self.ldisk = m.group(4)
859 self.rdisk = m.group(5)
861 # end reading of data from the LINE_RE or UNCONF_RE
863 self.is_standalone = self.cstatus == self.CS_STANDALONE
864 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
865 self.is_connected = self.cstatus == self.CS_CONNECTED
866 self.is_primary = self.lrole == self.RO_PRIMARY
867 self.is_secondary = self.lrole == self.RO_SECONDARY
868 self.peer_primary = self.rrole == self.RO_PRIMARY
869 self.peer_secondary = self.rrole == self.RO_SECONDARY
870 self.both_primary = self.is_primary and self.peer_primary
871 self.both_secondary = self.is_secondary and self.peer_secondary
873 self.is_diskless = self.ldisk == self.DS_DISKLESS
874 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
876 self.is_in_resync = self.cstatus in self.CSET_SYNC
877 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
879 m = self.SYNC_RE.match(procline)
881 self.sync_percent = float(m.group(1))
882 hours = int(m.group(2))
883 minutes = int(m.group(3))
884 seconds = int(m.group(4))
885 self.est_time = hours * 3600 + minutes * 60 + seconds
887 # we have (in this if branch) no percent information, but if
888 # we're resyncing we need to 'fake' a sync percent information,
889 # as this is how cmdlib determines if it makes sense to wait for
891 if self.is_in_resync:
892 self.sync_percent = 0
894 self.sync_percent = None
898 class BaseDRBD(BlockDev): # pylint: disable=W0223
901 This class contains a few bits of common functionality between the
902 0.7 and 8.x versions of DRBD.
905 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
906 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
907 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
908 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
911 _ST_UNCONFIGURED = "Unconfigured"
912 _ST_WFCONNECTION = "WFConnection"
913 _ST_CONNECTED = "Connected"
915 _STATUS_FILE = "/proc/drbd"
916 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
919 def _GetProcData(filename=_STATUS_FILE):
920 """Return data from /proc/drbd.
924 data = utils.ReadFile(filename).splitlines()
925 except EnvironmentError, err:
926 if err.errno == errno.ENOENT:
927 _ThrowError("The file %s cannot be opened, check if the module"
928 " is loaded (%s)", filename, str(err))
930 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
932 _ThrowError("Can't read any data from %s", filename)
936 def _MassageProcData(cls, data):
937 """Transform the output of _GetProdData into a nicer form.
939 @return: a dictionary of minor: joined lines from /proc/drbd
944 old_minor = old_line = None
946 if not line: # completely empty lines, as can be returned by drbd8.0+
948 lresult = cls._VALID_LINE_RE.match(line)
949 if lresult is not None:
950 if old_minor is not None:
951 results[old_minor] = old_line
952 old_minor = int(lresult.group(1))
955 if old_minor is not None:
956 old_line += " " + line.strip()
958 if old_minor is not None:
959 results[old_minor] = old_line
963 def _GetVersion(cls, proc_data):
964 """Return the DRBD version.
966 This will return a dict with keys:
972 - proto2 (only on drbd > 8.2.X)
975 first_line = proc_data[0].strip()
976 version = cls._VERSION_RE.match(first_line)
978 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
981 values = version.groups()
982 retval = {"k_major": int(values[0]),
983 "k_minor": int(values[1]),
984 "k_point": int(values[2]),
985 "api": int(values[3]),
986 "proto": int(values[4]),
988 if values[5] is not None:
989 retval["proto2"] = values[5]
994 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
995 """Returns DRBD usermode_helper currently set.
999 helper = utils.ReadFile(filename).splitlines()[0]
1000 except EnvironmentError, err:
1001 if err.errno == errno.ENOENT:
1002 _ThrowError("The file %s cannot be opened, check if the module"
1003 " is loaded (%s)", filename, str(err))
1005 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1007 _ThrowError("Can't read any data from %s", filename)
1011 def _DevPath(minor):
1012 """Return the path to a drbd device for a given minor.
1015 return "/dev/drbd%d" % minor
1018 def GetUsedDevs(cls):
1019 """Compute the list of used DRBD devices.
1022 data = cls._GetProcData()
1026 match = cls._VALID_LINE_RE.match(line)
1029 minor = int(match.group(1))
1030 state = match.group(2)
1031 if state == cls._ST_UNCONFIGURED:
1033 used_devs[minor] = state, line
1037 def _SetFromMinor(self, minor):
1038 """Set our parameters based on the given minor.
1040 This sets our minor variable and our dev_path.
1044 self.minor = self.dev_path = None
1045 self.attached = False
1048 self.dev_path = self._DevPath(minor)
1049 self.attached = True
1052 def _CheckMetaSize(meta_device):
1053 """Check if the given meta device looks like a valid one.
1055 This currently only check the size, which must be around
1059 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1061 _ThrowError("Failed to get device size: %s - %s",
1062 result.fail_reason, result.output)
1064 sectors = int(result.stdout)
1065 except (TypeError, ValueError):
1066 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1067 num_bytes = sectors * 512
1068 if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1069 _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1070 # the maximum *valid* size of the meta device when living on top
1071 # of LVM is hard to compute: it depends on the number of stripes
1072 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1073 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1074 # size meta device; as such, we restrict it to 1GB (a little bit
1075 # too generous, but making assumptions about PE size is hard)
1076 if num_bytes > 1024 * 1024 * 1024:
1077 _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1079 def Rename(self, new_id):
1082 This is not supported for drbd devices.
1085 raise errors.ProgrammerError("Can't rename a drbd device")
1088 class DRBD8(BaseDRBD):
1089 """DRBD v8.x block device.
1091 This implements the local host part of the DRBD device, i.e. it
1092 doesn't do anything to the supposed peer. If you need a fully
1093 connected DRBD pair, you need to use this class on both hosts.
1095 The unique_id for the drbd device is the (local_ip, local_port,
1096 remote_ip, remote_port) tuple, and it must have two children: the
1097 data device and the meta_device. The meta device is checked for
1098 valid size and is zeroed on create.
1105 _NET_RECONFIG_TIMEOUT = 60
1107 # command line options for barriers
1108 _DISABLE_DISK_OPTION = "--no-disk-barrier" # -a
1109 _DISABLE_DRAIN_OPTION = "--no-disk-drain" # -D
1110 _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
1111 _DISABLE_META_FLUSH_OPTION = "--no-md-flushes" # -m
1113 def __init__(self, unique_id, children, size, params):
1114 if children and children.count(None) > 0:
1116 if len(children) not in (0, 2):
1117 raise ValueError("Invalid configuration data %s" % str(children))
1118 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1119 raise ValueError("Invalid configuration data %s" % str(unique_id))
1120 (self._lhost, self._lport,
1121 self._rhost, self._rport,
1122 self._aminor, self._secret) = unique_id
1124 if not _CanReadDevice(children[1].dev_path):
1125 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1127 super(DRBD8, self).__init__(unique_id, children, size, params)
1128 self.major = self._DRBD_MAJOR
1129 version = self._GetVersion(self._GetProcData())
1130 if version["k_major"] != 8:
1131 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1132 " usage: kernel is %s.%s, ganeti wants 8.x",
1133 version["k_major"], version["k_minor"])
1135 if (self._lhost is not None and self._lhost == self._rhost and
1136 self._lport == self._rport):
1137 raise ValueError("Invalid configuration data, same local/remote %s" %
1142 def _InitMeta(cls, minor, dev_path):
1143 """Initialize a meta device.
1145 This will not work if the given minor is in use.
1148 # Zero the metadata first, in order to make sure drbdmeta doesn't
1149 # try to auto-detect existing filesystems or similar (see
1150 # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1151 # care about the first 128MB of data in the device, even though it
1153 result = utils.RunCmd([constants.DD_CMD,
1154 "if=/dev/zero", "of=%s" % dev_path,
1155 "bs=1048576", "count=128", "oflag=direct"])
1157 _ThrowError("Can't wipe the meta device: %s", result.output)
1159 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1160 "v08", dev_path, "0", "create-md"])
1162 _ThrowError("Can't initialize meta device: %s", result.output)
1165 def _FindUnusedMinor(cls):
1166 """Find an unused DRBD device.
1168 This is specific to 8.x as the minors are allocated dynamically,
1169 so non-existing numbers up to a max minor count are actually free.
1172 data = cls._GetProcData()
1176 match = cls._UNUSED_LINE_RE.match(line)
1178 return int(match.group(1))
1179 match = cls._VALID_LINE_RE.match(line)
1181 minor = int(match.group(1))
1182 highest = max(highest, minor)
1183 if highest is None: # there are no minors in use at all
1185 if highest >= cls._MAX_MINORS:
1186 logging.error("Error: no free drbd minors!")
1187 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1191 def _GetShowParser(cls):
1192 """Return a parser for `drbd show` output.
1194 This will either create or return an already-create parser for the
1195 output of the command `drbd show`.
1198 if cls._PARSE_SHOW is not None:
1199 return cls._PARSE_SHOW
1202 lbrace = pyp.Literal("{").suppress()
1203 rbrace = pyp.Literal("}").suppress()
1204 lbracket = pyp.Literal("[").suppress()
1205 rbracket = pyp.Literal("]").suppress()
1206 semi = pyp.Literal(";").suppress()
1207 colon = pyp.Literal(":").suppress()
1208 # this also converts the value to an int
1209 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1211 comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1212 defa = pyp.Literal("_is_default").suppress()
1213 dbl_quote = pyp.Literal('"').suppress()
1215 keyword = pyp.Word(pyp.alphanums + '-')
1218 value = pyp.Word(pyp.alphanums + '_-/.:')
1219 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1220 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1221 pyp.Word(pyp.nums + ".") + colon + number)
1222 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1223 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1224 pyp.Optional(rbracket) + colon + number)
1225 # meta device, extended syntax
1226 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1227 # device name, extended syntax
1228 device_value = pyp.Literal("minor").suppress() + number
1231 stmt = (~rbrace + keyword + ~lbrace +
1232 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1234 pyp.Optional(defa) + semi +
1235 pyp.Optional(pyp.restOfLine).suppress())
1238 section_name = pyp.Word(pyp.alphas + "_")
1239 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1241 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1244 cls._PARSE_SHOW = bnf
1249 def _GetShowData(cls, minor):
1250 """Return the `drbdsetup show` data for a minor.
1253 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1255 logging.error("Can't display the drbd config: %s - %s",
1256 result.fail_reason, result.output)
1258 return result.stdout
1261 def _GetDevInfo(cls, out):
1262 """Parse details about a given DRBD minor.
1264 This return, if available, the local backing device (as a path)
1265 and the local and remote (ip, port) information from a string
1266 containing the output of the `drbdsetup show` command as returned
1274 bnf = cls._GetShowParser()
1278 results = bnf.parseString(out)
1279 except pyp.ParseException, err:
1280 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1282 # and massage the results into our desired format
1283 for section in results:
1285 if sname == "_this_host":
1286 for lst in section[1:]:
1287 if lst[0] == "disk":
1288 data["local_dev"] = lst[1]
1289 elif lst[0] == "meta-disk":
1290 data["meta_dev"] = lst[1]
1291 data["meta_index"] = lst[2]
1292 elif lst[0] == "address":
1293 data["local_addr"] = tuple(lst[1:])
1294 elif sname == "_remote_host":
1295 for lst in section[1:]:
1296 if lst[0] == "address":
1297 data["remote_addr"] = tuple(lst[1:])
1300 def _MatchesLocal(self, info):
1301 """Test if our local config matches with an existing device.
1303 The parameter should be as returned from `_GetDevInfo()`. This
1304 method tests if our local backing device is the same as the one in
1305 the info parameter, in effect testing if we look like the given
1310 backend, meta = self._children
1312 backend = meta = None
1314 if backend is not None:
1315 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1317 retval = ("local_dev" not in info)
1319 if meta is not None:
1320 retval = retval and ("meta_dev" in info and
1321 info["meta_dev"] == meta.dev_path)
1322 retval = retval and ("meta_index" in info and
1323 info["meta_index"] == 0)
1325 retval = retval and ("meta_dev" not in info and
1326 "meta_index" not in info)
1329 def _MatchesNet(self, info):
1330 """Test if our network config matches with an existing device.
1332 The parameter should be as returned from `_GetDevInfo()`. This
1333 method tests if our network configuration is the same as the one
1334 in the info parameter, in effect testing if we look like the given
1338 if (((self._lhost is None and not ("local_addr" in info)) and
1339 (self._rhost is None and not ("remote_addr" in info)))):
1342 if self._lhost is None:
1345 if not ("local_addr" in info and
1346 "remote_addr" in info):
1349 retval = (info["local_addr"] == (self._lhost, self._lport))
1350 retval = (retval and
1351 info["remote_addr"] == (self._rhost, self._rport))
1354 def _AssembleLocal(self, minor, backend, meta, size):
1355 """Configure the local part of a DRBD device.
1358 args = ["drbdsetup", self._DevPath(minor), "disk",
1363 args.extend(["-d", "%sm" % size])
1365 version = self._GetVersion(self._GetProcData())
1366 vmaj = version["k_major"]
1367 vmin = version["k_minor"]
1368 vrel = version["k_point"]
1371 self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
1372 self.params[constants.LDP_BARRIERS],
1373 self.params[constants.LDP_NO_META_FLUSH])
1374 args.extend(barrier_args)
1376 if self.params[constants.LDP_DISK_CUSTOM]:
1377 args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
1379 result = utils.RunCmd(args)
1381 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1384 def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
1385 disable_meta_flush):
1386 """Compute the DRBD command line parameters for disk barriers
1388 Returns a list of the disk barrier parameters as requested via the
1389 disabled_barriers and disable_meta_flush arguments, and according to the
1390 supported ones in the DRBD version vmaj.vmin.vrel
1392 If the desired option is unsupported, raises errors.BlockDeviceError.
1395 disabled_barriers_set = frozenset(disabled_barriers)
1396 if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
1397 raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
1398 " barriers" % disabled_barriers)
1402 # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
1404 if not vmaj == 8 and vmin in (0, 2, 3):
1405 raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
1408 def _AppendOrRaise(option, min_version):
1409 """Helper for DRBD options"""
1410 if min_version is not None and vrel >= min_version:
1413 raise errors.BlockDeviceError("Could not use the option %s as the"
1414 " DRBD version %d.%d.%d does not support"
1415 " it." % (option, vmaj, vmin, vrel))
1417 # the minimum version for each feature is encoded via pairs of (minor
1418 # version -> x) where x is version in which support for the option was
1420 meta_flush_supported = disk_flush_supported = {
1426 disk_drain_supported = {
1431 disk_barriers_supported = {
1436 if disable_meta_flush:
1437 _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
1438 meta_flush_supported.get(vmin, None))
1441 if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
1442 _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
1443 disk_flush_supported.get(vmin, None))
1446 if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
1447 _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
1448 disk_drain_supported.get(vmin, None))
1451 if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
1452 _AppendOrRaise(cls._DISABLE_DISK_OPTION,
1453 disk_barriers_supported.get(vmin, None))
1457 def _AssembleNet(self, minor, net_info, protocol,
1458 dual_pri=False, hmac=None, secret=None):
1459 """Configure the network part of the device.
1462 lhost, lport, rhost, rport = net_info
1463 if None in net_info:
1464 # we don't want network connection and actually want to make
1466 self._ShutdownNet(minor)
1469 # Workaround for a race condition. When DRBD is doing its dance to
1470 # establish a connection with its peer, it also sends the
1471 # synchronization speed over the wire. In some cases setting the
1472 # sync speed only after setting up both sides can race with DRBD
1473 # connecting, hence we set it here before telling DRBD anything
1475 sync_speed = self.params.get(constants.LDP_RESYNC_RATE)
1476 self._SetMinorSyncSpeed(minor, sync_speed)
1478 if netutils.IP6Address.IsValid(lhost):
1479 if not netutils.IP6Address.IsValid(rhost):
1480 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1481 (minor, lhost, rhost))
1483 elif netutils.IP4Address.IsValid(lhost):
1484 if not netutils.IP4Address.IsValid(rhost):
1485 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1486 (minor, lhost, rhost))
1489 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1491 args = ["drbdsetup", self._DevPath(minor), "net",
1492 "%s:%s:%s" % (family, lhost, lport),
1493 "%s:%s:%s" % (family, rhost, rport), protocol,
1494 "-A", "discard-zero-changes",
1501 args.extend(["-a", hmac, "-x", secret])
1503 if self.params[constants.LDP_NET_CUSTOM]:
1504 args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
1506 result = utils.RunCmd(args)
1508 _ThrowError("drbd%d: can't setup network: %s - %s",
1509 minor, result.fail_reason, result.output)
1511 def _CheckNetworkConfig():
1512 info = self._GetDevInfo(self._GetShowData(minor))
1513 if not "local_addr" in info or not "remote_addr" in info:
1514 raise utils.RetryAgain()
1516 if (info["local_addr"] != (lhost, lport) or
1517 info["remote_addr"] != (rhost, rport)):
1518 raise utils.RetryAgain()
1521 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1522 except utils.RetryTimeout:
1523 _ThrowError("drbd%d: timeout while configuring network", minor)
1525 def AddChildren(self, devices):
1526 """Add a disk to the DRBD device.
1529 if self.minor is None:
1530 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1532 if len(devices) != 2:
1533 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1534 info = self._GetDevInfo(self._GetShowData(self.minor))
1535 if "local_dev" in info:
1536 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1537 backend, meta = devices
1538 if backend.dev_path is None or meta.dev_path is None:
1539 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1542 self._CheckMetaSize(meta.dev_path)
1543 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1545 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1546 self._children = devices
1548 def RemoveChildren(self, devices):
1549 """Detach the drbd device from local storage.
1552 if self.minor is None:
1553 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1555 # early return if we don't actually have backing storage
1556 info = self._GetDevInfo(self._GetShowData(self.minor))
1557 if "local_dev" not in info:
1559 if len(self._children) != 2:
1560 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1562 if self._children.count(None) == 2: # we don't actually have children :)
1563 logging.warning("drbd%d: requested detach while detached", self.minor)
1565 if len(devices) != 2:
1566 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1567 for child, dev in zip(self._children, devices):
1568 if dev != child.dev_path:
1569 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1570 " RemoveChildren", self.minor, dev, child.dev_path)
1572 self._ShutdownLocal(self.minor)
1576 def _SetMinorSyncSpeed(cls, minor, kbytes):
1577 """Set the speed of the DRBD syncer.
1579 This is the low-level implementation.
1582 @param minor: the drbd minor whose settings we change
1584 @param kbytes: the speed in kbytes/second
1586 @return: the success of the operation
1589 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1590 "-r", "%d" % kbytes, "--create-device"])
1592 logging.error("Can't change syncer rate: %s - %s",
1593 result.fail_reason, result.output)
1594 return not result.failed
1596 def SetSyncSpeed(self, kbytes):
1597 """Set the speed of the DRBD syncer.
1600 @param kbytes: the speed in kbytes/second
1602 @return: the success of the operation
1605 if self.minor is None:
1606 logging.info("Not attached during SetSyncSpeed")
1608 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1609 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1611 def PauseResumeSync(self, pause):
1612 """Pauses or resumes the sync of a DRBD device.
1614 @param pause: Wether to pause or resume
1615 @return: the success of the operation
1618 if self.minor is None:
1619 logging.info("Not attached during PauseSync")
1622 children_result = super(DRBD8, self).PauseResumeSync(pause)
1629 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1631 logging.error("Can't %s: %s - %s", cmd,
1632 result.fail_reason, result.output)
1633 return not result.failed and children_result
1635 def GetProcStatus(self):
1636 """Return device data from /proc.
1639 if self.minor is None:
1640 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1641 proc_info = self._MassageProcData(self._GetProcData())
1642 if self.minor not in proc_info:
1643 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1644 return DRBD8Status(proc_info[self.minor])
1646 def GetSyncStatus(self):
1647 """Returns the sync status of the device.
1650 If sync_percent is None, it means all is ok
1651 If estimated_time is None, it means we can't estimate
1652 the time needed, otherwise it's the time left in seconds.
1655 We set the is_degraded parameter to True on two conditions:
1656 network not connected or local disk missing.
1658 We compute the ldisk parameter based on whether we have a local
1661 @rtype: objects.BlockDevStatus
1664 if self.minor is None and not self.Attach():
1665 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1667 stats = self.GetProcStatus()
1668 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1670 if stats.is_disk_uptodate:
1671 ldisk_status = constants.LDS_OKAY
1672 elif stats.is_diskless:
1673 ldisk_status = constants.LDS_FAULTY
1675 ldisk_status = constants.LDS_UNKNOWN
1677 return objects.BlockDevStatus(dev_path=self.dev_path,
1680 sync_percent=stats.sync_percent,
1681 estimated_time=stats.est_time,
1682 is_degraded=is_degraded,
1683 ldisk_status=ldisk_status)
1685 def Open(self, force=False):
1686 """Make the local state primary.
1688 If the 'force' parameter is given, the '-o' option is passed to
1689 drbdsetup. Since this is a potentially dangerous operation, the
1690 force flag should be only given after creation, when it actually
1694 if self.minor is None and not self.Attach():
1695 logging.error("DRBD cannot attach to a device during open")
1697 cmd = ["drbdsetup", self.dev_path, "primary"]
1700 result = utils.RunCmd(cmd)
1702 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1706 """Make the local state secondary.
1708 This will, of course, fail if the device is in use.
1711 if self.minor is None and not self.Attach():
1712 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1713 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1715 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1716 self.minor, result.output)
1718 def DisconnectNet(self):
1719 """Removes network configuration.
1721 This method shutdowns the network side of the device.
1723 The method will wait up to a hardcoded timeout for the device to
1724 go into standalone after the 'disconnect' command before
1725 re-configuring it, as sometimes it takes a while for the
1726 disconnect to actually propagate and thus we might issue a 'net'
1727 command while the device is still connected. If the device will
1728 still be attached to the network and we time out, we raise an
1732 if self.minor is None:
1733 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1735 if None in (self._lhost, self._lport, self._rhost, self._rport):
1736 _ThrowError("drbd%d: DRBD disk missing network info in"
1737 " DisconnectNet()", self.minor)
1739 class _DisconnectStatus:
1740 def __init__(self, ever_disconnected):
1741 self.ever_disconnected = ever_disconnected
1743 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1745 def _WaitForDisconnect():
1746 if self.GetProcStatus().is_standalone:
1749 # retry the disconnect, it seems possible that due to a well-time
1750 # disconnect on the peer, my disconnect command might be ignored and
1752 dstatus.ever_disconnected = \
1753 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1755 raise utils.RetryAgain()
1758 start_time = time.time()
1761 # Start delay at 100 milliseconds and grow up to 2 seconds
1762 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1763 self._NET_RECONFIG_TIMEOUT)
1764 except utils.RetryTimeout:
1765 if dstatus.ever_disconnected:
1766 msg = ("drbd%d: device did not react to the"
1767 " 'disconnect' command in a timely manner")
1769 msg = "drbd%d: can't shutdown network, even after multiple retries"
1771 _ThrowError(msg, self.minor)
1773 reconfig_time = time.time() - start_time
1774 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1775 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1776 self.minor, reconfig_time)
1778 def AttachNet(self, multimaster):
1779 """Reconnects the network.
1781 This method connects the network side of the device with a
1782 specified multi-master flag. The device needs to be 'Standalone'
1783 but have valid network configuration data.
1786 - multimaster: init the network in dual-primary mode
1789 if self.minor is None:
1790 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1792 if None in (self._lhost, self._lport, self._rhost, self._rport):
1793 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1795 status = self.GetProcStatus()
1797 if not status.is_standalone:
1798 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1800 self._AssembleNet(self.minor,
1801 (self._lhost, self._lport, self._rhost, self._rport),
1802 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1803 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1806 """Check if our minor is configured.
1808 This doesn't do any device configurations - it only checks if the
1809 minor is in a state different from Unconfigured.
1811 Note that this function will not change the state of the system in
1812 any way (except in case of side-effects caused by reading from
1816 used_devs = self.GetUsedDevs()
1817 if self._aminor in used_devs:
1818 minor = self._aminor
1822 self._SetFromMinor(minor)
1823 return minor is not None
1826 """Assemble the drbd.
1829 - if we have a configured device, we try to ensure that it matches
1831 - if not, we create it from zero
1832 - anyway, set the device parameters
1835 super(DRBD8, self).Assemble()
1838 if self.minor is None:
1839 # local device completely unconfigured
1840 self._FastAssemble()
1842 # we have to recheck the local and network status and try to fix
1844 self._SlowAssemble()
1846 sync_speed = self.params.get(constants.LDP_RESYNC_RATE)
1847 self.SetSyncSpeed(sync_speed)
1849 def _SlowAssemble(self):
1850 """Assembles the DRBD device from a (partially) configured device.
1852 In case of partially attached (local device matches but no network
1853 setup), we perform the network attach. If successful, we re-test
1854 the attach if can return success.
1857 # TODO: Rewrite to not use a for loop just because there is 'break'
1858 # pylint: disable=W0631
1859 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1860 for minor in (self._aminor,):
1861 info = self._GetDevInfo(self._GetShowData(minor))
1862 match_l = self._MatchesLocal(info)
1863 match_r = self._MatchesNet(info)
1865 if match_l and match_r:
1866 # everything matches
1869 if match_l and not match_r and "local_addr" not in info:
1870 # disk matches, but not attached to network, attach and recheck
1871 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1872 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1873 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1876 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1877 " show' disagrees", minor)
1879 if match_r and "local_dev" not in info:
1880 # no local disk, but network attached and it matches
1881 self._AssembleLocal(minor, self._children[0].dev_path,
1882 self._children[1].dev_path, self.size)
1883 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1886 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1887 " show' disagrees", minor)
1889 # this case must be considered only if we actually have local
1890 # storage, i.e. not in diskless mode, because all diskless
1891 # devices are equal from the point of view of local
1893 if (match_l and "local_dev" in info and
1894 not match_r and "local_addr" in info):
1895 # strange case - the device network part points to somewhere
1896 # else, even though its local storage is ours; as we own the
1897 # drbd space, we try to disconnect from the remote peer and
1898 # reconnect to our correct one
1900 self._ShutdownNet(minor)
1901 except errors.BlockDeviceError, err:
1902 _ThrowError("drbd%d: device has correct local storage, wrong"
1903 " remote peer and is unable to disconnect in order"
1904 " to attach to the correct peer: %s", minor, str(err))
1905 # note: _AssembleNet also handles the case when we don't want
1906 # local storage (i.e. one or more of the _[lr](host|port) is
1908 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1909 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1910 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1913 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1914 " show' disagrees", minor)
1919 self._SetFromMinor(minor)
1921 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1924 def _FastAssemble(self):
1925 """Assemble the drbd device from zero.
1927 This is run when in Assemble we detect our minor is unused.
1930 minor = self._aminor
1931 if self._children and self._children[0] and self._children[1]:
1932 self._AssembleLocal(minor, self._children[0].dev_path,
1933 self._children[1].dev_path, self.size)
1934 if self._lhost and self._lport and self._rhost and self._rport:
1935 self._AssembleNet(minor,
1936 (self._lhost, self._lport, self._rhost, self._rport),
1937 constants.DRBD_NET_PROTOCOL,
1938 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1939 self._SetFromMinor(minor)
1942 def _ShutdownLocal(cls, minor):
1943 """Detach from the local device.
1945 I/Os will continue to be served from the remote device. If we
1946 don't have a remote device, this operation will fail.
1949 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1951 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1954 def _ShutdownNet(cls, minor):
1955 """Disconnect from the remote peer.
1957 This fails if we don't have a local device.
1960 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1962 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1965 def _ShutdownAll(cls, minor):
1966 """Deactivate the device.
1968 This will, of course, fail if the device is in use.
1971 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1973 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1974 minor, result.output)
1977 """Shutdown the DRBD device.
1980 if self.minor is None and not self.Attach():
1981 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1985 self.dev_path = None
1986 self._ShutdownAll(minor)
1989 """Stub remove for DRBD devices.
1995 def Create(cls, unique_id, children, size, params):
1996 """Create a new DRBD8 device.
1998 Since DRBD devices are not created per se, just assembled, this
1999 function only initializes the metadata.
2002 if len(children) != 2:
2003 raise errors.ProgrammerError("Invalid setup for the drbd device")
2004 # check that the minor is unused
2005 aminor = unique_id[4]
2006 proc_info = cls._MassageProcData(cls._GetProcData())
2007 if aminor in proc_info:
2008 status = DRBD8Status(proc_info[aminor])
2009 in_use = status.is_in_use
2013 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
2016 if not meta.Attach():
2017 _ThrowError("drbd%d: can't attach to meta device '%s'",
2019 cls._CheckMetaSize(meta.dev_path)
2020 cls._InitMeta(aminor, meta.dev_path)
2021 return cls(unique_id, children, size, params)
2023 def Grow(self, amount, dryrun):
2024 """Resize the DRBD device and its backing storage.
2027 if self.minor is None:
2028 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
2029 if len(self._children) != 2 or None in self._children:
2030 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
2031 self._children[0].Grow(amount, dryrun)
2033 # DRBD does not support dry-run mode, so we'll return here
2035 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
2036 "%dm" % (self.size + amount)])
2038 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
2041 class FileStorage(BlockDev):
2044 This class represents the a file storage backend device.
2046 The unique_id for the file device is a (file_driver, file_path) tuple.
2049 def __init__(self, unique_id, children, size, params):
2050 """Initalizes a file device backend.
2054 raise errors.BlockDeviceError("Invalid setup for file device")
2055 super(FileStorage, self).__init__(unique_id, children, size, params)
2056 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2057 raise ValueError("Invalid configuration data %s" % str(unique_id))
2058 self.driver = unique_id[0]
2059 self.dev_path = unique_id[1]
2063 """Assemble the device.
2065 Checks whether the file device exists, raises BlockDeviceError otherwise.
2068 if not os.path.exists(self.dev_path):
2069 _ThrowError("File device '%s' does not exist" % self.dev_path)
2072 """Shutdown the device.
2074 This is a no-op for the file type, as we don't deactivate
2075 the file on shutdown.
2080 def Open(self, force=False):
2081 """Make the device ready for I/O.
2083 This is a no-op for the file type.
2089 """Notifies that the device will no longer be used for I/O.
2091 This is a no-op for the file type.
2097 """Remove the file backing the block device.
2100 @return: True if the removal was successful
2104 os.remove(self.dev_path)
2105 except OSError, err:
2106 if err.errno != errno.ENOENT:
2107 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2109 def Rename(self, new_id):
2110 """Renames the file.
2113 # TODO: implement rename for file-based storage
2114 _ThrowError("Rename is not supported for file-based storage")
2116 def Grow(self, amount, dryrun):
2119 @param amount: the amount (in mebibytes) to grow with
2122 # Check that the file exists
2124 current_size = self.GetActualSize()
2125 new_size = current_size + amount * 1024 * 1024
2126 assert new_size > current_size, "Cannot Grow with a negative amount"
2127 # We can't really simulate the growth
2131 f = open(self.dev_path, "a+")
2132 f.truncate(new_size)
2134 except EnvironmentError, err:
2135 _ThrowError("Error in file growth: %", str(err))
2138 """Attach to an existing file.
2140 Check if this file already exists.
2143 @return: True if file exists
2146 self.attached = os.path.exists(self.dev_path)
2147 return self.attached
2149 def GetActualSize(self):
2150 """Return the actual disk size.
2152 @note: the device needs to be active when this is called
2155 assert self.attached, "BlockDevice not attached in GetActualSize()"
2157 st = os.stat(self.dev_path)
2159 except OSError, err:
2160 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2163 def Create(cls, unique_id, children, size, params):
2164 """Create a new file.
2166 @param size: the size of file in MiB
2168 @rtype: L{bdev.FileStorage}
2169 @return: an instance of FileStorage
2172 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2173 raise ValueError("Invalid configuration data %s" % str(unique_id))
2174 dev_path = unique_id[1]
2176 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2177 f = os.fdopen(fd, "w")
2178 f.truncate(size * 1024 * 1024)
2180 except EnvironmentError, err:
2181 if err.errno == errno.EEXIST:
2182 _ThrowError("File already existing: %s", dev_path)
2183 _ThrowError("Error in file creation: %", str(err))
2185 return FileStorage(unique_id, children, size, params)
2188 class PersistentBlockDevice(BlockDev):
2189 """A block device with persistent node
2191 May be either directly attached, or exposed through DM (e.g. dm-multipath).
2192 udev helpers are probably required to give persistent, human-friendly
2195 For the time being, pathnames are required to lie under /dev.
2198 def __init__(self, unique_id, children, size, params):
2199 """Attaches to a static block device.
2201 The unique_id is a path under /dev.
2204 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
2206 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2207 raise ValueError("Invalid configuration data %s" % str(unique_id))
2208 self.dev_path = unique_id[1]
2209 if not os.path.realpath(self.dev_path).startswith("/dev/"):
2210 raise ValueError("Full path '%s' lies outside /dev" %
2211 os.path.realpath(self.dev_path))
2212 # TODO: this is just a safety guard checking that we only deal with devices
2213 # we know how to handle. In the future this will be integrated with
2214 # external storage backends and possible values will probably be collected
2215 # from the cluster configuration.
2216 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2217 raise ValueError("Got persistent block device of invalid type: %s" %
2220 self.major = self.minor = None
2224 def Create(cls, unique_id, children, size, params):
2225 """Create a new device
2227 This is a noop, we only return a PersistentBlockDevice instance
2230 return PersistentBlockDevice(unique_id, children, 0, params)
2240 def Rename(self, new_id):
2241 """Rename this device.
2244 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2247 """Attach to an existing block device.
2251 self.attached = False
2253 st = os.stat(self.dev_path)
2254 except OSError, err:
2255 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2258 if not stat.S_ISBLK(st.st_mode):
2259 logging.error("%s is not a block device", self.dev_path)
2262 self.major = os.major(st.st_rdev)
2263 self.minor = os.minor(st.st_rdev)
2264 self.attached = True
2269 """Assemble the device.
2275 """Shutdown the device.
2280 def Open(self, force=False):
2281 """Make the device ready for I/O.
2287 """Notifies that the device will no longer be used for I/O.
2292 def Grow(self, amount, dryrun):
2293 """Grow the logical volume.
2296 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2300 constants.LD_LV: LogicalVolume,
2301 constants.LD_DRBD8: DRBD8,
2302 constants.LD_BLOCKDEV: PersistentBlockDevice,
2305 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2306 DEV_MAP[constants.LD_FILE] = FileStorage
2309 def _VerifyDiskType(dev_type):
2310 if dev_type not in DEV_MAP:
2311 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2314 def FindDevice(disk, children):
2315 """Search for an existing, assembled device.
2317 This will succeed only if the device exists and is assembled, but it
2318 does not do any actions in order to activate the device.
2320 @type disk: L{objects.Disk}
2321 @param disk: the disk object to find
2322 @type children: list of L{bdev.BlockDev}
2323 @param children: the list of block devices that are children of the device
2324 represented by the disk parameter
2327 _VerifyDiskType(disk.dev_type)
2328 dev_params = objects.FillDict(constants.DISK_LD_DEFAULTS[disk.dev_type],
2330 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2332 if not device.attached:
2337 def Assemble(disk, children):
2338 """Try to attach or assemble an existing device.
2340 This will attach to assemble the device, as needed, to bring it
2341 fully up. It must be safe to run on already-assembled devices.
2343 @type disk: L{objects.Disk}
2344 @param disk: the disk object to assemble
2345 @type children: list of L{bdev.BlockDev}
2346 @param children: the list of block devices that are children of the device
2347 represented by the disk parameter
2350 _VerifyDiskType(disk.dev_type)
2351 dev_params = objects.FillDict(constants.DISK_LD_DEFAULTS[disk.dev_type],
2353 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2359 def Create(disk, children):
2362 @type disk: L{objects.Disk}
2363 @param disk: the disk object to create
2364 @type children: list of L{bdev.BlockDev}
2365 @param children: the list of block devices that are children of the device
2366 represented by the disk parameter
2369 _VerifyDiskType(disk.dev_type)
2370 dev_params = objects.FillDict(constants.DISK_LD_DEFAULTS[disk.dev_type],
2372 device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,