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"""
28 import pyparsing as pyp
32 from ganeti import utils
33 from ganeti import errors
34 from ganeti import constants
35 from ganeti import objects
36 from ganeti import compat
37 from ganeti import netutils
40 # Size of reads in _CanReadDevice
41 _DEVICE_READ_SIZE = 128 * 1024
44 def _IgnoreError(fn, *args, **kwargs):
45 """Executes the given function, ignoring BlockDeviceErrors.
47 This is used in order to simplify the execution of cleanup or
51 @return: True when fn didn't raise an exception, False otherwise
57 except errors.BlockDeviceError, err:
58 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
62 def _ThrowError(msg, *args):
63 """Log an error to the node daemon and the raise an exception.
66 @param msg: the text of the exception
67 @raise errors.BlockDeviceError
73 raise errors.BlockDeviceError(msg)
76 def _CanReadDevice(path):
77 """Check if we can read from the given device.
79 This tries to read the first 128k of the device.
83 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
85 except EnvironmentError:
86 logging.warning("Can't read from device %s", path, exc_info=True)
90 class BlockDev(object):
91 """Block device abstract class.
93 A block device can be in the following states:
94 - not existing on the system, and by `Create()` it goes into:
95 - existing but not setup/not active, and by `Assemble()` goes into:
96 - active read-write and by `Open()` it goes into
97 - online (=used, or ready for use)
99 A device can also be online but read-only, however we are not using
100 the readonly state (LV has it, if needed in the future) and we are
101 usually looking at this like at a stack, so it's easier to
102 conceptualise the transition from not-existing to online and back
105 The many different states of the device are due to the fact that we
106 need to cover many device types:
107 - logical volumes are created, lvchange -a y $lv, and used
108 - drbd devices are attached to a local disk/remote peer and made primary
110 A block device is identified by three items:
111 - the /dev path of the device (dynamic)
112 - a unique ID of the device (static)
113 - it's major/minor pair (dynamic)
115 Not all devices implement both the first two as distinct items. LVM
116 logical volumes have their unique ID (the pair volume group, logical
117 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
118 the /dev path is again dynamic and the unique id is the pair (host1,
119 dev1), (host2, dev2).
121 You can get to a device in two ways:
122 - creating the (real) device, which returns you
123 an attached instance (lvcreate)
124 - attaching of a python instance to an existing (real) device
126 The second point, the attachement to a device, is different
127 depending on whether the device is assembled or not. At init() time,
128 we search for a device with the same unique_id as us. If found,
129 good. It also means that the device is already assembled. If not,
130 after assembly we'll have our correct major/minor.
133 def __init__(self, unique_id, children, size):
134 self._children = children
136 self.unique_id = unique_id
139 self.attached = False
143 """Assemble the device from its components.
145 Implementations of this method by child classes must ensure that:
146 - after the device has been assembled, it knows its major/minor
147 numbers; this allows other devices (usually parents) to probe
148 correctly for their children
149 - calling this method on an existing, in-use device is safe
150 - if the device is already configured (and in an OK state),
151 this method is idempotent
157 """Find a device which matches our config and attach to it.
160 raise NotImplementedError
163 """Notifies that the device will no longer be used for I/O.
166 raise NotImplementedError
169 def Create(cls, unique_id, children, size):
170 """Create the device.
172 If the device cannot be created, it will return None
173 instead. Error messages go to the logging system.
175 Note that for some devices, the unique_id is used, and for other,
176 the children. The idea is that these two, taken together, are
177 enough for both creation and assembly (later).
180 raise NotImplementedError
183 """Remove this device.
185 This makes sense only for some of the device types: LV and file
186 storage. Also note that if the device can't attach, the removal
190 raise NotImplementedError
192 def Rename(self, new_id):
193 """Rename this device.
195 This may or may not make sense for a given device type.
198 raise NotImplementedError
200 def Open(self, force=False):
201 """Make the device ready for use.
203 This makes the device ready for I/O. For now, just the DRBD
206 The force parameter signifies that if the device has any kind of
207 --force thing, it should be used, we know what we are doing.
210 raise NotImplementedError
213 """Shut down the device, freeing its children.
215 This undoes the `Assemble()` work, except for the child
216 assembling; as such, the children on the device are still
217 assembled after this call.
220 raise NotImplementedError
222 def SetSyncSpeed(self, speed):
223 """Adjust the sync speed of the mirror.
225 In case this is not a mirroring device, this is no-op.
230 for child in self._children:
231 result = result and child.SetSyncSpeed(speed)
234 def PauseResumeSync(self, pause):
235 """Pause/Resume the sync of the mirror.
237 In case this is not a mirroring device, this is no-op.
239 @param pause: Wheater to pause or resume
244 for child in self._children:
245 result = result and child.PauseResumeSync(pause)
248 def GetSyncStatus(self):
249 """Returns the sync status of the device.
251 If this device is a mirroring device, this function returns the
252 status of the mirror.
254 If sync_percent is None, it means the device is not syncing.
256 If estimated_time is None, it means we can't estimate
257 the time needed, otherwise it's the time left in seconds.
259 If is_degraded is True, it means the device is missing
260 redundancy. This is usually a sign that something went wrong in
261 the device setup, if sync_percent is None.
263 The ldisk parameter represents the degradation of the local
264 data. This is only valid for some devices, the rest will always
265 return False (not degraded).
267 @rtype: objects.BlockDevStatus
270 return objects.BlockDevStatus(dev_path=self.dev_path,
276 ldisk_status=constants.LDS_OKAY)
278 def CombinedSyncStatus(self):
279 """Calculate the mirror status recursively for our children.
281 The return value is the same as for `GetSyncStatus()` except the
282 minimum percent and maximum time are calculated across our
285 @rtype: objects.BlockDevStatus
288 status = self.GetSyncStatus()
290 min_percent = status.sync_percent
291 max_time = status.estimated_time
292 is_degraded = status.is_degraded
293 ldisk_status = status.ldisk_status
296 for child in self._children:
297 child_status = child.GetSyncStatus()
299 if min_percent is None:
300 min_percent = child_status.sync_percent
301 elif child_status.sync_percent is not None:
302 min_percent = min(min_percent, child_status.sync_percent)
305 max_time = child_status.estimated_time
306 elif child_status.estimated_time is not None:
307 max_time = max(max_time, child_status.estimated_time)
309 is_degraded = is_degraded or child_status.is_degraded
311 if ldisk_status is None:
312 ldisk_status = child_status.ldisk_status
313 elif child_status.ldisk_status is not None:
314 ldisk_status = max(ldisk_status, child_status.ldisk_status)
316 return objects.BlockDevStatus(dev_path=self.dev_path,
319 sync_percent=min_percent,
320 estimated_time=max_time,
321 is_degraded=is_degraded,
322 ldisk_status=ldisk_status)
325 def SetInfo(self, text):
326 """Update metadata with info text.
328 Only supported for some device types.
331 for child in self._children:
334 def Grow(self, amount, dryrun):
335 """Grow the block device.
337 @type amount: integer
338 @param amount: the amount (in mebibytes) to grow with
339 @type dryrun: boolean
340 @param dryrun: whether to execute the operation in simulation mode
341 only, without actually increasing the size
344 raise NotImplementedError
346 def GetActualSize(self):
347 """Return the actual disk size.
349 @note: the device needs to be active when this is called
352 assert self.attached, "BlockDevice not attached in GetActualSize()"
353 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
355 _ThrowError("blockdev failed (%s): %s",
356 result.fail_reason, result.output)
358 sz = int(result.output.strip())
359 except (ValueError, TypeError), err:
360 _ThrowError("Failed to parse blockdev output: %s", str(err))
364 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
365 (self.__class__, self.unique_id, self._children,
366 self.major, self.minor, self.dev_path))
369 class LogicalVolume(BlockDev):
370 """Logical Volume block device.
373 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
374 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
375 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
377 def __init__(self, unique_id, children, size):
378 """Attaches to a LV device.
380 The unique_id is a tuple (vg_name, lv_name)
383 super(LogicalVolume, self).__init__(unique_id, children, size)
384 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
385 raise ValueError("Invalid configuration data %s" % str(unique_id))
386 self._vg_name, self._lv_name = unique_id
387 self._ValidateName(self._vg_name)
388 self._ValidateName(self._lv_name)
389 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
390 self._degraded = True
391 self.major = self.minor = self.pe_size = self.stripe_count = None
395 def Create(cls, unique_id, children, size):
396 """Create a new logical volume.
399 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
400 raise errors.ProgrammerError("Invalid configuration data %s" %
402 vg_name, lv_name = unique_id
403 cls._ValidateName(vg_name)
404 cls._ValidateName(lv_name)
405 pvs_info = cls.GetPVInfo([vg_name])
407 _ThrowError("Can't compute PV info for vg %s", vg_name)
411 pvlist = [ pv[1] for pv in pvs_info ]
412 if compat.any(":" in v for v in pvlist):
413 _ThrowError("Some of your PVs have the invalid character ':' in their"
414 " name, this is not supported - please filter them out"
415 " in lvm.conf using either 'filter' or 'preferred_names'")
416 free_size = sum([ pv[0] for pv in pvs_info ])
417 current_pvs = len(pvlist)
418 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
420 # The size constraint should have been checked from the master before
421 # calling the create function.
423 _ThrowError("Not enough free space: required %s,"
424 " available %s", size, free_size)
425 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
426 # If the free space is not well distributed, we won't be able to
427 # create an optimally-striped volume; in that case, we want to try
428 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
430 for stripes_arg in range(stripes, 0, -1):
431 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
432 if not result.failed:
435 _ThrowError("LV create failed (%s): %s",
436 result.fail_reason, result.output)
437 return LogicalVolume(unique_id, children, size)
440 def _GetVolumeInfo(lvm_cmd, fields):
441 """Returns LVM Volumen infos using lvm_cmd
443 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
444 @param fields: Fields to return
445 @return: A list of dicts each with the parsed fields
449 raise errors.ProgrammerError("No fields specified")
452 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
453 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
455 result = utils.RunCmd(cmd)
457 raise errors.CommandError("Can't get the volume information: %s - %s" %
458 (result.fail_reason, result.output))
461 for line in result.stdout.splitlines():
462 splitted_fields = line.strip().split(sep)
464 if len(fields) != len(splitted_fields):
465 raise errors.CommandError("Can't parse %s output: line '%s'" %
468 data.append(splitted_fields)
473 def GetPVInfo(cls, vg_names, filter_allocatable=True):
474 """Get the free space info for PVs in a volume group.
476 @param vg_names: list of volume group names, if empty all will be returned
477 @param filter_allocatable: whether to skip over unallocatable PVs
480 @return: list of tuples (free_space, name) with free_space in mebibytes
484 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
486 except errors.GenericError, err:
487 logging.error("Can't get PV information: %s", err)
491 for pv_name, vg_name, pv_free, pv_attr in info:
492 # (possibly) skip over pvs which are not allocatable
493 if filter_allocatable and pv_attr[0] != "a":
495 # (possibly) skip over pvs which are not in the right volume group(s)
496 if vg_names and vg_name not in vg_names:
498 data.append((float(pv_free), pv_name, vg_name))
503 def GetVGInfo(cls, vg_names, filter_readonly=True):
504 """Get the free space info for specific VGs.
506 @param vg_names: list of volume group names, if empty all will be returned
507 @param filter_readonly: whether to skip over readonly VGs
510 @return: list of tuples (free_space, total_size, name) with free_space in
515 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
517 except errors.GenericError, err:
518 logging.error("Can't get VG information: %s", err)
522 for vg_name, vg_free, vg_attr, vg_size in info:
523 # (possibly) skip over vgs which are not writable
524 if filter_readonly and vg_attr[0] == "r":
526 # (possibly) skip over vgs which are not in the right volume group(s)
527 if vg_names and vg_name not in vg_names:
529 data.append((float(vg_free), float(vg_size), vg_name))
534 def _ValidateName(cls, name):
535 """Validates that a given name is valid as VG or LV name.
537 The list of valid characters and restricted names is taken out of
538 the lvm(8) manpage, with the simplification that we enforce both
539 VG and LV restrictions on the names.
542 if (not cls._VALID_NAME_RE.match(name) or
543 name in cls._INVALID_NAMES or
544 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
545 _ThrowError("Invalid LVM name '%s'", name)
548 """Remove this logical volume.
551 if not self.minor and not self.Attach():
552 # the LV does not exist
554 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
555 (self._vg_name, self._lv_name)])
557 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
559 def Rename(self, new_id):
560 """Rename this logical volume.
563 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
564 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
565 new_vg, new_name = new_id
566 if new_vg != self._vg_name:
567 raise errors.ProgrammerError("Can't move a logical volume across"
568 " volume groups (from %s to to %s)" %
569 (self._vg_name, new_vg))
570 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
572 _ThrowError("Failed to rename the logical volume: %s", result.output)
573 self._lv_name = new_name
574 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
577 """Attach to an existing LV.
579 This method will try to see if an existing and active LV exists
580 which matches our name. If so, its major/minor will be
584 self.attached = False
585 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
586 "--units=m", "--nosuffix",
587 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
588 "vg_extent_size,stripes", self.dev_path])
590 logging.error("Can't find LV %s: %s, %s",
591 self.dev_path, result.fail_reason, result.output)
593 # the output can (and will) have multiple lines for multi-segment
594 # LVs, as the 'stripes' parameter is a segment one, so we take
595 # only the last entry, which is the one we're interested in; note
596 # that with LVM2 anyway the 'stripes' value must be constant
597 # across segments, so this is a no-op actually
598 out = result.stdout.splitlines()
599 if not out: # totally empty result? splitlines() returns at least
600 # one line for any non-empty string
601 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
603 out = out[-1].strip().rstrip(",")
606 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
609 status, major, minor, pe_size, stripes = out
611 logging.error("lvs lv_attr is not 6 characters (%s)", status)
617 except (TypeError, ValueError), err:
618 logging.error("lvs major/minor cannot be parsed: %s", str(err))
621 pe_size = int(float(pe_size))
622 except (TypeError, ValueError), err:
623 logging.error("Can't parse vg extent size: %s", err)
627 stripes = int(stripes)
628 except (TypeError, ValueError), err:
629 logging.error("Can't parse the number of stripes: %s", err)
634 self.pe_size = pe_size
635 self.stripe_count = stripes
636 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
642 """Assemble the device.
644 We always run `lvchange -ay` on the LV to ensure it's active before
645 use, as there were cases when xenvg was not active after boot
646 (also possibly after disk issues).
649 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
651 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
654 """Shutdown the device.
656 This is a no-op for the LV device type, as we don't deactivate the
662 def GetSyncStatus(self):
663 """Returns the sync status of the device.
665 If this device is a mirroring device, this function returns the
666 status of the mirror.
668 For logical volumes, sync_percent and estimated_time are always
669 None (no recovery in progress, as we don't handle the mirrored LV
670 case). The is_degraded parameter is the inverse of the ldisk
673 For the ldisk parameter, we check if the logical volume has the
674 'virtual' type, which means it's not backed by existing storage
675 anymore (read from it return I/O error). This happens after a
676 physical disk failure and subsequent 'vgreduce --removemissing' on
679 The status was already read in Attach, so we just return it.
681 @rtype: objects.BlockDevStatus
685 ldisk_status = constants.LDS_FAULTY
687 ldisk_status = constants.LDS_OKAY
689 return objects.BlockDevStatus(dev_path=self.dev_path,
694 is_degraded=self._degraded,
695 ldisk_status=ldisk_status)
697 def Open(self, force=False):
698 """Make the device ready for I/O.
700 This is a no-op for the LV device type.
706 """Notifies that the device will no longer be used for I/O.
708 This is a no-op for the LV device type.
713 def Snapshot(self, size):
714 """Create a snapshot copy of an lvm block device.
716 @returns: tuple (vg, lv)
719 snap_name = self._lv_name + ".snap"
721 # remove existing snapshot if found
722 snap = LogicalVolume((self._vg_name, snap_name), None, size)
723 _IgnoreError(snap.Remove)
725 vg_info = self.GetVGInfo([self._vg_name])
727 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
728 free_size, _, _ = vg_info[0]
730 _ThrowError("Not enough free space: required %s,"
731 " available %s", size, free_size)
733 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
734 "-n%s" % snap_name, self.dev_path])
736 _ThrowError("command: %s error: %s - %s",
737 result.cmd, result.fail_reason, result.output)
739 return (self._vg_name, snap_name)
741 def SetInfo(self, text):
742 """Update metadata with info text.
745 BlockDev.SetInfo(self, text)
747 # Replace invalid characters
748 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
749 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
751 # Only up to 128 characters are allowed
754 result = utils.RunCmd(["lvchange", "--addtag", text,
757 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
760 def Grow(self, amount, dryrun):
761 """Grow the logical volume.
764 if self.pe_size is None or self.stripe_count is None:
765 if not self.Attach():
766 _ThrowError("Can't attach to LV during Grow()")
767 full_stripe_size = self.pe_size * self.stripe_count
768 rest = amount % full_stripe_size
770 amount += full_stripe_size - rest
771 cmd = ["lvextend", "-L", "+%dm" % amount]
774 # we try multiple algorithms since the 'best' ones might not have
775 # space available in the right place, but later ones might (since
776 # they have less constraints); also note that only recent LVM
778 for alloc_policy in "contiguous", "cling", "normal":
779 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
780 if not result.failed:
782 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
785 class DRBD8Status(object):
786 """A DRBD status representation class.
788 Note that this doesn't support unconfigured devices (cs:Unconfigured).
791 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
792 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
793 "\s+ds:([^/]+)/(\S+)\s+.*$")
794 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
795 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
797 CS_UNCONFIGURED = "Unconfigured"
798 CS_STANDALONE = "StandAlone"
799 CS_WFCONNECTION = "WFConnection"
800 CS_WFREPORTPARAMS = "WFReportParams"
801 CS_CONNECTED = "Connected"
802 CS_STARTINGSYNCS = "StartingSyncS"
803 CS_STARTINGSYNCT = "StartingSyncT"
804 CS_WFBITMAPS = "WFBitMapS"
805 CS_WFBITMAPT = "WFBitMapT"
806 CS_WFSYNCUUID = "WFSyncUUID"
807 CS_SYNCSOURCE = "SyncSource"
808 CS_SYNCTARGET = "SyncTarget"
809 CS_PAUSEDSYNCS = "PausedSyncS"
810 CS_PAUSEDSYNCT = "PausedSyncT"
811 CSET_SYNC = frozenset([
824 DS_DISKLESS = "Diskless"
825 DS_ATTACHING = "Attaching" # transient state
826 DS_FAILED = "Failed" # transient state, next: diskless
827 DS_NEGOTIATING = "Negotiating" # transient state
828 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
829 DS_OUTDATED = "Outdated"
830 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
831 DS_CONSISTENT = "Consistent"
832 DS_UPTODATE = "UpToDate" # normal state
834 RO_PRIMARY = "Primary"
835 RO_SECONDARY = "Secondary"
836 RO_UNKNOWN = "Unknown"
838 def __init__(self, procline):
839 u = self.UNCONF_RE.match(procline)
841 self.cstatus = self.CS_UNCONFIGURED
842 self.lrole = self.rrole = self.ldisk = self.rdisk = None
844 m = self.LINE_RE.match(procline)
846 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
847 self.cstatus = m.group(1)
848 self.lrole = m.group(2)
849 self.rrole = m.group(3)
850 self.ldisk = m.group(4)
851 self.rdisk = m.group(5)
853 # end reading of data from the LINE_RE or UNCONF_RE
855 self.is_standalone = self.cstatus == self.CS_STANDALONE
856 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
857 self.is_connected = self.cstatus == self.CS_CONNECTED
858 self.is_primary = self.lrole == self.RO_PRIMARY
859 self.is_secondary = self.lrole == self.RO_SECONDARY
860 self.peer_primary = self.rrole == self.RO_PRIMARY
861 self.peer_secondary = self.rrole == self.RO_SECONDARY
862 self.both_primary = self.is_primary and self.peer_primary
863 self.both_secondary = self.is_secondary and self.peer_secondary
865 self.is_diskless = self.ldisk == self.DS_DISKLESS
866 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
868 self.is_in_resync = self.cstatus in self.CSET_SYNC
869 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
871 m = self.SYNC_RE.match(procline)
873 self.sync_percent = float(m.group(1))
874 hours = int(m.group(2))
875 minutes = int(m.group(3))
876 seconds = int(m.group(4))
877 self.est_time = hours * 3600 + minutes * 60 + seconds
879 # we have (in this if branch) no percent information, but if
880 # we're resyncing we need to 'fake' a sync percent information,
881 # as this is how cmdlib determines if it makes sense to wait for
883 if self.is_in_resync:
884 self.sync_percent = 0
886 self.sync_percent = None
890 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
893 This class contains a few bits of common functionality between the
894 0.7 and 8.x versions of DRBD.
897 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
898 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
899 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
900 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
903 _ST_UNCONFIGURED = "Unconfigured"
904 _ST_WFCONNECTION = "WFConnection"
905 _ST_CONNECTED = "Connected"
907 _STATUS_FILE = "/proc/drbd"
908 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
911 def _GetProcData(filename=_STATUS_FILE):
912 """Return data from /proc/drbd.
916 data = utils.ReadFile(filename).splitlines()
917 except EnvironmentError, err:
918 if err.errno == errno.ENOENT:
919 _ThrowError("The file %s cannot be opened, check if the module"
920 " is loaded (%s)", filename, str(err))
922 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
924 _ThrowError("Can't read any data from %s", filename)
928 def _MassageProcData(cls, data):
929 """Transform the output of _GetProdData into a nicer form.
931 @return: a dictionary of minor: joined lines from /proc/drbd
936 old_minor = old_line = None
938 if not line: # completely empty lines, as can be returned by drbd8.0+
940 lresult = cls._VALID_LINE_RE.match(line)
941 if lresult is not None:
942 if old_minor is not None:
943 results[old_minor] = old_line
944 old_minor = int(lresult.group(1))
947 if old_minor is not None:
948 old_line += " " + line.strip()
950 if old_minor is not None:
951 results[old_minor] = old_line
955 def _GetVersion(cls, proc_data):
956 """Return the DRBD version.
958 This will return a dict with keys:
964 - proto2 (only on drbd > 8.2.X)
967 first_line = proc_data[0].strip()
968 version = cls._VERSION_RE.match(first_line)
970 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
973 values = version.groups()
974 retval = {"k_major": int(values[0]),
975 "k_minor": int(values[1]),
976 "k_point": int(values[2]),
977 "api": int(values[3]),
978 "proto": int(values[4]),
980 if values[5] is not None:
981 retval["proto2"] = values[5]
986 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
987 """Returns DRBD usermode_helper currently set.
991 helper = utils.ReadFile(filename).splitlines()[0]
992 except EnvironmentError, err:
993 if err.errno == errno.ENOENT:
994 _ThrowError("The file %s cannot be opened, check if the module"
995 " is loaded (%s)", filename, str(err))
997 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
999 _ThrowError("Can't read any data from %s", filename)
1003 def _DevPath(minor):
1004 """Return the path to a drbd device for a given minor.
1007 return "/dev/drbd%d" % minor
1010 def GetUsedDevs(cls):
1011 """Compute the list of used DRBD devices.
1014 data = cls._GetProcData()
1018 match = cls._VALID_LINE_RE.match(line)
1021 minor = int(match.group(1))
1022 state = match.group(2)
1023 if state == cls._ST_UNCONFIGURED:
1025 used_devs[minor] = state, line
1029 def _SetFromMinor(self, minor):
1030 """Set our parameters based on the given minor.
1032 This sets our minor variable and our dev_path.
1036 self.minor = self.dev_path = None
1037 self.attached = False
1040 self.dev_path = self._DevPath(minor)
1041 self.attached = True
1044 def _CheckMetaSize(meta_device):
1045 """Check if the given meta device looks like a valid one.
1047 This currently only check the size, which must be around
1051 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1053 _ThrowError("Failed to get device size: %s - %s",
1054 result.fail_reason, result.output)
1056 sectors = int(result.stdout)
1057 except (TypeError, ValueError):
1058 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1059 num_bytes = sectors * 512
1060 if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1061 _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1062 # the maximum *valid* size of the meta device when living on top
1063 # of LVM is hard to compute: it depends on the number of stripes
1064 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1065 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1066 # size meta device; as such, we restrict it to 1GB (a little bit
1067 # too generous, but making assumptions about PE size is hard)
1068 if num_bytes > 1024 * 1024 * 1024:
1069 _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1071 def Rename(self, new_id):
1074 This is not supported for drbd devices.
1077 raise errors.ProgrammerError("Can't rename a drbd device")
1080 class DRBD8(BaseDRBD):
1081 """DRBD v8.x block device.
1083 This implements the local host part of the DRBD device, i.e. it
1084 doesn't do anything to the supposed peer. If you need a fully
1085 connected DRBD pair, you need to use this class on both hosts.
1087 The unique_id for the drbd device is the (local_ip, local_port,
1088 remote_ip, remote_port) tuple, and it must have two children: the
1089 data device and the meta_device. The meta device is checked for
1090 valid size and is zeroed on create.
1097 _NET_RECONFIG_TIMEOUT = 60
1099 def __init__(self, unique_id, children, size):
1100 if children and children.count(None) > 0:
1102 if len(children) not in (0, 2):
1103 raise ValueError("Invalid configuration data %s" % str(children))
1104 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1105 raise ValueError("Invalid configuration data %s" % str(unique_id))
1106 (self._lhost, self._lport,
1107 self._rhost, self._rport,
1108 self._aminor, self._secret) = unique_id
1110 if not _CanReadDevice(children[1].dev_path):
1111 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1113 super(DRBD8, self).__init__(unique_id, children, size)
1114 self.major = self._DRBD_MAJOR
1115 version = self._GetVersion(self._GetProcData())
1116 if version["k_major"] != 8 :
1117 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1118 " usage: kernel is %s.%s, ganeti wants 8.x",
1119 version["k_major"], version["k_minor"])
1121 if (self._lhost is not None and self._lhost == self._rhost and
1122 self._lport == self._rport):
1123 raise ValueError("Invalid configuration data, same local/remote %s" %
1128 def _InitMeta(cls, minor, dev_path):
1129 """Initialize a meta device.
1131 This will not work if the given minor is in use.
1134 # Zero the metadata first, in order to make sure drbdmeta doesn't
1135 # try to auto-detect existing filesystems or similar (see
1136 # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1137 # care about the first 128MB of data in the device, even though it
1139 result = utils.RunCmd([constants.DD_CMD,
1140 "if=/dev/zero", "of=%s" % dev_path,
1141 "bs=1048576", "count=128", "oflag=direct"])
1143 _ThrowError("Can't wipe the meta device: %s", result.output)
1145 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1146 "v08", dev_path, "0", "create-md"])
1148 _ThrowError("Can't initialize meta device: %s", result.output)
1151 def _FindUnusedMinor(cls):
1152 """Find an unused DRBD device.
1154 This is specific to 8.x as the minors are allocated dynamically,
1155 so non-existing numbers up to a max minor count are actually free.
1158 data = cls._GetProcData()
1162 match = cls._UNUSED_LINE_RE.match(line)
1164 return int(match.group(1))
1165 match = cls._VALID_LINE_RE.match(line)
1167 minor = int(match.group(1))
1168 highest = max(highest, minor)
1169 if highest is None: # there are no minors in use at all
1171 if highest >= cls._MAX_MINORS:
1172 logging.error("Error: no free drbd minors!")
1173 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1177 def _GetShowParser(cls):
1178 """Return a parser for `drbd show` output.
1180 This will either create or return an already-create parser for the
1181 output of the command `drbd show`.
1184 if cls._PARSE_SHOW is not None:
1185 return cls._PARSE_SHOW
1188 lbrace = pyp.Literal("{").suppress()
1189 rbrace = pyp.Literal("}").suppress()
1190 lbracket = pyp.Literal("[").suppress()
1191 rbracket = pyp.Literal("]").suppress()
1192 semi = pyp.Literal(";").suppress()
1193 colon = pyp.Literal(":").suppress()
1194 # this also converts the value to an int
1195 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1197 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1198 defa = pyp.Literal("_is_default").suppress()
1199 dbl_quote = pyp.Literal('"').suppress()
1201 keyword = pyp.Word(pyp.alphanums + '-')
1204 value = pyp.Word(pyp.alphanums + '_-/.:')
1205 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1206 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1207 pyp.Word(pyp.nums + ".") + colon + number)
1208 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1209 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1210 pyp.Optional(rbracket) + colon + number)
1211 # meta device, extended syntax
1212 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1213 # device name, extended syntax
1214 device_value = pyp.Literal("minor").suppress() + number
1217 stmt = (~rbrace + keyword + ~lbrace +
1218 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1220 pyp.Optional(defa) + semi +
1221 pyp.Optional(pyp.restOfLine).suppress())
1224 section_name = pyp.Word(pyp.alphas + "_")
1225 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1227 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1230 cls._PARSE_SHOW = bnf
1235 def _GetShowData(cls, minor):
1236 """Return the `drbdsetup show` data for a minor.
1239 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1241 logging.error("Can't display the drbd config: %s - %s",
1242 result.fail_reason, result.output)
1244 return result.stdout
1247 def _GetDevInfo(cls, out):
1248 """Parse details about a given DRBD minor.
1250 This return, if available, the local backing device (as a path)
1251 and the local and remote (ip, port) information from a string
1252 containing the output of the `drbdsetup show` command as returned
1260 bnf = cls._GetShowParser()
1264 results = bnf.parseString(out)
1265 except pyp.ParseException, err:
1266 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1268 # and massage the results into our desired format
1269 for section in results:
1271 if sname == "_this_host":
1272 for lst in section[1:]:
1273 if lst[0] == "disk":
1274 data["local_dev"] = lst[1]
1275 elif lst[0] == "meta-disk":
1276 data["meta_dev"] = lst[1]
1277 data["meta_index"] = lst[2]
1278 elif lst[0] == "address":
1279 data["local_addr"] = tuple(lst[1:])
1280 elif sname == "_remote_host":
1281 for lst in section[1:]:
1282 if lst[0] == "address":
1283 data["remote_addr"] = tuple(lst[1:])
1286 def _MatchesLocal(self, info):
1287 """Test if our local config matches with an existing device.
1289 The parameter should be as returned from `_GetDevInfo()`. This
1290 method tests if our local backing device is the same as the one in
1291 the info parameter, in effect testing if we look like the given
1296 backend, meta = self._children
1298 backend = meta = None
1300 if backend is not None:
1301 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1303 retval = ("local_dev" not in info)
1305 if meta is not None:
1306 retval = retval and ("meta_dev" in info and
1307 info["meta_dev"] == meta.dev_path)
1308 retval = retval and ("meta_index" in info and
1309 info["meta_index"] == 0)
1311 retval = retval and ("meta_dev" not in info and
1312 "meta_index" not in info)
1315 def _MatchesNet(self, info):
1316 """Test if our network config matches with an existing device.
1318 The parameter should be as returned from `_GetDevInfo()`. This
1319 method tests if our network configuration is the same as the one
1320 in the info parameter, in effect testing if we look like the given
1324 if (((self._lhost is None and not ("local_addr" in info)) and
1325 (self._rhost is None and not ("remote_addr" in info)))):
1328 if self._lhost is None:
1331 if not ("local_addr" in info and
1332 "remote_addr" in info):
1335 retval = (info["local_addr"] == (self._lhost, self._lport))
1336 retval = (retval and
1337 info["remote_addr"] == (self._rhost, self._rport))
1341 def _AssembleLocal(cls, minor, backend, meta, size):
1342 """Configure the local part of a DRBD device.
1345 args = ["drbdsetup", cls._DevPath(minor), "disk",
1350 args.extend(["-d", "%sm" % size])
1351 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1352 version = cls._GetVersion(cls._GetProcData())
1353 # various DRBD versions support different disk barrier options;
1354 # what we aim here is to revert back to the 'drain' method of
1355 # disk flushes and to disable metadata barriers, in effect going
1356 # back to pre-8.0.7 behaviour
1357 vmaj = version["k_major"]
1358 vmin = version["k_minor"]
1359 vrel = version["k_point"]
1361 if vmin == 0: # 8.0.x
1363 args.extend(["-i", "-m"])
1364 elif vmin == 2: # 8.2.x
1366 args.extend(["-i", "-m"])
1367 elif vmaj >= 3: # 8.3.x or newer
1368 args.extend(["-i", "-a", "m"])
1369 result = utils.RunCmd(args)
1371 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1374 def _AssembleNet(cls, minor, net_info, protocol,
1375 dual_pri=False, hmac=None, secret=None):
1376 """Configure the network part of the device.
1379 lhost, lport, rhost, rport = net_info
1380 if None in net_info:
1381 # we don't want network connection and actually want to make
1383 cls._ShutdownNet(minor)
1386 # Workaround for a race condition. When DRBD is doing its dance to
1387 # establish a connection with its peer, it also sends the
1388 # synchronization speed over the wire. In some cases setting the
1389 # sync speed only after setting up both sides can race with DRBD
1390 # connecting, hence we set it here before telling DRBD anything
1392 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1394 if netutils.IP6Address.IsValid(lhost):
1395 if not netutils.IP6Address.IsValid(rhost):
1396 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1397 (minor, lhost, rhost))
1399 elif netutils.IP4Address.IsValid(lhost):
1400 if not netutils.IP4Address.IsValid(rhost):
1401 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1402 (minor, lhost, rhost))
1405 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1407 args = ["drbdsetup", cls._DevPath(minor), "net",
1408 "%s:%s:%s" % (family, lhost, lport),
1409 "%s:%s:%s" % (family, rhost, rport), protocol,
1410 "-A", "discard-zero-changes",
1417 args.extend(["-a", hmac, "-x", secret])
1418 result = utils.RunCmd(args)
1420 _ThrowError("drbd%d: can't setup network: %s - %s",
1421 minor, result.fail_reason, result.output)
1423 def _CheckNetworkConfig():
1424 info = cls._GetDevInfo(cls._GetShowData(minor))
1425 if not "local_addr" in info or not "remote_addr" in info:
1426 raise utils.RetryAgain()
1428 if (info["local_addr"] != (lhost, lport) or
1429 info["remote_addr"] != (rhost, rport)):
1430 raise utils.RetryAgain()
1433 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1434 except utils.RetryTimeout:
1435 _ThrowError("drbd%d: timeout while configuring network", minor)
1437 def AddChildren(self, devices):
1438 """Add a disk to the DRBD device.
1441 if self.minor is None:
1442 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1444 if len(devices) != 2:
1445 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1446 info = self._GetDevInfo(self._GetShowData(self.minor))
1447 if "local_dev" in info:
1448 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1449 backend, meta = devices
1450 if backend.dev_path is None or meta.dev_path is None:
1451 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1454 self._CheckMetaSize(meta.dev_path)
1455 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1457 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1458 self._children = devices
1460 def RemoveChildren(self, devices):
1461 """Detach the drbd device from local storage.
1464 if self.minor is None:
1465 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1467 # early return if we don't actually have backing storage
1468 info = self._GetDevInfo(self._GetShowData(self.minor))
1469 if "local_dev" not in info:
1471 if len(self._children) != 2:
1472 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1474 if self._children.count(None) == 2: # we don't actually have children :)
1475 logging.warning("drbd%d: requested detach while detached", self.minor)
1477 if len(devices) != 2:
1478 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1479 for child, dev in zip(self._children, devices):
1480 if dev != child.dev_path:
1481 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1482 " RemoveChildren", self.minor, dev, child.dev_path)
1484 self._ShutdownLocal(self.minor)
1488 def _SetMinorSyncSpeed(cls, minor, kbytes):
1489 """Set the speed of the DRBD syncer.
1491 This is the low-level implementation.
1494 @param minor: the drbd minor whose settings we change
1496 @param kbytes: the speed in kbytes/second
1498 @return: the success of the operation
1501 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1502 "-r", "%d" % kbytes, "--create-device"])
1504 logging.error("Can't change syncer rate: %s - %s",
1505 result.fail_reason, result.output)
1506 return not result.failed
1508 def SetSyncSpeed(self, kbytes):
1509 """Set the speed of the DRBD syncer.
1512 @param kbytes: the speed in kbytes/second
1514 @return: the success of the operation
1517 if self.minor is None:
1518 logging.info("Not attached during SetSyncSpeed")
1520 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1521 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1523 def PauseResumeSync(self, pause):
1524 """Pauses or resumes the sync of a DRBD device.
1526 @param pause: Wether to pause or resume
1527 @return: the success of the operation
1530 if self.minor is None:
1531 logging.info("Not attached during PauseSync")
1534 children_result = super(DRBD8, self).PauseResumeSync(pause)
1541 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1543 logging.error("Can't %s: %s - %s", cmd,
1544 result.fail_reason, result.output)
1545 return not result.failed and children_result
1547 def GetProcStatus(self):
1548 """Return device data from /proc.
1551 if self.minor is None:
1552 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1553 proc_info = self._MassageProcData(self._GetProcData())
1554 if self.minor not in proc_info:
1555 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1556 return DRBD8Status(proc_info[self.minor])
1558 def GetSyncStatus(self):
1559 """Returns the sync status of the device.
1562 If sync_percent is None, it means all is ok
1563 If estimated_time is None, it means we can't estimate
1564 the time needed, otherwise it's the time left in seconds.
1567 We set the is_degraded parameter to True on two conditions:
1568 network not connected or local disk missing.
1570 We compute the ldisk parameter based on whether we have a local
1573 @rtype: objects.BlockDevStatus
1576 if self.minor is None and not self.Attach():
1577 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1579 stats = self.GetProcStatus()
1580 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1582 if stats.is_disk_uptodate:
1583 ldisk_status = constants.LDS_OKAY
1584 elif stats.is_diskless:
1585 ldisk_status = constants.LDS_FAULTY
1587 ldisk_status = constants.LDS_UNKNOWN
1589 return objects.BlockDevStatus(dev_path=self.dev_path,
1592 sync_percent=stats.sync_percent,
1593 estimated_time=stats.est_time,
1594 is_degraded=is_degraded,
1595 ldisk_status=ldisk_status)
1597 def Open(self, force=False):
1598 """Make the local state primary.
1600 If the 'force' parameter is given, the '-o' option is passed to
1601 drbdsetup. Since this is a potentially dangerous operation, the
1602 force flag should be only given after creation, when it actually
1606 if self.minor is None and not self.Attach():
1607 logging.error("DRBD cannot attach to a device during open")
1609 cmd = ["drbdsetup", self.dev_path, "primary"]
1612 result = utils.RunCmd(cmd)
1614 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1618 """Make the local state secondary.
1620 This will, of course, fail if the device is in use.
1623 if self.minor is None and not self.Attach():
1624 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1625 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1627 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1628 self.minor, result.output)
1630 def DisconnectNet(self):
1631 """Removes network configuration.
1633 This method shutdowns the network side of the device.
1635 The method will wait up to a hardcoded timeout for the device to
1636 go into standalone after the 'disconnect' command before
1637 re-configuring it, as sometimes it takes a while for the
1638 disconnect to actually propagate and thus we might issue a 'net'
1639 command while the device is still connected. If the device will
1640 still be attached to the network and we time out, we raise an
1644 if self.minor is None:
1645 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1647 if None in (self._lhost, self._lport, self._rhost, self._rport):
1648 _ThrowError("drbd%d: DRBD disk missing network info in"
1649 " DisconnectNet()", self.minor)
1651 class _DisconnectStatus:
1652 def __init__(self, ever_disconnected):
1653 self.ever_disconnected = ever_disconnected
1655 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1657 def _WaitForDisconnect():
1658 if self.GetProcStatus().is_standalone:
1661 # retry the disconnect, it seems possible that due to a well-time
1662 # disconnect on the peer, my disconnect command might be ignored and
1664 dstatus.ever_disconnected = \
1665 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1667 raise utils.RetryAgain()
1670 start_time = time.time()
1673 # Start delay at 100 milliseconds and grow up to 2 seconds
1674 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1675 self._NET_RECONFIG_TIMEOUT)
1676 except utils.RetryTimeout:
1677 if dstatus.ever_disconnected:
1678 msg = ("drbd%d: device did not react to the"
1679 " 'disconnect' command in a timely manner")
1681 msg = "drbd%d: can't shutdown network, even after multiple retries"
1683 _ThrowError(msg, self.minor)
1685 reconfig_time = time.time() - start_time
1686 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1687 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1688 self.minor, reconfig_time)
1690 def AttachNet(self, multimaster):
1691 """Reconnects the network.
1693 This method connects the network side of the device with a
1694 specified multi-master flag. The device needs to be 'Standalone'
1695 but have valid network configuration data.
1698 - multimaster: init the network in dual-primary mode
1701 if self.minor is None:
1702 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1704 if None in (self._lhost, self._lport, self._rhost, self._rport):
1705 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1707 status = self.GetProcStatus()
1709 if not status.is_standalone:
1710 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1712 self._AssembleNet(self.minor,
1713 (self._lhost, self._lport, self._rhost, self._rport),
1714 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1715 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1718 """Check if our minor is configured.
1720 This doesn't do any device configurations - it only checks if the
1721 minor is in a state different from Unconfigured.
1723 Note that this function will not change the state of the system in
1724 any way (except in case of side-effects caused by reading from
1728 used_devs = self.GetUsedDevs()
1729 if self._aminor in used_devs:
1730 minor = self._aminor
1734 self._SetFromMinor(minor)
1735 return minor is not None
1738 """Assemble the drbd.
1741 - if we have a configured device, we try to ensure that it matches
1743 - if not, we create it from zero
1746 super(DRBD8, self).Assemble()
1749 if self.minor is None:
1750 # local device completely unconfigured
1751 self._FastAssemble()
1753 # we have to recheck the local and network status and try to fix
1755 self._SlowAssemble()
1757 def _SlowAssemble(self):
1758 """Assembles the DRBD device from a (partially) configured device.
1760 In case of partially attached (local device matches but no network
1761 setup), we perform the network attach. If successful, we re-test
1762 the attach if can return success.
1765 # TODO: Rewrite to not use a for loop just because there is 'break'
1766 # pylint: disable-msg=W0631
1767 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1768 for minor in (self._aminor,):
1769 info = self._GetDevInfo(self._GetShowData(minor))
1770 match_l = self._MatchesLocal(info)
1771 match_r = self._MatchesNet(info)
1773 if match_l and match_r:
1774 # everything matches
1777 if match_l and not match_r and "local_addr" not in info:
1778 # disk matches, but not attached to network, attach and recheck
1779 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1780 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1781 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1784 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1785 " show' disagrees", minor)
1787 if match_r and "local_dev" not in info:
1788 # no local disk, but network attached and it matches
1789 self._AssembleLocal(minor, self._children[0].dev_path,
1790 self._children[1].dev_path, self.size)
1791 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1794 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1795 " show' disagrees", minor)
1797 # this case must be considered only if we actually have local
1798 # storage, i.e. not in diskless mode, because all diskless
1799 # devices are equal from the point of view of local
1801 if (match_l and "local_dev" in info and
1802 not match_r and "local_addr" in info):
1803 # strange case - the device network part points to somewhere
1804 # else, even though its local storage is ours; as we own the
1805 # drbd space, we try to disconnect from the remote peer and
1806 # reconnect to our correct one
1808 self._ShutdownNet(minor)
1809 except errors.BlockDeviceError, err:
1810 _ThrowError("drbd%d: device has correct local storage, wrong"
1811 " remote peer and is unable to disconnect in order"
1812 " to attach to the correct peer: %s", minor, str(err))
1813 # note: _AssembleNet also handles the case when we don't want
1814 # local storage (i.e. one or more of the _[lr](host|port) is
1816 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1817 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1818 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1821 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1822 " show' disagrees", minor)
1827 self._SetFromMinor(minor)
1829 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1832 def _FastAssemble(self):
1833 """Assemble the drbd device from zero.
1835 This is run when in Assemble we detect our minor is unused.
1838 minor = self._aminor
1839 if self._children and self._children[0] and self._children[1]:
1840 self._AssembleLocal(minor, self._children[0].dev_path,
1841 self._children[1].dev_path, self.size)
1842 if self._lhost and self._lport and self._rhost and self._rport:
1843 self._AssembleNet(minor,
1844 (self._lhost, self._lport, self._rhost, self._rport),
1845 constants.DRBD_NET_PROTOCOL,
1846 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1847 self._SetFromMinor(minor)
1850 def _ShutdownLocal(cls, minor):
1851 """Detach from the local device.
1853 I/Os will continue to be served from the remote device. If we
1854 don't have a remote device, this operation will fail.
1857 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1859 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1862 def _ShutdownNet(cls, minor):
1863 """Disconnect from the remote peer.
1865 This fails if we don't have a local device.
1868 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1870 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1873 def _ShutdownAll(cls, minor):
1874 """Deactivate the device.
1876 This will, of course, fail if the device is in use.
1879 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1881 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1882 minor, result.output)
1885 """Shutdown the DRBD device.
1888 if self.minor is None and not self.Attach():
1889 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1893 self.dev_path = None
1894 self._ShutdownAll(minor)
1897 """Stub remove for DRBD devices.
1903 def Create(cls, unique_id, children, size):
1904 """Create a new DRBD8 device.
1906 Since DRBD devices are not created per se, just assembled, this
1907 function only initializes the metadata.
1910 if len(children) != 2:
1911 raise errors.ProgrammerError("Invalid setup for the drbd device")
1912 # check that the minor is unused
1913 aminor = unique_id[4]
1914 proc_info = cls._MassageProcData(cls._GetProcData())
1915 if aminor in proc_info:
1916 status = DRBD8Status(proc_info[aminor])
1917 in_use = status.is_in_use
1921 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1924 if not meta.Attach():
1925 _ThrowError("drbd%d: can't attach to meta device '%s'",
1927 cls._CheckMetaSize(meta.dev_path)
1928 cls._InitMeta(aminor, meta.dev_path)
1929 return cls(unique_id, children, size)
1931 def Grow(self, amount, dryrun):
1932 """Resize the DRBD device and its backing storage.
1935 if self.minor is None:
1936 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1937 if len(self._children) != 2 or None in self._children:
1938 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1939 self._children[0].Grow(amount, dryrun)
1941 # DRBD does not support dry-run mode, so we'll return here
1943 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1944 "%dm" % (self.size + amount)])
1946 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1949 class FileStorage(BlockDev):
1952 This class represents the a file storage backend device.
1954 The unique_id for the file device is a (file_driver, file_path) tuple.
1957 def __init__(self, unique_id, children, size):
1958 """Initalizes a file device backend.
1962 raise errors.BlockDeviceError("Invalid setup for file device")
1963 super(FileStorage, self).__init__(unique_id, children, size)
1964 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1965 raise ValueError("Invalid configuration data %s" % str(unique_id))
1966 self.driver = unique_id[0]
1967 self.dev_path = unique_id[1]
1971 """Assemble the device.
1973 Checks whether the file device exists, raises BlockDeviceError otherwise.
1976 if not os.path.exists(self.dev_path):
1977 _ThrowError("File device '%s' does not exist" % self.dev_path)
1980 """Shutdown the device.
1982 This is a no-op for the file type, as we don't deactivate
1983 the file on shutdown.
1988 def Open(self, force=False):
1989 """Make the device ready for I/O.
1991 This is a no-op for the file type.
1997 """Notifies that the device will no longer be used for I/O.
1999 This is a no-op for the file type.
2005 """Remove the file backing the block device.
2008 @return: True if the removal was successful
2012 os.remove(self.dev_path)
2013 except OSError, err:
2014 if err.errno != errno.ENOENT:
2015 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2017 def Rename(self, new_id):
2018 """Renames the file.
2021 # TODO: implement rename for file-based storage
2022 _ThrowError("Rename is not supported for file-based storage")
2024 def Grow(self, amount, dryrun):
2027 @param amount: the amount (in mebibytes) to grow with
2030 # Check that the file exists
2032 current_size = self.GetActualSize()
2033 new_size = current_size + amount * 1024 * 1024
2034 assert new_size > current_size, "Cannot Grow with a negative amount"
2035 # We can't really simulate the growth
2039 f = open(self.dev_path, "a+")
2040 f.truncate(new_size)
2042 except EnvironmentError, err:
2043 _ThrowError("Error in file growth: %", str(err))
2046 """Attach to an existing file.
2048 Check if this file already exists.
2051 @return: True if file exists
2054 self.attached = os.path.exists(self.dev_path)
2055 return self.attached
2057 def GetActualSize(self):
2058 """Return the actual disk size.
2060 @note: the device needs to be active when this is called
2063 assert self.attached, "BlockDevice not attached in GetActualSize()"
2065 st = os.stat(self.dev_path)
2067 except OSError, err:
2068 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2071 def Create(cls, unique_id, children, size):
2072 """Create a new file.
2074 @param size: the size of file in MiB
2076 @rtype: L{bdev.FileStorage}
2077 @return: an instance of FileStorage
2080 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2081 raise ValueError("Invalid configuration data %s" % str(unique_id))
2082 dev_path = unique_id[1]
2084 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2085 f = os.fdopen(fd, "w")
2086 f.truncate(size * 1024 * 1024)
2088 except EnvironmentError, err:
2089 if err.errno == errno.EEXIST:
2090 _ThrowError("File already existing: %s", dev_path)
2091 _ThrowError("Error in file creation: %", str(err))
2093 return FileStorage(unique_id, children, size)
2096 class PersistentBlockDevice(BlockDev):
2097 """A block device with persistent node
2099 May be either directly attached, or exposed through DM (e.g. dm-multipath).
2100 udev helpers are probably required to give persistent, human-friendly
2103 For the time being, pathnames are required to lie under /dev.
2106 def __init__(self, unique_id, children, size):
2107 """Attaches to a static block device.
2109 The unique_id is a path under /dev.
2112 super(PersistentBlockDevice, self).__init__(unique_id, children, size)
2113 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2114 raise ValueError("Invalid configuration data %s" % str(unique_id))
2115 self.dev_path = unique_id[1]
2116 if not os.path.realpath(self.dev_path).startswith("/dev/"):
2117 raise ValueError("Full path '%s' lies outside /dev" %
2118 os.path.realpath(self.dev_path))
2119 # TODO: this is just a safety guard checking that we only deal with devices
2120 # we know how to handle. In the future this will be integrated with
2121 # external storage backends and possible values will probably be collected
2122 # from the cluster configuration.
2123 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2124 raise ValueError("Got persistent block device of invalid type: %s" %
2127 self.major = self.minor = None
2131 def Create(cls, unique_id, children, size):
2132 """Create a new device
2134 This is a noop, we only return a PersistentBlockDevice instance
2137 return PersistentBlockDevice(unique_id, children, 0)
2147 def Rename(self, new_id):
2148 """Rename this device.
2151 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2154 """Attach to an existing block device.
2158 self.attached = False
2160 st = os.stat(self.dev_path)
2161 except OSError, err:
2162 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2165 if not stat.S_ISBLK(st.st_mode):
2166 logging.error("%s is not a block device", self.dev_path)
2169 self.major = os.major(st.st_rdev)
2170 self.minor = os.minor(st.st_rdev)
2171 self.attached = True
2176 """Assemble the device.
2182 """Shutdown the device.
2187 def Open(self, force=False):
2188 """Make the device ready for I/O.
2194 """Notifies that the device will no longer be used for I/O.
2199 def Grow(self, amount, dryrun):
2200 """Grow the logical volume.
2203 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2207 constants.LD_LV: LogicalVolume,
2208 constants.LD_DRBD8: DRBD8,
2209 constants.LD_BLOCKDEV: PersistentBlockDevice,
2212 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2213 DEV_MAP[constants.LD_FILE] = FileStorage
2216 def FindDevice(dev_type, unique_id, children, size):
2217 """Search for an existing, assembled device.
2219 This will succeed only if the device exists and is assembled, but it
2220 does not do any actions in order to activate the device.
2223 if dev_type not in DEV_MAP:
2224 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2225 device = DEV_MAP[dev_type](unique_id, children, size)
2226 if not device.attached:
2231 def Assemble(dev_type, unique_id, children, size):
2232 """Try to attach or assemble an existing device.
2234 This will attach to assemble the device, as needed, to bring it
2235 fully up. It must be safe to run on already-assembled devices.
2238 if dev_type not in DEV_MAP:
2239 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2240 device = DEV_MAP[dev_type](unique_id, children, size)
2245 def Create(dev_type, unique_id, children, size):
2249 if dev_type not in DEV_MAP:
2250 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2251 device = DEV_MAP[dev_type].Create(unique_id, children, size)