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 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1135 "v08", dev_path, "0", "create-md"])
1137 _ThrowError("Can't initialize meta device: %s", result.output)
1140 def _FindUnusedMinor(cls):
1141 """Find an unused DRBD device.
1143 This is specific to 8.x as the minors are allocated dynamically,
1144 so non-existing numbers up to a max minor count are actually free.
1147 data = cls._GetProcData()
1151 match = cls._UNUSED_LINE_RE.match(line)
1153 return int(match.group(1))
1154 match = cls._VALID_LINE_RE.match(line)
1156 minor = int(match.group(1))
1157 highest = max(highest, minor)
1158 if highest is None: # there are no minors in use at all
1160 if highest >= cls._MAX_MINORS:
1161 logging.error("Error: no free drbd minors!")
1162 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1166 def _GetShowParser(cls):
1167 """Return a parser for `drbd show` output.
1169 This will either create or return an already-create parser for the
1170 output of the command `drbd show`.
1173 if cls._PARSE_SHOW is not None:
1174 return cls._PARSE_SHOW
1177 lbrace = pyp.Literal("{").suppress()
1178 rbrace = pyp.Literal("}").suppress()
1179 lbracket = pyp.Literal("[").suppress()
1180 rbracket = pyp.Literal("]").suppress()
1181 semi = pyp.Literal(";").suppress()
1182 colon = pyp.Literal(":").suppress()
1183 # this also converts the value to an int
1184 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1186 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1187 defa = pyp.Literal("_is_default").suppress()
1188 dbl_quote = pyp.Literal('"').suppress()
1190 keyword = pyp.Word(pyp.alphanums + '-')
1193 value = pyp.Word(pyp.alphanums + '_-/.:')
1194 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1195 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1196 pyp.Word(pyp.nums + ".") + colon + number)
1197 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1198 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1199 pyp.Optional(rbracket) + colon + number)
1200 # meta device, extended syntax
1201 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1202 # device name, extended syntax
1203 device_value = pyp.Literal("minor").suppress() + number
1206 stmt = (~rbrace + keyword + ~lbrace +
1207 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1209 pyp.Optional(defa) + semi +
1210 pyp.Optional(pyp.restOfLine).suppress())
1213 section_name = pyp.Word(pyp.alphas + '_')
1214 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1216 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1219 cls._PARSE_SHOW = bnf
1224 def _GetShowData(cls, minor):
1225 """Return the `drbdsetup show` data for a minor.
1228 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1230 logging.error("Can't display the drbd config: %s - %s",
1231 result.fail_reason, result.output)
1233 return result.stdout
1236 def _GetDevInfo(cls, out):
1237 """Parse details about a given DRBD minor.
1239 This return, if available, the local backing device (as a path)
1240 and the local and remote (ip, port) information from a string
1241 containing the output of the `drbdsetup show` command as returned
1249 bnf = cls._GetShowParser()
1253 results = bnf.parseString(out)
1254 except pyp.ParseException, err:
1255 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1257 # and massage the results into our desired format
1258 for section in results:
1260 if sname == "_this_host":
1261 for lst in section[1:]:
1262 if lst[0] == "disk":
1263 data["local_dev"] = lst[1]
1264 elif lst[0] == "meta-disk":
1265 data["meta_dev"] = lst[1]
1266 data["meta_index"] = lst[2]
1267 elif lst[0] == "address":
1268 data["local_addr"] = tuple(lst[1:])
1269 elif sname == "_remote_host":
1270 for lst in section[1:]:
1271 if lst[0] == "address":
1272 data["remote_addr"] = tuple(lst[1:])
1275 def _MatchesLocal(self, info):
1276 """Test if our local config matches with an existing device.
1278 The parameter should be as returned from `_GetDevInfo()`. This
1279 method tests if our local backing device is the same as the one in
1280 the info parameter, in effect testing if we look like the given
1285 backend, meta = self._children
1287 backend = meta = None
1289 if backend is not None:
1290 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1292 retval = ("local_dev" not in info)
1294 if meta is not None:
1295 retval = retval and ("meta_dev" in info and
1296 info["meta_dev"] == meta.dev_path)
1297 retval = retval and ("meta_index" in info and
1298 info["meta_index"] == 0)
1300 retval = retval and ("meta_dev" not in info and
1301 "meta_index" not in info)
1304 def _MatchesNet(self, info):
1305 """Test if our network config matches with an existing device.
1307 The parameter should be as returned from `_GetDevInfo()`. This
1308 method tests if our network configuration is the same as the one
1309 in the info parameter, in effect testing if we look like the given
1313 if (((self._lhost is None and not ("local_addr" in info)) and
1314 (self._rhost is None and not ("remote_addr" in info)))):
1317 if self._lhost is None:
1320 if not ("local_addr" in info and
1321 "remote_addr" in info):
1324 retval = (info["local_addr"] == (self._lhost, self._lport))
1325 retval = (retval and
1326 info["remote_addr"] == (self._rhost, self._rport))
1330 def _AssembleLocal(cls, minor, backend, meta, size):
1331 """Configure the local part of a DRBD device.
1334 args = ["drbdsetup", cls._DevPath(minor), "disk",
1339 args.extend(["-d", "%sm" % size])
1340 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1341 version = cls._GetVersion(cls._GetProcData())
1342 # various DRBD versions support different disk barrier options;
1343 # what we aim here is to revert back to the 'drain' method of
1344 # disk flushes and to disable metadata barriers, in effect going
1345 # back to pre-8.0.7 behaviour
1346 vmaj = version['k_major']
1347 vmin = version['k_minor']
1348 vrel = version['k_point']
1350 if vmin == 0: # 8.0.x
1352 args.extend(['-i', '-m'])
1353 elif vmin == 2: # 8.2.x
1355 args.extend(['-i', '-m'])
1356 elif vmaj >= 3: # 8.3.x or newer
1357 args.extend(['-i', '-a', 'm'])
1358 result = utils.RunCmd(args)
1360 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1363 def _AssembleNet(cls, minor, net_info, protocol,
1364 dual_pri=False, hmac=None, secret=None):
1365 """Configure the network part of the device.
1368 lhost, lport, rhost, rport = net_info
1369 if None in net_info:
1370 # we don't want network connection and actually want to make
1372 cls._ShutdownNet(minor)
1375 # Workaround for a race condition. When DRBD is doing its dance to
1376 # establish a connection with its peer, it also sends the
1377 # synchronization speed over the wire. In some cases setting the
1378 # sync speed only after setting up both sides can race with DRBD
1379 # connecting, hence we set it here before telling DRBD anything
1381 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1383 if netutils.IP6Address.IsValid(lhost):
1384 if not netutils.IP6Address.IsValid(rhost):
1385 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1386 (minor, lhost, rhost))
1388 elif netutils.IP4Address.IsValid(lhost):
1389 if not netutils.IP4Address.IsValid(rhost):
1390 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1391 (minor, lhost, rhost))
1394 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1396 args = ["drbdsetup", cls._DevPath(minor), "net",
1397 "%s:%s:%s" % (family, lhost, lport),
1398 "%s:%s:%s" % (family, rhost, rport), protocol,
1399 "-A", "discard-zero-changes",
1406 args.extend(["-a", hmac, "-x", secret])
1407 result = utils.RunCmd(args)
1409 _ThrowError("drbd%d: can't setup network: %s - %s",
1410 minor, result.fail_reason, result.output)
1412 def _CheckNetworkConfig():
1413 info = cls._GetDevInfo(cls._GetShowData(minor))
1414 if not "local_addr" in info or not "remote_addr" in info:
1415 raise utils.RetryAgain()
1417 if (info["local_addr"] != (lhost, lport) or
1418 info["remote_addr"] != (rhost, rport)):
1419 raise utils.RetryAgain()
1422 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1423 except utils.RetryTimeout:
1424 _ThrowError("drbd%d: timeout while configuring network", minor)
1426 def AddChildren(self, devices):
1427 """Add a disk to the DRBD device.
1430 if self.minor is None:
1431 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1433 if len(devices) != 2:
1434 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1435 info = self._GetDevInfo(self._GetShowData(self.minor))
1436 if "local_dev" in info:
1437 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1438 backend, meta = devices
1439 if backend.dev_path is None or meta.dev_path is None:
1440 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1443 self._CheckMetaSize(meta.dev_path)
1444 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1446 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1447 self._children = devices
1449 def RemoveChildren(self, devices):
1450 """Detach the drbd device from local storage.
1453 if self.minor is None:
1454 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1456 # early return if we don't actually have backing storage
1457 info = self._GetDevInfo(self._GetShowData(self.minor))
1458 if "local_dev" not in info:
1460 if len(self._children) != 2:
1461 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1463 if self._children.count(None) == 2: # we don't actually have children :)
1464 logging.warning("drbd%d: requested detach while detached", self.minor)
1466 if len(devices) != 2:
1467 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1468 for child, dev in zip(self._children, devices):
1469 if dev != child.dev_path:
1470 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1471 " RemoveChildren", self.minor, dev, child.dev_path)
1473 self._ShutdownLocal(self.minor)
1477 def _SetMinorSyncSpeed(cls, minor, kbytes):
1478 """Set the speed of the DRBD syncer.
1480 This is the low-level implementation.
1483 @param minor: the drbd minor whose settings we change
1485 @param kbytes: the speed in kbytes/second
1487 @return: the success of the operation
1490 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1491 "-r", "%d" % kbytes, "--create-device"])
1493 logging.error("Can't change syncer rate: %s - %s",
1494 result.fail_reason, result.output)
1495 return not result.failed
1497 def SetSyncSpeed(self, kbytes):
1498 """Set the speed of the DRBD syncer.
1501 @param kbytes: the speed in kbytes/second
1503 @return: the success of the operation
1506 if self.minor is None:
1507 logging.info("Not attached during SetSyncSpeed")
1509 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1510 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1512 def PauseResumeSync(self, pause):
1513 """Pauses or resumes the sync of a DRBD device.
1515 @param pause: Wether to pause or resume
1516 @return: the success of the operation
1519 if self.minor is None:
1520 logging.info("Not attached during PauseSync")
1523 children_result = super(DRBD8, self).PauseResumeSync(pause)
1530 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1532 logging.error("Can't %s: %s - %s", cmd,
1533 result.fail_reason, result.output)
1534 return not result.failed and children_result
1536 def GetProcStatus(self):
1537 """Return device data from /proc.
1540 if self.minor is None:
1541 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1542 proc_info = self._MassageProcData(self._GetProcData())
1543 if self.minor not in proc_info:
1544 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1545 return DRBD8Status(proc_info[self.minor])
1547 def GetSyncStatus(self):
1548 """Returns the sync status of the device.
1551 If sync_percent is None, it means all is ok
1552 If estimated_time is None, it means we can't estimate
1553 the time needed, otherwise it's the time left in seconds.
1556 We set the is_degraded parameter to True on two conditions:
1557 network not connected or local disk missing.
1559 We compute the ldisk parameter based on whether we have a local
1562 @rtype: objects.BlockDevStatus
1565 if self.minor is None and not self.Attach():
1566 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1568 stats = self.GetProcStatus()
1569 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1571 if stats.is_disk_uptodate:
1572 ldisk_status = constants.LDS_OKAY
1573 elif stats.is_diskless:
1574 ldisk_status = constants.LDS_FAULTY
1576 ldisk_status = constants.LDS_UNKNOWN
1578 return objects.BlockDevStatus(dev_path=self.dev_path,
1581 sync_percent=stats.sync_percent,
1582 estimated_time=stats.est_time,
1583 is_degraded=is_degraded,
1584 ldisk_status=ldisk_status)
1586 def Open(self, force=False):
1587 """Make the local state primary.
1589 If the 'force' parameter is given, the '-o' option is passed to
1590 drbdsetup. Since this is a potentially dangerous operation, the
1591 force flag should be only given after creation, when it actually
1595 if self.minor is None and not self.Attach():
1596 logging.error("DRBD cannot attach to a device during open")
1598 cmd = ["drbdsetup", self.dev_path, "primary"]
1601 result = utils.RunCmd(cmd)
1603 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1607 """Make the local state secondary.
1609 This will, of course, fail if the device is in use.
1612 if self.minor is None and not self.Attach():
1613 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1614 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1616 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1617 self.minor, result.output)
1619 def DisconnectNet(self):
1620 """Removes network configuration.
1622 This method shutdowns the network side of the device.
1624 The method will wait up to a hardcoded timeout for the device to
1625 go into standalone after the 'disconnect' command before
1626 re-configuring it, as sometimes it takes a while for the
1627 disconnect to actually propagate and thus we might issue a 'net'
1628 command while the device is still connected. If the device will
1629 still be attached to the network and we time out, we raise an
1633 if self.minor is None:
1634 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1636 if None in (self._lhost, self._lport, self._rhost, self._rport):
1637 _ThrowError("drbd%d: DRBD disk missing network info in"
1638 " DisconnectNet()", self.minor)
1640 class _DisconnectStatus:
1641 def __init__(self, ever_disconnected):
1642 self.ever_disconnected = ever_disconnected
1644 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1646 def _WaitForDisconnect():
1647 if self.GetProcStatus().is_standalone:
1650 # retry the disconnect, it seems possible that due to a well-time
1651 # disconnect on the peer, my disconnect command might be ignored and
1653 dstatus.ever_disconnected = \
1654 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1656 raise utils.RetryAgain()
1659 start_time = time.time()
1662 # Start delay at 100 milliseconds and grow up to 2 seconds
1663 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1664 self._NET_RECONFIG_TIMEOUT)
1665 except utils.RetryTimeout:
1666 if dstatus.ever_disconnected:
1667 msg = ("drbd%d: device did not react to the"
1668 " 'disconnect' command in a timely manner")
1670 msg = "drbd%d: can't shutdown network, even after multiple retries"
1672 _ThrowError(msg, self.minor)
1674 reconfig_time = time.time() - start_time
1675 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1676 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1677 self.minor, reconfig_time)
1679 def AttachNet(self, multimaster):
1680 """Reconnects the network.
1682 This method connects the network side of the device with a
1683 specified multi-master flag. The device needs to be 'Standalone'
1684 but have valid network configuration data.
1687 - multimaster: init the network in dual-primary mode
1690 if self.minor is None:
1691 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1693 if None in (self._lhost, self._lport, self._rhost, self._rport):
1694 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1696 status = self.GetProcStatus()
1698 if not status.is_standalone:
1699 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1701 self._AssembleNet(self.minor,
1702 (self._lhost, self._lport, self._rhost, self._rport),
1703 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1704 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1707 """Check if our minor is configured.
1709 This doesn't do any device configurations - it only checks if the
1710 minor is in a state different from Unconfigured.
1712 Note that this function will not change the state of the system in
1713 any way (except in case of side-effects caused by reading from
1717 used_devs = self.GetUsedDevs()
1718 if self._aminor in used_devs:
1719 minor = self._aminor
1723 self._SetFromMinor(minor)
1724 return minor is not None
1727 """Assemble the drbd.
1730 - if we have a configured device, we try to ensure that it matches
1732 - if not, we create it from zero
1735 super(DRBD8, self).Assemble()
1738 if self.minor is None:
1739 # local device completely unconfigured
1740 self._FastAssemble()
1742 # we have to recheck the local and network status and try to fix
1744 self._SlowAssemble()
1746 def _SlowAssemble(self):
1747 """Assembles the DRBD device from a (partially) configured device.
1749 In case of partially attached (local device matches but no network
1750 setup), we perform the network attach. If successful, we re-test
1751 the attach if can return success.
1754 # TODO: Rewrite to not use a for loop just because there is 'break'
1755 # pylint: disable-msg=W0631
1756 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1757 for minor in (self._aminor,):
1758 info = self._GetDevInfo(self._GetShowData(minor))
1759 match_l = self._MatchesLocal(info)
1760 match_r = self._MatchesNet(info)
1762 if match_l and match_r:
1763 # everything matches
1766 if match_l and not match_r and "local_addr" not in info:
1767 # disk matches, but not attached to network, attach and recheck
1768 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1769 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1770 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1773 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1774 " show' disagrees", minor)
1776 if match_r and "local_dev" not in info:
1777 # no local disk, but network attached and it matches
1778 self._AssembleLocal(minor, self._children[0].dev_path,
1779 self._children[1].dev_path, self.size)
1780 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1783 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1784 " show' disagrees", minor)
1786 # this case must be considered only if we actually have local
1787 # storage, i.e. not in diskless mode, because all diskless
1788 # devices are equal from the point of view of local
1790 if (match_l and "local_dev" in info and
1791 not match_r and "local_addr" in info):
1792 # strange case - the device network part points to somewhere
1793 # else, even though its local storage is ours; as we own the
1794 # drbd space, we try to disconnect from the remote peer and
1795 # reconnect to our correct one
1797 self._ShutdownNet(minor)
1798 except errors.BlockDeviceError, err:
1799 _ThrowError("drbd%d: device has correct local storage, wrong"
1800 " remote peer and is unable to disconnect in order"
1801 " to attach to the correct peer: %s", minor, str(err))
1802 # note: _AssembleNet also handles the case when we don't want
1803 # local storage (i.e. one or more of the _[lr](host|port) is
1805 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1806 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1807 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1810 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1811 " show' disagrees", minor)
1816 self._SetFromMinor(minor)
1818 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1821 def _FastAssemble(self):
1822 """Assemble the drbd device from zero.
1824 This is run when in Assemble we detect our minor is unused.
1827 minor = self._aminor
1828 if self._children and self._children[0] and self._children[1]:
1829 self._AssembleLocal(minor, self._children[0].dev_path,
1830 self._children[1].dev_path, self.size)
1831 if self._lhost and self._lport and self._rhost and self._rport:
1832 self._AssembleNet(minor,
1833 (self._lhost, self._lport, self._rhost, self._rport),
1834 constants.DRBD_NET_PROTOCOL,
1835 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1836 self._SetFromMinor(minor)
1839 def _ShutdownLocal(cls, minor):
1840 """Detach from the local device.
1842 I/Os will continue to be served from the remote device. If we
1843 don't have a remote device, this operation will fail.
1846 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1848 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1851 def _ShutdownNet(cls, minor):
1852 """Disconnect from the remote peer.
1854 This fails if we don't have a local device.
1857 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1859 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1862 def _ShutdownAll(cls, minor):
1863 """Deactivate the device.
1865 This will, of course, fail if the device is in use.
1868 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1870 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1871 minor, result.output)
1874 """Shutdown the DRBD device.
1877 if self.minor is None and not self.Attach():
1878 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1882 self.dev_path = None
1883 self._ShutdownAll(minor)
1886 """Stub remove for DRBD devices.
1892 def Create(cls, unique_id, children, size):
1893 """Create a new DRBD8 device.
1895 Since DRBD devices are not created per se, just assembled, this
1896 function only initializes the metadata.
1899 if len(children) != 2:
1900 raise errors.ProgrammerError("Invalid setup for the drbd device")
1901 # check that the minor is unused
1902 aminor = unique_id[4]
1903 proc_info = cls._MassageProcData(cls._GetProcData())
1904 if aminor in proc_info:
1905 status = DRBD8Status(proc_info[aminor])
1906 in_use = status.is_in_use
1910 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1913 if not meta.Attach():
1914 _ThrowError("drbd%d: can't attach to meta device '%s'",
1916 cls._CheckMetaSize(meta.dev_path)
1917 cls._InitMeta(aminor, meta.dev_path)
1918 return cls(unique_id, children, size)
1920 def Grow(self, amount, dryrun):
1921 """Resize the DRBD device and its backing storage.
1924 if self.minor is None:
1925 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1926 if len(self._children) != 2 or None in self._children:
1927 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1928 self._children[0].Grow(amount, dryrun)
1930 # DRBD does not support dry-run mode, so we'll return here
1932 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1933 "%dm" % (self.size + amount)])
1935 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1938 class FileStorage(BlockDev):
1941 This class represents the a file storage backend device.
1943 The unique_id for the file device is a (file_driver, file_path) tuple.
1946 def __init__(self, unique_id, children, size):
1947 """Initalizes a file device backend.
1951 raise errors.BlockDeviceError("Invalid setup for file device")
1952 super(FileStorage, self).__init__(unique_id, children, size)
1953 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1954 raise ValueError("Invalid configuration data %s" % str(unique_id))
1955 self.driver = unique_id[0]
1956 self.dev_path = unique_id[1]
1960 """Assemble the device.
1962 Checks whether the file device exists, raises BlockDeviceError otherwise.
1965 if not os.path.exists(self.dev_path):
1966 _ThrowError("File device '%s' does not exist" % self.dev_path)
1969 """Shutdown the device.
1971 This is a no-op for the file type, as we don't deactivate
1972 the file on shutdown.
1977 def Open(self, force=False):
1978 """Make the device ready for I/O.
1980 This is a no-op for the file type.
1986 """Notifies that the device will no longer be used for I/O.
1988 This is a no-op for the file type.
1994 """Remove the file backing the block device.
1997 @return: True if the removal was successful
2001 os.remove(self.dev_path)
2002 except OSError, err:
2003 if err.errno != errno.ENOENT:
2004 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2006 def Rename(self, new_id):
2007 """Renames the file.
2010 # TODO: implement rename for file-based storage
2011 _ThrowError("Rename is not supported for file-based storage")
2013 def Grow(self, amount, dryrun):
2016 @param amount: the amount (in mebibytes) to grow with
2019 # Check that the file exists
2021 current_size = self.GetActualSize()
2022 new_size = current_size + amount * 1024 * 1024
2023 assert new_size > current_size, "Cannot Grow with a negative amount"
2024 # We can't really simulate the growth
2028 f = open(self.dev_path, "a+")
2029 f.truncate(new_size)
2031 except EnvironmentError, err:
2032 _ThrowError("Error in file growth: %", str(err))
2035 """Attach to an existing file.
2037 Check if this file already exists.
2040 @return: True if file exists
2043 self.attached = os.path.exists(self.dev_path)
2044 return self.attached
2046 def GetActualSize(self):
2047 """Return the actual disk size.
2049 @note: the device needs to be active when this is called
2052 assert self.attached, "BlockDevice not attached in GetActualSize()"
2054 st = os.stat(self.dev_path)
2056 except OSError, err:
2057 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2060 def Create(cls, unique_id, children, size):
2061 """Create a new file.
2063 @param size: the size of file in MiB
2065 @rtype: L{bdev.FileStorage}
2066 @return: an instance of FileStorage
2069 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2070 raise ValueError("Invalid configuration data %s" % str(unique_id))
2071 dev_path = unique_id[1]
2073 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2074 f = os.fdopen(fd, "w")
2075 f.truncate(size * 1024 * 1024)
2077 except EnvironmentError, err:
2078 if err.errno == errno.EEXIST:
2079 _ThrowError("File already existing: %s", dev_path)
2080 _ThrowError("Error in file creation: %", str(err))
2082 return FileStorage(unique_id, children, size)
2085 class PersistentBlockDevice(BlockDev):
2086 """A block device with persistent node
2088 May be either directly attached, or exposed through DM (e.g. dm-multipath).
2089 udev helpers are probably required to give persistent, human-friendly
2092 For the time being, pathnames are required to lie under /dev.
2095 def __init__(self, unique_id, children, size):
2096 """Attaches to a static block device.
2098 The unique_id is a path under /dev.
2101 super(PersistentBlockDevice, self).__init__(unique_id, children, size)
2102 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2103 raise ValueError("Invalid configuration data %s" % str(unique_id))
2104 self.dev_path = unique_id[1]
2105 if not os.path.realpath(self.dev_path).startswith('/dev/'):
2106 raise ValueError("Full path '%s' lies outside /dev" %
2107 os.path.realpath(self.dev_path))
2108 # TODO: this is just a safety guard checking that we only deal with devices
2109 # we know how to handle. In the future this will be integrated with
2110 # external storage backends and possible values will probably be collected
2111 # from the cluster configuration.
2112 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2113 raise ValueError("Got persistent block device of invalid type: %s" %
2116 self.major = self.minor = None
2120 def Create(cls, unique_id, children, size):
2121 """Create a new device
2123 This is a noop, we only return a PersistentBlockDevice instance
2126 return PersistentBlockDevice(unique_id, children, 0)
2136 def Rename(self, new_id):
2137 """Rename this device.
2140 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2143 """Attach to an existing block device.
2147 self.attached = False
2149 st = os.stat(self.dev_path)
2150 except OSError, err:
2151 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2154 if not stat.S_ISBLK(st.st_mode):
2155 logging.error("%s is not a block device", self.dev_path)
2158 self.major = os.major(st.st_rdev)
2159 self.minor = os.minor(st.st_rdev)
2160 self.attached = True
2165 """Assemble the device.
2171 """Shutdown the device.
2176 def Open(self, force=False):
2177 """Make the device ready for I/O.
2183 """Notifies that the device will no longer be used for I/O.
2188 def Grow(self, amount, dryrun):
2189 """Grow the logical volume.
2192 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2196 constants.LD_LV: LogicalVolume,
2197 constants.LD_DRBD8: DRBD8,
2198 constants.LD_BLOCKDEV: PersistentBlockDevice,
2201 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2202 DEV_MAP[constants.LD_FILE] = FileStorage
2205 def FindDevice(dev_type, unique_id, children, size):
2206 """Search for an existing, assembled device.
2208 This will succeed only if the device exists and is assembled, but it
2209 does not do any actions in order to activate the device.
2212 if dev_type not in DEV_MAP:
2213 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2214 device = DEV_MAP[dev_type](unique_id, children, size)
2215 if not device.attached:
2220 def Assemble(dev_type, unique_id, children, size):
2221 """Try to attach or assemble an existing device.
2223 This will attach to assemble the device, as needed, to bring it
2224 fully up. It must be safe to run on already-assembled devices.
2227 if dev_type not in DEV_MAP:
2228 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2229 device = DEV_MAP[dev_type](unique_id, children, size)
2234 def Create(dev_type, unique_id, children, size):
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].Create(unique_id, children, size)