4 # Copyright (C) 2006, 2007, 2010 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"""
27 import pyparsing as pyp
31 from ganeti import utils
32 from ganeti import errors
33 from ganeti import constants
34 from ganeti import objects
35 from ganeti import compat
36 from ganeti import netutils
39 # Size of reads in _CanReadDevice
40 _DEVICE_READ_SIZE = 128 * 1024
43 def _IgnoreError(fn, *args, **kwargs):
44 """Executes the given function, ignoring BlockDeviceErrors.
46 This is used in order to simplify the execution of cleanup or
50 @return: True when fn didn't raise an exception, False otherwise
56 except errors.BlockDeviceError, err:
57 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
61 def _ThrowError(msg, *args):
62 """Log an error to the node daemon and the raise an exception.
65 @param msg: the text of the exception
66 @raise errors.BlockDeviceError
72 raise errors.BlockDeviceError(msg)
75 def _CanReadDevice(path):
76 """Check if we can read from the given device.
78 This tries to read the first 128k of the device.
82 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
84 except EnvironmentError:
85 logging.warning("Can't read from device %s", path, exc_info=True)
89 class BlockDev(object):
90 """Block device abstract class.
92 A block device can be in the following states:
93 - not existing on the system, and by `Create()` it goes into:
94 - existing but not setup/not active, and by `Assemble()` goes into:
95 - active read-write and by `Open()` it goes into
96 - online (=used, or ready for use)
98 A device can also be online but read-only, however we are not using
99 the readonly state (LV has it, if needed in the future) and we are
100 usually looking at this like at a stack, so it's easier to
101 conceptualise the transition from not-existing to online and back
104 The many different states of the device are due to the fact that we
105 need to cover many device types:
106 - logical volumes are created, lvchange -a y $lv, and used
107 - drbd devices are attached to a local disk/remote peer and made primary
109 A block device is identified by three items:
110 - the /dev path of the device (dynamic)
111 - a unique ID of the device (static)
112 - it's major/minor pair (dynamic)
114 Not all devices implement both the first two as distinct items. LVM
115 logical volumes have their unique ID (the pair volume group, logical
116 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
117 the /dev path is again dynamic and the unique id is the pair (host1,
118 dev1), (host2, dev2).
120 You can get to a device in two ways:
121 - creating the (real) device, which returns you
122 an attached instance (lvcreate)
123 - attaching of a python instance to an existing (real) device
125 The second point, the attachement to a device, is different
126 depending on whether the device is assembled or not. At init() time,
127 we search for a device with the same unique_id as us. If found,
128 good. It also means that the device is already assembled. If not,
129 after assembly we'll have our correct major/minor.
132 def __init__(self, unique_id, children, size):
133 self._children = children
135 self.unique_id = unique_id
138 self.attached = False
142 """Assemble the device from its components.
144 Implementations of this method by child classes must ensure that:
145 - after the device has been assembled, it knows its major/minor
146 numbers; this allows other devices (usually parents) to probe
147 correctly for their children
148 - calling this method on an existing, in-use device is safe
149 - if the device is already configured (and in an OK state),
150 this method is idempotent
156 """Find a device which matches our config and attach to it.
159 raise NotImplementedError
162 """Notifies that the device will no longer be used for I/O.
165 raise NotImplementedError
168 def Create(cls, unique_id, children, size):
169 """Create the device.
171 If the device cannot be created, it will return None
172 instead. Error messages go to the logging system.
174 Note that for some devices, the unique_id is used, and for other,
175 the children. The idea is that these two, taken together, are
176 enough for both creation and assembly (later).
179 raise NotImplementedError
182 """Remove this device.
184 This makes sense only for some of the device types: LV and file
185 storage. Also note that if the device can't attach, the removal
189 raise NotImplementedError
191 def Rename(self, new_id):
192 """Rename this device.
194 This may or may not make sense for a given device type.
197 raise NotImplementedError
199 def Open(self, force=False):
200 """Make the device ready for use.
202 This makes the device ready for I/O. For now, just the DRBD
205 The force parameter signifies that if the device has any kind of
206 --force thing, it should be used, we know what we are doing.
209 raise NotImplementedError
212 """Shut down the device, freeing its children.
214 This undoes the `Assemble()` work, except for the child
215 assembling; as such, the children on the device are still
216 assembled after this call.
219 raise NotImplementedError
221 def SetSyncSpeed(self, speed):
222 """Adjust the sync speed of the mirror.
224 In case this is not a mirroring device, this is no-op.
229 for child in self._children:
230 result = result and child.SetSyncSpeed(speed)
233 def PauseResumeSync(self, pause):
234 """Pause/Resume the sync of the mirror.
236 In case this is not a mirroring device, this is no-op.
238 @param pause: Wheater to pause or resume
243 for child in self._children:
244 result = result and child.PauseResumeSync(pause)
247 def GetSyncStatus(self):
248 """Returns the sync status of the device.
250 If this device is a mirroring device, this function returns the
251 status of the mirror.
253 If sync_percent is None, it means the device is not syncing.
255 If estimated_time is None, it means we can't estimate
256 the time needed, otherwise it's the time left in seconds.
258 If is_degraded is True, it means the device is missing
259 redundancy. This is usually a sign that something went wrong in
260 the device setup, if sync_percent is None.
262 The ldisk parameter represents the degradation of the local
263 data. This is only valid for some devices, the rest will always
264 return False (not degraded).
266 @rtype: objects.BlockDevStatus
269 return objects.BlockDevStatus(dev_path=self.dev_path,
275 ldisk_status=constants.LDS_OKAY)
277 def CombinedSyncStatus(self):
278 """Calculate the mirror status recursively for our children.
280 The return value is the same as for `GetSyncStatus()` except the
281 minimum percent and maximum time are calculated across our
284 @rtype: objects.BlockDevStatus
287 status = self.GetSyncStatus()
289 min_percent = status.sync_percent
290 max_time = status.estimated_time
291 is_degraded = status.is_degraded
292 ldisk_status = status.ldisk_status
295 for child in self._children:
296 child_status = child.GetSyncStatus()
298 if min_percent is None:
299 min_percent = child_status.sync_percent
300 elif child_status.sync_percent is not None:
301 min_percent = min(min_percent, child_status.sync_percent)
304 max_time = child_status.estimated_time
305 elif child_status.estimated_time is not None:
306 max_time = max(max_time, child_status.estimated_time)
308 is_degraded = is_degraded or child_status.is_degraded
310 if ldisk_status is None:
311 ldisk_status = child_status.ldisk_status
312 elif child_status.ldisk_status is not None:
313 ldisk_status = max(ldisk_status, child_status.ldisk_status)
315 return objects.BlockDevStatus(dev_path=self.dev_path,
318 sync_percent=min_percent,
319 estimated_time=max_time,
320 is_degraded=is_degraded,
321 ldisk_status=ldisk_status)
324 def SetInfo(self, text):
325 """Update metadata with info text.
327 Only supported for some device types.
330 for child in self._children:
333 def Grow(self, amount):
334 """Grow the block device.
336 @param amount: the amount (in mebibytes) to grow with
339 raise NotImplementedError
341 def GetActualSize(self):
342 """Return the actual disk size.
344 @note: the device needs to be active when this is called
347 assert self.attached, "BlockDevice not attached in GetActualSize()"
348 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
350 _ThrowError("blockdev failed (%s): %s",
351 result.fail_reason, result.output)
353 sz = int(result.output.strip())
354 except (ValueError, TypeError), err:
355 _ThrowError("Failed to parse blockdev output: %s", str(err))
359 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
360 (self.__class__, self.unique_id, self._children,
361 self.major, self.minor, self.dev_path))
364 class LogicalVolume(BlockDev):
365 """Logical Volume block device.
368 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
369 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
370 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
372 def __init__(self, unique_id, children, size):
373 """Attaches to a LV device.
375 The unique_id is a tuple (vg_name, lv_name)
378 super(LogicalVolume, self).__init__(unique_id, children, size)
379 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
380 raise ValueError("Invalid configuration data %s" % str(unique_id))
381 self._vg_name, self._lv_name = unique_id
382 self._ValidateName(self._vg_name)
383 self._ValidateName(self._lv_name)
384 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
385 self._degraded = True
386 self.major = self.minor = self.pe_size = self.stripe_count = None
390 def Create(cls, unique_id, children, size):
391 """Create a new logical volume.
394 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
395 raise errors.ProgrammerError("Invalid configuration data %s" %
397 vg_name, lv_name = unique_id
398 cls._ValidateName(vg_name)
399 cls._ValidateName(lv_name)
400 pvs_info = cls.GetPVInfo([vg_name])
402 _ThrowError("Can't compute PV info for vg %s", vg_name)
406 pvlist = [ pv[1] for pv in pvs_info ]
407 if compat.any(":" in v for v in pvlist):
408 _ThrowError("Some of your PVs have the invalid character ':' in their"
409 " name, this is not supported - please filter them out"
410 " in lvm.conf using either 'filter' or 'preferred_names'")
411 free_size = sum([ pv[0] for pv in pvs_info ])
412 current_pvs = len(pvlist)
413 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
415 # The size constraint should have been checked from the master before
416 # calling the create function.
418 _ThrowError("Not enough free space: required %s,"
419 " available %s", size, free_size)
420 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
421 # If the free space is not well distributed, we won't be able to
422 # create an optimally-striped volume; in that case, we want to try
423 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
425 for stripes_arg in range(stripes, 0, -1):
426 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
427 if not result.failed:
430 _ThrowError("LV create failed (%s): %s",
431 result.fail_reason, result.output)
432 return LogicalVolume(unique_id, children, size)
435 def _GetVolumeInfo(lvm_cmd, fields):
436 """Returns LVM Volumen infos using lvm_cmd
438 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
439 @param fields: Fields to return
440 @return: A list of dicts each with the parsed fields
444 raise errors.ProgrammerError("No fields specified")
447 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
448 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
450 result = utils.RunCmd(cmd)
452 raise errors.CommandError("Can't get the volume information: %s - %s" %
453 (result.fail_reason, result.output))
456 for line in result.stdout.splitlines():
457 splitted_fields = line.strip().split(sep)
459 if len(fields) != len(splitted_fields):
460 raise errors.CommandError("Can't parse %s output: line '%s'" %
463 data.append(splitted_fields)
468 def GetPVInfo(cls, vg_names, filter_allocatable=True):
469 """Get the free space info for PVs in a volume group.
471 @param vg_names: list of volume group names, if empty all will be returned
472 @param filter_allocatable: whether to skip over unallocatable PVs
475 @return: list of tuples (free_space, name) with free_space in mebibytes
479 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
481 except errors.GenericError, err:
482 logging.error("Can't get PV information: %s", err)
486 for pv_name, vg_name, pv_free, pv_attr in info:
487 # (possibly) skip over pvs which are not allocatable
488 if filter_allocatable and pv_attr[0] != "a":
490 # (possibly) skip over pvs which are not in the right volume group(s)
491 if vg_names and vg_name not in vg_names:
493 data.append((float(pv_free), pv_name, vg_name))
498 def GetVGInfo(cls, vg_names, filter_readonly=True):
499 """Get the free space info for specific VGs.
501 @param vg_names: list of volume group names, if empty all will be returned
502 @param filter_readonly: whether to skip over readonly VGs
505 @return: list of tuples (free_space, total_size, name) with free_space in
510 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
512 except errors.GenericError, err:
513 logging.error("Can't get VG information: %s", err)
517 for vg_name, vg_free, vg_attr, vg_size in info:
518 # (possibly) skip over vgs which are not writable
519 if filter_readonly and vg_attr[0] == "r":
521 # (possibly) skip over vgs which are not in the right volume group(s)
522 if vg_names and vg_name not in vg_names:
524 data.append((float(vg_free), float(vg_size), vg_name))
529 def _ValidateName(cls, name):
530 """Validates that a given name is valid as VG or LV name.
532 The list of valid characters and restricted names is taken out of
533 the lvm(8) manpage, with the simplification that we enforce both
534 VG and LV restrictions on the names.
537 if (not cls._VALID_NAME_RE.match(name) or
538 name in cls._INVALID_NAMES or
539 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
540 _ThrowError("Invalid LVM name '%s'", name)
543 """Remove this logical volume.
546 if not self.minor and not self.Attach():
547 # the LV does not exist
549 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
550 (self._vg_name, self._lv_name)])
552 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
554 def Rename(self, new_id):
555 """Rename this logical volume.
558 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
559 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
560 new_vg, new_name = new_id
561 if new_vg != self._vg_name:
562 raise errors.ProgrammerError("Can't move a logical volume across"
563 " volume groups (from %s to to %s)" %
564 (self._vg_name, new_vg))
565 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
567 _ThrowError("Failed to rename the logical volume: %s", result.output)
568 self._lv_name = new_name
569 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
572 """Attach to an existing LV.
574 This method will try to see if an existing and active LV exists
575 which matches our name. If so, its major/minor will be
579 self.attached = False
580 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
581 "--units=m", "--nosuffix",
582 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
583 "vg_extent_size,stripes", self.dev_path])
585 logging.error("Can't find LV %s: %s, %s",
586 self.dev_path, result.fail_reason, result.output)
588 # the output can (and will) have multiple lines for multi-segment
589 # LVs, as the 'stripes' parameter is a segment one, so we take
590 # only the last entry, which is the one we're interested in; note
591 # that with LVM2 anyway the 'stripes' value must be constant
592 # across segments, so this is a no-op actually
593 out = result.stdout.splitlines()
594 if not out: # totally empty result? splitlines() returns at least
595 # one line for any non-empty string
596 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
598 out = out[-1].strip().rstrip(',')
601 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
604 status, major, minor, pe_size, stripes = out
606 logging.error("lvs lv_attr is not 6 characters (%s)", status)
612 except (TypeError, ValueError), err:
613 logging.error("lvs major/minor cannot be parsed: %s", str(err))
616 pe_size = int(float(pe_size))
617 except (TypeError, ValueError), err:
618 logging.error("Can't parse vg extent size: %s", err)
622 stripes = int(stripes)
623 except (TypeError, ValueError), err:
624 logging.error("Can't parse the number of stripes: %s", err)
629 self.pe_size = pe_size
630 self.stripe_count = stripes
631 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
637 """Assemble the device.
639 We always run `lvchange -ay` on the LV to ensure it's active before
640 use, as there were cases when xenvg was not active after boot
641 (also possibly after disk issues).
644 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
646 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
649 """Shutdown the device.
651 This is a no-op for the LV device type, as we don't deactivate the
657 def GetSyncStatus(self):
658 """Returns the sync status of the device.
660 If this device is a mirroring device, this function returns the
661 status of the mirror.
663 For logical volumes, sync_percent and estimated_time are always
664 None (no recovery in progress, as we don't handle the mirrored LV
665 case). The is_degraded parameter is the inverse of the ldisk
668 For the ldisk parameter, we check if the logical volume has the
669 'virtual' type, which means it's not backed by existing storage
670 anymore (read from it return I/O error). This happens after a
671 physical disk failure and subsequent 'vgreduce --removemissing' on
674 The status was already read in Attach, so we just return it.
676 @rtype: objects.BlockDevStatus
680 ldisk_status = constants.LDS_FAULTY
682 ldisk_status = constants.LDS_OKAY
684 return objects.BlockDevStatus(dev_path=self.dev_path,
689 is_degraded=self._degraded,
690 ldisk_status=ldisk_status)
692 def Open(self, force=False):
693 """Make the device ready for I/O.
695 This is a no-op for the LV device type.
701 """Notifies that the device will no longer be used for I/O.
703 This is a no-op for the LV device type.
708 def Snapshot(self, size):
709 """Create a snapshot copy of an lvm block device.
711 @returns: tuple (vg, lv)
714 snap_name = self._lv_name + ".snap"
716 # remove existing snapshot if found
717 snap = LogicalVolume((self._vg_name, snap_name), None, size)
718 _IgnoreError(snap.Remove)
720 vg_info = self.GetVGInfo([self._vg_name])
722 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
723 free_size, _, _ = vg_info[0]
725 _ThrowError("Not enough free space: required %s,"
726 " available %s", size, free_size)
728 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
729 "-n%s" % snap_name, self.dev_path])
731 _ThrowError("command: %s error: %s - %s",
732 result.cmd, result.fail_reason, result.output)
734 return (self._vg_name, snap_name)
736 def SetInfo(self, text):
737 """Update metadata with info text.
740 BlockDev.SetInfo(self, text)
742 # Replace invalid characters
743 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
744 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
746 # Only up to 128 characters are allowed
749 result = utils.RunCmd(["lvchange", "--addtag", text,
752 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
755 def Grow(self, amount):
756 """Grow the logical volume.
759 if self.pe_size is None or self.stripe_count is None:
760 if not self.Attach():
761 _ThrowError("Can't attach to LV during Grow()")
762 full_stripe_size = self.pe_size * self.stripe_count
763 rest = amount % full_stripe_size
765 amount += full_stripe_size - rest
766 # we try multiple algorithms since the 'best' ones might not have
767 # space available in the right place, but later ones might (since
768 # they have less constraints); also note that only recent LVM
770 for alloc_policy in "contiguous", "cling", "normal":
771 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
772 "-L", "+%dm" % amount, self.dev_path])
773 if not result.failed:
775 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
778 class DRBD8Status(object):
779 """A DRBD status representation class.
781 Note that this doesn't support unconfigured devices (cs:Unconfigured).
784 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
785 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
786 "\s+ds:([^/]+)/(\S+)\s+.*$")
787 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
788 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
790 CS_UNCONFIGURED = "Unconfigured"
791 CS_STANDALONE = "StandAlone"
792 CS_WFCONNECTION = "WFConnection"
793 CS_WFREPORTPARAMS = "WFReportParams"
794 CS_CONNECTED = "Connected"
795 CS_STARTINGSYNCS = "StartingSyncS"
796 CS_STARTINGSYNCT = "StartingSyncT"
797 CS_WFBITMAPS = "WFBitMapS"
798 CS_WFBITMAPT = "WFBitMapT"
799 CS_WFSYNCUUID = "WFSyncUUID"
800 CS_SYNCSOURCE = "SyncSource"
801 CS_SYNCTARGET = "SyncTarget"
802 CS_PAUSEDSYNCS = "PausedSyncS"
803 CS_PAUSEDSYNCT = "PausedSyncT"
804 CSET_SYNC = frozenset([
817 DS_DISKLESS = "Diskless"
818 DS_ATTACHING = "Attaching" # transient state
819 DS_FAILED = "Failed" # transient state, next: diskless
820 DS_NEGOTIATING = "Negotiating" # transient state
821 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
822 DS_OUTDATED = "Outdated"
823 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
824 DS_CONSISTENT = "Consistent"
825 DS_UPTODATE = "UpToDate" # normal state
827 RO_PRIMARY = "Primary"
828 RO_SECONDARY = "Secondary"
829 RO_UNKNOWN = "Unknown"
831 def __init__(self, procline):
832 u = self.UNCONF_RE.match(procline)
834 self.cstatus = self.CS_UNCONFIGURED
835 self.lrole = self.rrole = self.ldisk = self.rdisk = None
837 m = self.LINE_RE.match(procline)
839 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
840 self.cstatus = m.group(1)
841 self.lrole = m.group(2)
842 self.rrole = m.group(3)
843 self.ldisk = m.group(4)
844 self.rdisk = m.group(5)
846 # end reading of data from the LINE_RE or UNCONF_RE
848 self.is_standalone = self.cstatus == self.CS_STANDALONE
849 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
850 self.is_connected = self.cstatus == self.CS_CONNECTED
851 self.is_primary = self.lrole == self.RO_PRIMARY
852 self.is_secondary = self.lrole == self.RO_SECONDARY
853 self.peer_primary = self.rrole == self.RO_PRIMARY
854 self.peer_secondary = self.rrole == self.RO_SECONDARY
855 self.both_primary = self.is_primary and self.peer_primary
856 self.both_secondary = self.is_secondary and self.peer_secondary
858 self.is_diskless = self.ldisk == self.DS_DISKLESS
859 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
861 self.is_in_resync = self.cstatus in self.CSET_SYNC
862 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
864 m = self.SYNC_RE.match(procline)
866 self.sync_percent = float(m.group(1))
867 hours = int(m.group(2))
868 minutes = int(m.group(3))
869 seconds = int(m.group(4))
870 self.est_time = hours * 3600 + minutes * 60 + seconds
872 # we have (in this if branch) no percent information, but if
873 # we're resyncing we need to 'fake' a sync percent information,
874 # as this is how cmdlib determines if it makes sense to wait for
876 if self.is_in_resync:
877 self.sync_percent = 0
879 self.sync_percent = None
883 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
886 This class contains a few bits of common functionality between the
887 0.7 and 8.x versions of DRBD.
890 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
891 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
892 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
893 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
896 _ST_UNCONFIGURED = "Unconfigured"
897 _ST_WFCONNECTION = "WFConnection"
898 _ST_CONNECTED = "Connected"
900 _STATUS_FILE = "/proc/drbd"
901 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
904 def _GetProcData(filename=_STATUS_FILE):
905 """Return data from /proc/drbd.
909 data = utils.ReadFile(filename).splitlines()
910 except EnvironmentError, err:
911 if err.errno == errno.ENOENT:
912 _ThrowError("The file %s cannot be opened, check if the module"
913 " is loaded (%s)", filename, str(err))
915 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
917 _ThrowError("Can't read any data from %s", filename)
921 def _MassageProcData(cls, data):
922 """Transform the output of _GetProdData into a nicer form.
924 @return: a dictionary of minor: joined lines from /proc/drbd
929 old_minor = old_line = None
931 if not line: # completely empty lines, as can be returned by drbd8.0+
933 lresult = cls._VALID_LINE_RE.match(line)
934 if lresult is not None:
935 if old_minor is not None:
936 results[old_minor] = old_line
937 old_minor = int(lresult.group(1))
940 if old_minor is not None:
941 old_line += " " + line.strip()
943 if old_minor is not None:
944 results[old_minor] = old_line
948 def _GetVersion(cls, proc_data):
949 """Return the DRBD version.
951 This will return a dict with keys:
957 - proto2 (only on drbd > 8.2.X)
960 first_line = proc_data[0].strip()
961 version = cls._VERSION_RE.match(first_line)
963 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
966 values = version.groups()
967 retval = {'k_major': int(values[0]),
968 'k_minor': int(values[1]),
969 'k_point': int(values[2]),
970 'api': int(values[3]),
971 'proto': int(values[4]),
973 if values[5] is not None:
974 retval['proto2'] = values[5]
979 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
980 """Returns DRBD usermode_helper currently set.
984 helper = utils.ReadFile(filename).splitlines()[0]
985 except EnvironmentError, err:
986 if err.errno == errno.ENOENT:
987 _ThrowError("The file %s cannot be opened, check if the module"
988 " is loaded (%s)", filename, str(err))
990 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
992 _ThrowError("Can't read any data from %s", filename)
997 """Return the path to a drbd device for a given minor.
1000 return "/dev/drbd%d" % minor
1003 def GetUsedDevs(cls):
1004 """Compute the list of used DRBD devices.
1007 data = cls._GetProcData()
1011 match = cls._VALID_LINE_RE.match(line)
1014 minor = int(match.group(1))
1015 state = match.group(2)
1016 if state == cls._ST_UNCONFIGURED:
1018 used_devs[minor] = state, line
1022 def _SetFromMinor(self, minor):
1023 """Set our parameters based on the given minor.
1025 This sets our minor variable and our dev_path.
1029 self.minor = self.dev_path = None
1030 self.attached = False
1033 self.dev_path = self._DevPath(minor)
1034 self.attached = True
1037 def _CheckMetaSize(meta_device):
1038 """Check if the given meta device looks like a valid one.
1040 This currently only check the size, which must be around
1044 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1046 _ThrowError("Failed to get device size: %s - %s",
1047 result.fail_reason, result.output)
1049 sectors = int(result.stdout)
1050 except (TypeError, ValueError):
1051 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1052 num_bytes = sectors * 512
1053 if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1054 _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1055 # the maximum *valid* size of the meta device when living on top
1056 # of LVM is hard to compute: it depends on the number of stripes
1057 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1058 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1059 # size meta device; as such, we restrict it to 1GB (a little bit
1060 # too generous, but making assumptions about PE size is hard)
1061 if num_bytes > 1024 * 1024 * 1024:
1062 _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1064 def Rename(self, new_id):
1067 This is not supported for drbd devices.
1070 raise errors.ProgrammerError("Can't rename a drbd device")
1073 class DRBD8(BaseDRBD):
1074 """DRBD v8.x block device.
1076 This implements the local host part of the DRBD device, i.e. it
1077 doesn't do anything to the supposed peer. If you need a fully
1078 connected DRBD pair, you need to use this class on both hosts.
1080 The unique_id for the drbd device is the (local_ip, local_port,
1081 remote_ip, remote_port) tuple, and it must have two children: the
1082 data device and the meta_device. The meta device is checked for
1083 valid size and is zeroed on create.
1090 _NET_RECONFIG_TIMEOUT = 60
1092 def __init__(self, unique_id, children, size):
1093 if children and children.count(None) > 0:
1095 if len(children) not in (0, 2):
1096 raise ValueError("Invalid configuration data %s" % str(children))
1097 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1098 raise ValueError("Invalid configuration data %s" % str(unique_id))
1099 (self._lhost, self._lport,
1100 self._rhost, self._rport,
1101 self._aminor, self._secret) = unique_id
1103 if not _CanReadDevice(children[1].dev_path):
1104 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1106 super(DRBD8, self).__init__(unique_id, children, size)
1107 self.major = self._DRBD_MAJOR
1108 version = self._GetVersion(self._GetProcData())
1109 if version['k_major'] != 8 :
1110 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1111 " usage: kernel is %s.%s, ganeti wants 8.x",
1112 version['k_major'], version['k_minor'])
1114 if (self._lhost is not None and self._lhost == self._rhost and
1115 self._lport == self._rport):
1116 raise ValueError("Invalid configuration data, same local/remote %s" %
1121 def _InitMeta(cls, minor, dev_path):
1122 """Initialize a meta device.
1124 This will not work if the given minor is in use.
1127 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1128 "v08", dev_path, "0", "create-md"])
1130 _ThrowError("Can't initialize meta device: %s", result.output)
1133 def _FindUnusedMinor(cls):
1134 """Find an unused DRBD device.
1136 This is specific to 8.x as the minors are allocated dynamically,
1137 so non-existing numbers up to a max minor count are actually free.
1140 data = cls._GetProcData()
1144 match = cls._UNUSED_LINE_RE.match(line)
1146 return int(match.group(1))
1147 match = cls._VALID_LINE_RE.match(line)
1149 minor = int(match.group(1))
1150 highest = max(highest, minor)
1151 if highest is None: # there are no minors in use at all
1153 if highest >= cls._MAX_MINORS:
1154 logging.error("Error: no free drbd minors!")
1155 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1159 def _GetShowParser(cls):
1160 """Return a parser for `drbd show` output.
1162 This will either create or return an already-create parser for the
1163 output of the command `drbd show`.
1166 if cls._PARSE_SHOW is not None:
1167 return cls._PARSE_SHOW
1170 lbrace = pyp.Literal("{").suppress()
1171 rbrace = pyp.Literal("}").suppress()
1172 lbracket = pyp.Literal("[").suppress()
1173 rbracket = pyp.Literal("]").suppress()
1174 semi = pyp.Literal(";").suppress()
1175 colon = pyp.Literal(":").suppress()
1176 # this also converts the value to an int
1177 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1179 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1180 defa = pyp.Literal("_is_default").suppress()
1181 dbl_quote = pyp.Literal('"').suppress()
1183 keyword = pyp.Word(pyp.alphanums + '-')
1186 value = pyp.Word(pyp.alphanums + '_-/.:')
1187 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1188 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1189 pyp.Word(pyp.nums + ".") + colon + number)
1190 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1191 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1192 pyp.Optional(rbracket) + colon + number)
1193 # meta device, extended syntax
1194 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1195 # device name, extended syntax
1196 device_value = pyp.Literal("minor").suppress() + number
1199 stmt = (~rbrace + keyword + ~lbrace +
1200 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1202 pyp.Optional(defa) + semi +
1203 pyp.Optional(pyp.restOfLine).suppress())
1206 section_name = pyp.Word(pyp.alphas + '_')
1207 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1209 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1212 cls._PARSE_SHOW = bnf
1217 def _GetShowData(cls, minor):
1218 """Return the `drbdsetup show` data for a minor.
1221 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1223 logging.error("Can't display the drbd config: %s - %s",
1224 result.fail_reason, result.output)
1226 return result.stdout
1229 def _GetDevInfo(cls, out):
1230 """Parse details about a given DRBD minor.
1232 This return, if available, the local backing device (as a path)
1233 and the local and remote (ip, port) information from a string
1234 containing the output of the `drbdsetup show` command as returned
1242 bnf = cls._GetShowParser()
1246 results = bnf.parseString(out)
1247 except pyp.ParseException, err:
1248 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1250 # and massage the results into our desired format
1251 for section in results:
1253 if sname == "_this_host":
1254 for lst in section[1:]:
1255 if lst[0] == "disk":
1256 data["local_dev"] = lst[1]
1257 elif lst[0] == "meta-disk":
1258 data["meta_dev"] = lst[1]
1259 data["meta_index"] = lst[2]
1260 elif lst[0] == "address":
1261 data["local_addr"] = tuple(lst[1:])
1262 elif sname == "_remote_host":
1263 for lst in section[1:]:
1264 if lst[0] == "address":
1265 data["remote_addr"] = tuple(lst[1:])
1268 def _MatchesLocal(self, info):
1269 """Test if our local config matches with an existing device.
1271 The parameter should be as returned from `_GetDevInfo()`. This
1272 method tests if our local backing device is the same as the one in
1273 the info parameter, in effect testing if we look like the given
1278 backend, meta = self._children
1280 backend = meta = None
1282 if backend is not None:
1283 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1285 retval = ("local_dev" not in info)
1287 if meta is not None:
1288 retval = retval and ("meta_dev" in info and
1289 info["meta_dev"] == meta.dev_path)
1290 retval = retval and ("meta_index" in info and
1291 info["meta_index"] == 0)
1293 retval = retval and ("meta_dev" not in info and
1294 "meta_index" not in info)
1297 def _MatchesNet(self, info):
1298 """Test if our network config matches with an existing device.
1300 The parameter should be as returned from `_GetDevInfo()`. This
1301 method tests if our network configuration is the same as the one
1302 in the info parameter, in effect testing if we look like the given
1306 if (((self._lhost is None and not ("local_addr" in info)) and
1307 (self._rhost is None and not ("remote_addr" in info)))):
1310 if self._lhost is None:
1313 if not ("local_addr" in info and
1314 "remote_addr" in info):
1317 retval = (info["local_addr"] == (self._lhost, self._lport))
1318 retval = (retval and
1319 info["remote_addr"] == (self._rhost, self._rport))
1323 def _AssembleLocal(cls, minor, backend, meta, size):
1324 """Configure the local part of a DRBD device.
1327 args = ["drbdsetup", cls._DevPath(minor), "disk",
1332 args.extend(["-d", "%sm" % size])
1333 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1334 version = cls._GetVersion(cls._GetProcData())
1335 # various DRBD versions support different disk barrier options;
1336 # what we aim here is to revert back to the 'drain' method of
1337 # disk flushes and to disable metadata barriers, in effect going
1338 # back to pre-8.0.7 behaviour
1339 vmaj = version['k_major']
1340 vmin = version['k_minor']
1341 vrel = version['k_point']
1343 if vmin == 0: # 8.0.x
1345 args.extend(['-i', '-m'])
1346 elif vmin == 2: # 8.2.x
1348 args.extend(['-i', '-m'])
1349 elif vmaj >= 3: # 8.3.x or newer
1350 args.extend(['-i', '-a', 'm'])
1351 result = utils.RunCmd(args)
1353 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1356 def _AssembleNet(cls, minor, net_info, protocol,
1357 dual_pri=False, hmac=None, secret=None):
1358 """Configure the network part of the device.
1361 lhost, lport, rhost, rport = net_info
1362 if None in net_info:
1363 # we don't want network connection and actually want to make
1365 cls._ShutdownNet(minor)
1368 # Workaround for a race condition. When DRBD is doing its dance to
1369 # establish a connection with its peer, it also sends the
1370 # synchronization speed over the wire. In some cases setting the
1371 # sync speed only after setting up both sides can race with DRBD
1372 # connecting, hence we set it here before telling DRBD anything
1374 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1376 if netutils.IP6Address.IsValid(lhost):
1377 if not netutils.IP6Address.IsValid(rhost):
1378 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1379 (minor, lhost, rhost))
1381 elif netutils.IP4Address.IsValid(lhost):
1382 if not netutils.IP4Address.IsValid(rhost):
1383 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1384 (minor, lhost, rhost))
1387 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1389 args = ["drbdsetup", cls._DevPath(minor), "net",
1390 "%s:%s:%s" % (family, lhost, lport),
1391 "%s:%s:%s" % (family, rhost, rport), protocol,
1392 "-A", "discard-zero-changes",
1399 args.extend(["-a", hmac, "-x", secret])
1400 result = utils.RunCmd(args)
1402 _ThrowError("drbd%d: can't setup network: %s - %s",
1403 minor, result.fail_reason, result.output)
1405 def _CheckNetworkConfig():
1406 info = cls._GetDevInfo(cls._GetShowData(minor))
1407 if not "local_addr" in info or not "remote_addr" in info:
1408 raise utils.RetryAgain()
1410 if (info["local_addr"] != (lhost, lport) or
1411 info["remote_addr"] != (rhost, rport)):
1412 raise utils.RetryAgain()
1415 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1416 except utils.RetryTimeout:
1417 _ThrowError("drbd%d: timeout while configuring network", minor)
1419 def AddChildren(self, devices):
1420 """Add a disk to the DRBD device.
1423 if self.minor is None:
1424 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1426 if len(devices) != 2:
1427 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1428 info = self._GetDevInfo(self._GetShowData(self.minor))
1429 if "local_dev" in info:
1430 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1431 backend, meta = devices
1432 if backend.dev_path is None or meta.dev_path is None:
1433 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1436 self._CheckMetaSize(meta.dev_path)
1437 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1439 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1440 self._children = devices
1442 def RemoveChildren(self, devices):
1443 """Detach the drbd device from local storage.
1446 if self.minor is None:
1447 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1449 # early return if we don't actually have backing storage
1450 info = self._GetDevInfo(self._GetShowData(self.minor))
1451 if "local_dev" not in info:
1453 if len(self._children) != 2:
1454 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1456 if self._children.count(None) == 2: # we don't actually have children :)
1457 logging.warning("drbd%d: requested detach while detached", self.minor)
1459 if len(devices) != 2:
1460 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1461 for child, dev in zip(self._children, devices):
1462 if dev != child.dev_path:
1463 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1464 " RemoveChildren", self.minor, dev, child.dev_path)
1466 self._ShutdownLocal(self.minor)
1470 def _SetMinorSyncSpeed(cls, minor, kbytes):
1471 """Set the speed of the DRBD syncer.
1473 This is the low-level implementation.
1476 @param minor: the drbd minor whose settings we change
1478 @param kbytes: the speed in kbytes/second
1480 @return: the success of the operation
1483 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1484 "-r", "%d" % kbytes, "--create-device"])
1486 logging.error("Can't change syncer rate: %s - %s",
1487 result.fail_reason, result.output)
1488 return not result.failed
1490 def SetSyncSpeed(self, kbytes):
1491 """Set the speed of the DRBD syncer.
1494 @param kbytes: the speed in kbytes/second
1496 @return: the success of the operation
1499 if self.minor is None:
1500 logging.info("Not attached during SetSyncSpeed")
1502 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1503 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1505 def PauseResumeSync(self, pause):
1506 """Pauses or resumes the sync of a DRBD device.
1508 @param pause: Wether to pause or resume
1509 @return: the success of the operation
1512 if self.minor is None:
1513 logging.info("Not attached during PauseSync")
1516 children_result = super(DRBD8, self).PauseResumeSync(pause)
1523 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1525 logging.error("Can't %s: %s - %s", cmd,
1526 result.fail_reason, result.output)
1527 return not result.failed and children_result
1529 def GetProcStatus(self):
1530 """Return device data from /proc.
1533 if self.minor is None:
1534 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1535 proc_info = self._MassageProcData(self._GetProcData())
1536 if self.minor not in proc_info:
1537 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1538 return DRBD8Status(proc_info[self.minor])
1540 def GetSyncStatus(self):
1541 """Returns the sync status of the device.
1544 If sync_percent is None, it means all is ok
1545 If estimated_time is None, it means we can't estimate
1546 the time needed, otherwise it's the time left in seconds.
1549 We set the is_degraded parameter to True on two conditions:
1550 network not connected or local disk missing.
1552 We compute the ldisk parameter based on whether we have a local
1555 @rtype: objects.BlockDevStatus
1558 if self.minor is None and not self.Attach():
1559 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1561 stats = self.GetProcStatus()
1562 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1564 if stats.is_disk_uptodate:
1565 ldisk_status = constants.LDS_OKAY
1566 elif stats.is_diskless:
1567 ldisk_status = constants.LDS_FAULTY
1569 ldisk_status = constants.LDS_UNKNOWN
1571 return objects.BlockDevStatus(dev_path=self.dev_path,
1574 sync_percent=stats.sync_percent,
1575 estimated_time=stats.est_time,
1576 is_degraded=is_degraded,
1577 ldisk_status=ldisk_status)
1579 def Open(self, force=False):
1580 """Make the local state primary.
1582 If the 'force' parameter is given, the '-o' option is passed to
1583 drbdsetup. Since this is a potentially dangerous operation, the
1584 force flag should be only given after creation, when it actually
1588 if self.minor is None and not self.Attach():
1589 logging.error("DRBD cannot attach to a device during open")
1591 cmd = ["drbdsetup", self.dev_path, "primary"]
1594 result = utils.RunCmd(cmd)
1596 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1600 """Make the local state secondary.
1602 This will, of course, fail if the device is in use.
1605 if self.minor is None and not self.Attach():
1606 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1607 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1609 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1610 self.minor, result.output)
1612 def DisconnectNet(self):
1613 """Removes network configuration.
1615 This method shutdowns the network side of the device.
1617 The method will wait up to a hardcoded timeout for the device to
1618 go into standalone after the 'disconnect' command before
1619 re-configuring it, as sometimes it takes a while for the
1620 disconnect to actually propagate and thus we might issue a 'net'
1621 command while the device is still connected. If the device will
1622 still be attached to the network and we time out, we raise an
1626 if self.minor is None:
1627 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1629 if None in (self._lhost, self._lport, self._rhost, self._rport):
1630 _ThrowError("drbd%d: DRBD disk missing network info in"
1631 " DisconnectNet()", self.minor)
1633 class _DisconnectStatus:
1634 def __init__(self, ever_disconnected):
1635 self.ever_disconnected = ever_disconnected
1637 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1639 def _WaitForDisconnect():
1640 if self.GetProcStatus().is_standalone:
1643 # retry the disconnect, it seems possible that due to a well-time
1644 # disconnect on the peer, my disconnect command might be ignored and
1646 dstatus.ever_disconnected = \
1647 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1649 raise utils.RetryAgain()
1652 start_time = time.time()
1655 # Start delay at 100 milliseconds and grow up to 2 seconds
1656 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1657 self._NET_RECONFIG_TIMEOUT)
1658 except utils.RetryTimeout:
1659 if dstatus.ever_disconnected:
1660 msg = ("drbd%d: device did not react to the"
1661 " 'disconnect' command in a timely manner")
1663 msg = "drbd%d: can't shutdown network, even after multiple retries"
1665 _ThrowError(msg, self.minor)
1667 reconfig_time = time.time() - start_time
1668 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1669 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1670 self.minor, reconfig_time)
1672 def AttachNet(self, multimaster):
1673 """Reconnects the network.
1675 This method connects the network side of the device with a
1676 specified multi-master flag. The device needs to be 'Standalone'
1677 but have valid network configuration data.
1680 - multimaster: init the network in dual-primary mode
1683 if self.minor is None:
1684 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1686 if None in (self._lhost, self._lport, self._rhost, self._rport):
1687 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1689 status = self.GetProcStatus()
1691 if not status.is_standalone:
1692 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1694 self._AssembleNet(self.minor,
1695 (self._lhost, self._lport, self._rhost, self._rport),
1696 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1697 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1700 """Check if our minor is configured.
1702 This doesn't do any device configurations - it only checks if the
1703 minor is in a state different from Unconfigured.
1705 Note that this function will not change the state of the system in
1706 any way (except in case of side-effects caused by reading from
1710 used_devs = self.GetUsedDevs()
1711 if self._aminor in used_devs:
1712 minor = self._aminor
1716 self._SetFromMinor(minor)
1717 return minor is not None
1720 """Assemble the drbd.
1723 - if we have a configured device, we try to ensure that it matches
1725 - if not, we create it from zero
1728 super(DRBD8, self).Assemble()
1731 if self.minor is None:
1732 # local device completely unconfigured
1733 self._FastAssemble()
1735 # we have to recheck the local and network status and try to fix
1737 self._SlowAssemble()
1739 def _SlowAssemble(self):
1740 """Assembles the DRBD device from a (partially) configured device.
1742 In case of partially attached (local device matches but no network
1743 setup), we perform the network attach. If successful, we re-test
1744 the attach if can return success.
1747 # TODO: Rewrite to not use a for loop just because there is 'break'
1748 # pylint: disable-msg=W0631
1749 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1750 for minor in (self._aminor,):
1751 info = self._GetDevInfo(self._GetShowData(minor))
1752 match_l = self._MatchesLocal(info)
1753 match_r = self._MatchesNet(info)
1755 if match_l and match_r:
1756 # everything matches
1759 if match_l and not match_r and "local_addr" not in info:
1760 # disk matches, but not attached to network, attach and recheck
1761 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1762 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1763 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1766 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1767 " show' disagrees", minor)
1769 if match_r and "local_dev" not in info:
1770 # no local disk, but network attached and it matches
1771 self._AssembleLocal(minor, self._children[0].dev_path,
1772 self._children[1].dev_path, self.size)
1773 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1776 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1777 " show' disagrees", minor)
1779 # this case must be considered only if we actually have local
1780 # storage, i.e. not in diskless mode, because all diskless
1781 # devices are equal from the point of view of local
1783 if (match_l and "local_dev" in info and
1784 not match_r and "local_addr" in info):
1785 # strange case - the device network part points to somewhere
1786 # else, even though its local storage is ours; as we own the
1787 # drbd space, we try to disconnect from the remote peer and
1788 # reconnect to our correct one
1790 self._ShutdownNet(minor)
1791 except errors.BlockDeviceError, err:
1792 _ThrowError("drbd%d: device has correct local storage, wrong"
1793 " remote peer and is unable to disconnect in order"
1794 " to attach to the correct peer: %s", minor, str(err))
1795 # note: _AssembleNet also handles the case when we don't want
1796 # local storage (i.e. one or more of the _[lr](host|port) is
1798 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1799 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1800 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1803 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1804 " show' disagrees", minor)
1809 self._SetFromMinor(minor)
1811 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1814 def _FastAssemble(self):
1815 """Assemble the drbd device from zero.
1817 This is run when in Assemble we detect our minor is unused.
1820 minor = self._aminor
1821 if self._children and self._children[0] and self._children[1]:
1822 self._AssembleLocal(minor, self._children[0].dev_path,
1823 self._children[1].dev_path, self.size)
1824 if self._lhost and self._lport and self._rhost and self._rport:
1825 self._AssembleNet(minor,
1826 (self._lhost, self._lport, self._rhost, self._rport),
1827 constants.DRBD_NET_PROTOCOL,
1828 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1829 self._SetFromMinor(minor)
1832 def _ShutdownLocal(cls, minor):
1833 """Detach from the local device.
1835 I/Os will continue to be served from the remote device. If we
1836 don't have a remote device, this operation will fail.
1839 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1841 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1844 def _ShutdownNet(cls, minor):
1845 """Disconnect from the remote peer.
1847 This fails if we don't have a local device.
1850 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1852 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1855 def _ShutdownAll(cls, minor):
1856 """Deactivate the device.
1858 This will, of course, fail if the device is in use.
1861 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1863 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1864 minor, result.output)
1867 """Shutdown the DRBD device.
1870 if self.minor is None and not self.Attach():
1871 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1875 self.dev_path = None
1876 self._ShutdownAll(minor)
1879 """Stub remove for DRBD devices.
1885 def Create(cls, unique_id, children, size):
1886 """Create a new DRBD8 device.
1888 Since DRBD devices are not created per se, just assembled, this
1889 function only initializes the metadata.
1892 if len(children) != 2:
1893 raise errors.ProgrammerError("Invalid setup for the drbd device")
1894 # check that the minor is unused
1895 aminor = unique_id[4]
1896 proc_info = cls._MassageProcData(cls._GetProcData())
1897 if aminor in proc_info:
1898 status = DRBD8Status(proc_info[aminor])
1899 in_use = status.is_in_use
1903 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1906 if not meta.Attach():
1907 _ThrowError("drbd%d: can't attach to meta device '%s'",
1909 cls._CheckMetaSize(meta.dev_path)
1910 cls._InitMeta(aminor, meta.dev_path)
1911 return cls(unique_id, children, size)
1913 def Grow(self, amount):
1914 """Resize the DRBD device and its backing storage.
1917 if self.minor is None:
1918 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1919 if len(self._children) != 2 or None in self._children:
1920 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1921 self._children[0].Grow(amount)
1922 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1923 "%dm" % (self.size + amount)])
1925 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1928 class FileStorage(BlockDev):
1931 This class represents the a file storage backend device.
1933 The unique_id for the file device is a (file_driver, file_path) tuple.
1936 def __init__(self, unique_id, children, size):
1937 """Initalizes a file device backend.
1941 raise errors.BlockDeviceError("Invalid setup for file device")
1942 super(FileStorage, self).__init__(unique_id, children, size)
1943 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1944 raise ValueError("Invalid configuration data %s" % str(unique_id))
1945 self.driver = unique_id[0]
1946 self.dev_path = unique_id[1]
1950 """Assemble the device.
1952 Checks whether the file device exists, raises BlockDeviceError otherwise.
1955 if not os.path.exists(self.dev_path):
1956 _ThrowError("File device '%s' does not exist" % self.dev_path)
1959 """Shutdown the device.
1961 This is a no-op for the file type, as we don't deactivate
1962 the file on shutdown.
1967 def Open(self, force=False):
1968 """Make the device ready for I/O.
1970 This is a no-op for the file type.
1976 """Notifies that the device will no longer be used for I/O.
1978 This is a no-op for the file type.
1984 """Remove the file backing the block device.
1987 @return: True if the removal was successful
1991 os.remove(self.dev_path)
1992 except OSError, err:
1993 if err.errno != errno.ENOENT:
1994 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1996 def Rename(self, new_id):
1997 """Renames the file.
2000 # TODO: implement rename for file-based storage
2001 _ThrowError("Rename is not supported for file-based storage")
2003 def Grow(self, amount):
2006 @param amount: the amount (in mebibytes) to grow with
2009 # Check that the file exists
2011 current_size = self.GetActualSize()
2012 new_size = current_size + amount * 1024 * 1024
2013 assert new_size > current_size, "Cannot Grow with a negative amount"
2015 f = open(self.dev_path, "a+")
2016 f.truncate(new_size)
2018 except EnvironmentError, err:
2019 _ThrowError("Error in file growth: %", str(err))
2022 """Attach to an existing file.
2024 Check if this file already exists.
2027 @return: True if file exists
2030 self.attached = os.path.exists(self.dev_path)
2031 return self.attached
2033 def GetActualSize(self):
2034 """Return the actual disk size.
2036 @note: the device needs to be active when this is called
2039 assert self.attached, "BlockDevice not attached in GetActualSize()"
2041 st = os.stat(self.dev_path)
2043 except OSError, err:
2044 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2047 def Create(cls, unique_id, children, size):
2048 """Create a new file.
2050 @param size: the size of file in MiB
2052 @rtype: L{bdev.FileStorage}
2053 @return: an instance of FileStorage
2056 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2057 raise ValueError("Invalid configuration data %s" % str(unique_id))
2058 dev_path = unique_id[1]
2060 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2061 f = os.fdopen(fd, "w")
2062 f.truncate(size * 1024 * 1024)
2064 except EnvironmentError, err:
2065 if err.errno == errno.EEXIST:
2066 _ThrowError("File already existing: %s", dev_path)
2067 _ThrowError("Error in file creation: %", str(err))
2069 return FileStorage(unique_id, children, size)
2073 constants.LD_LV: LogicalVolume,
2074 constants.LD_DRBD8: DRBD8,
2077 if constants.ENABLE_FILE_STORAGE:
2078 DEV_MAP[constants.LD_FILE] = FileStorage
2081 def FindDevice(dev_type, unique_id, children, size):
2082 """Search for an existing, assembled device.
2084 This will succeed only if the device exists and is assembled, but it
2085 does not do any actions in order to activate the device.
2088 if dev_type not in DEV_MAP:
2089 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2090 device = DEV_MAP[dev_type](unique_id, children, size)
2091 if not device.attached:
2096 def Assemble(dev_type, unique_id, children, size):
2097 """Try to attach or assemble an existing device.
2099 This will attach to assemble the device, as needed, to bring it
2100 fully up. It must be safe to run on already-assembled devices.
2103 if dev_type not in DEV_MAP:
2104 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2105 device = DEV_MAP[dev_type](unique_id, children, size)
2110 def Create(dev_type, unique_id, children, size):
2114 if dev_type not in DEV_MAP:
2115 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2116 device = DEV_MAP[dev_type].Create(unique_id, children, size)