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 GetSyncStatus(self):
234 """Returns the sync status of the device.
236 If this device is a mirroring device, this function returns the
237 status of the mirror.
239 If sync_percent is None, it means the device is not syncing.
241 If estimated_time is None, it means we can't estimate
242 the time needed, otherwise it's the time left in seconds.
244 If is_degraded is True, it means the device is missing
245 redundancy. This is usually a sign that something went wrong in
246 the device setup, if sync_percent is None.
248 The ldisk parameter represents the degradation of the local
249 data. This is only valid for some devices, the rest will always
250 return False (not degraded).
252 @rtype: objects.BlockDevStatus
255 return objects.BlockDevStatus(dev_path=self.dev_path,
261 ldisk_status=constants.LDS_OKAY)
263 def CombinedSyncStatus(self):
264 """Calculate the mirror status recursively for our children.
266 The return value is the same as for `GetSyncStatus()` except the
267 minimum percent and maximum time are calculated across our
270 @rtype: objects.BlockDevStatus
273 status = self.GetSyncStatus()
275 min_percent = status.sync_percent
276 max_time = status.estimated_time
277 is_degraded = status.is_degraded
278 ldisk_status = status.ldisk_status
281 for child in self._children:
282 child_status = child.GetSyncStatus()
284 if min_percent is None:
285 min_percent = child_status.sync_percent
286 elif child_status.sync_percent is not None:
287 min_percent = min(min_percent, child_status.sync_percent)
290 max_time = child_status.estimated_time
291 elif child_status.estimated_time is not None:
292 max_time = max(max_time, child_status.estimated_time)
294 is_degraded = is_degraded or child_status.is_degraded
296 if ldisk_status is None:
297 ldisk_status = child_status.ldisk_status
298 elif child_status.ldisk_status is not None:
299 ldisk_status = max(ldisk_status, child_status.ldisk_status)
301 return objects.BlockDevStatus(dev_path=self.dev_path,
304 sync_percent=min_percent,
305 estimated_time=max_time,
306 is_degraded=is_degraded,
307 ldisk_status=ldisk_status)
310 def SetInfo(self, text):
311 """Update metadata with info text.
313 Only supported for some device types.
316 for child in self._children:
319 def Grow(self, amount):
320 """Grow the block device.
322 @param amount: the amount (in mebibytes) to grow with
325 raise NotImplementedError
327 def GetActualSize(self):
328 """Return the actual disk size.
330 @note: the device needs to be active when this is called
333 assert self.attached, "BlockDevice not attached in GetActualSize()"
334 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
336 _ThrowError("blockdev failed (%s): %s",
337 result.fail_reason, result.output)
339 sz = int(result.output.strip())
340 except (ValueError, TypeError), err:
341 _ThrowError("Failed to parse blockdev output: %s", str(err))
345 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
346 (self.__class__, self.unique_id, self._children,
347 self.major, self.minor, self.dev_path))
350 class LogicalVolume(BlockDev):
351 """Logical Volume block device.
354 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
355 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
356 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
358 def __init__(self, unique_id, children, size):
359 """Attaches to a LV device.
361 The unique_id is a tuple (vg_name, lv_name)
364 super(LogicalVolume, self).__init__(unique_id, children, size)
365 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
366 raise ValueError("Invalid configuration data %s" % str(unique_id))
367 self._vg_name, self._lv_name = unique_id
368 self._ValidateName(self._vg_name)
369 self._ValidateName(self._lv_name)
370 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
371 self._degraded = True
372 self.major = self.minor = self.pe_size = self.stripe_count = None
376 def Create(cls, unique_id, children, size):
377 """Create a new logical volume.
380 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
381 raise errors.ProgrammerError("Invalid configuration data %s" %
383 vg_name, lv_name = unique_id
384 cls._ValidateName(vg_name)
385 cls._ValidateName(lv_name)
386 pvs_info = cls.GetPVInfo([vg_name])
388 _ThrowError("Can't compute PV info for vg %s", vg_name)
392 pvlist = [ pv[1] for pv in pvs_info ]
393 if compat.any(":" in v for v in pvlist):
394 _ThrowError("Some of your PVs have the invalid character ':' in their"
395 " name, this is not supported - please filter them out"
396 " in lvm.conf using either 'filter' or 'preferred_names'")
397 free_size = sum([ pv[0] for pv in pvs_info ])
398 current_pvs = len(pvlist)
399 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
401 # The size constraint should have been checked from the master before
402 # calling the create function.
404 _ThrowError("Not enough free space: required %s,"
405 " available %s", size, free_size)
406 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
407 # If the free space is not well distributed, we won't be able to
408 # create an optimally-striped volume; in that case, we want to try
409 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
411 for stripes_arg in range(stripes, 0, -1):
412 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
413 if not result.failed:
416 _ThrowError("LV create failed (%s): %s",
417 result.fail_reason, result.output)
418 return LogicalVolume(unique_id, children, size)
421 def _GetVolumeInfo(lvm_cmd, fields):
422 """Returns LVM Volumen infos using lvm_cmd
424 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
425 @param fields: Fields to return
426 @return: A list of dicts each with the parsed fields
430 raise errors.ProgrammerError("No fields specified")
433 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
434 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
436 result = utils.RunCmd(cmd)
438 raise errors.CommandError("Can't get the volume information: %s - %s" %
439 (result.fail_reason, result.output))
442 for line in result.stdout.splitlines():
443 splitted_fields = line.strip().split(sep)
445 if len(fields) != len(splitted_fields):
446 raise errors.CommandError("Can't parse %s output: line '%s'" %
449 data.append(splitted_fields)
454 def GetPVInfo(cls, vg_names, filter_allocatable=True):
455 """Get the free space info for PVs in a volume group.
457 @param vg_names: list of volume group names, if empty all will be returned
458 @param filter_allocatable: whether to skip over unallocatable PVs
461 @return: list of tuples (free_space, name) with free_space in mebibytes
465 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
467 except errors.GenericError, err:
468 logging.error("Can't get PV information: %s", err)
472 for pv_name, vg_name, pv_free, pv_attr in info:
473 # (possibly) skip over pvs which are not allocatable
474 if filter_allocatable and pv_attr[0] != "a":
476 # (possibly) skip over pvs which are not in the right volume group(s)
477 if vg_names and vg_name not in vg_names:
479 data.append((float(pv_free), pv_name, vg_name))
484 def GetVGInfo(cls, vg_names, filter_readonly=True):
485 """Get the free space info for specific VGs.
487 @param vg_names: list of volume group names, if empty all will be returned
488 @param filter_readonly: whether to skip over readonly VGs
491 @return: list of tuples (free_space, total_size, name) with free_space in
496 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
498 except errors.GenericError, err:
499 logging.error("Can't get VG information: %s", err)
503 for vg_name, vg_free, vg_attr, vg_size in info:
504 # (possibly) skip over vgs which are not writable
505 if filter_readonly and vg_attr[0] == "r":
507 # (possibly) skip over vgs which are not in the right volume group(s)
508 if vg_names and vg_name not in vg_names:
510 data.append((float(vg_free), float(vg_size), vg_name))
515 def _ValidateName(cls, name):
516 """Validates that a given name is valid as VG or LV name.
518 The list of valid characters and restricted names is taken out of
519 the lvm(8) manpage, with the simplification that we enforce both
520 VG and LV restrictions on the names.
523 if (not cls._VALID_NAME_RE.match(name) or
524 name in cls._INVALID_NAMES or
525 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
526 _ThrowError("Invalid LVM name '%s'", name)
529 """Remove this logical volume.
532 if not self.minor and not self.Attach():
533 # the LV does not exist
535 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
536 (self._vg_name, self._lv_name)])
538 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
540 def Rename(self, new_id):
541 """Rename this logical volume.
544 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
545 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
546 new_vg, new_name = new_id
547 if new_vg != self._vg_name:
548 raise errors.ProgrammerError("Can't move a logical volume across"
549 " volume groups (from %s to to %s)" %
550 (self._vg_name, new_vg))
551 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
553 _ThrowError("Failed to rename the logical volume: %s", result.output)
554 self._lv_name = new_name
555 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
558 """Attach to an existing LV.
560 This method will try to see if an existing and active LV exists
561 which matches our name. If so, its major/minor will be
565 self.attached = False
566 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
567 "--units=m", "--nosuffix",
568 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
569 "vg_extent_size,stripes", self.dev_path])
571 logging.error("Can't find LV %s: %s, %s",
572 self.dev_path, result.fail_reason, result.output)
574 # the output can (and will) have multiple lines for multi-segment
575 # LVs, as the 'stripes' parameter is a segment one, so we take
576 # only the last entry, which is the one we're interested in; note
577 # that with LVM2 anyway the 'stripes' value must be constant
578 # across segments, so this is a no-op actually
579 out = result.stdout.splitlines()
580 if not out: # totally empty result? splitlines() returns at least
581 # one line for any non-empty string
582 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
584 out = out[-1].strip().rstrip(',')
587 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
590 status, major, minor, pe_size, stripes = out
592 logging.error("lvs lv_attr is not 6 characters (%s)", status)
598 except (TypeError, ValueError), err:
599 logging.error("lvs major/minor cannot be parsed: %s", str(err))
602 pe_size = int(float(pe_size))
603 except (TypeError, ValueError), err:
604 logging.error("Can't parse vg extent size: %s", err)
608 stripes = int(stripes)
609 except (TypeError, ValueError), err:
610 logging.error("Can't parse the number of stripes: %s", err)
615 self.pe_size = pe_size
616 self.stripe_count = stripes
617 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
623 """Assemble the device.
625 We always run `lvchange -ay` on the LV to ensure it's active before
626 use, as there were cases when xenvg was not active after boot
627 (also possibly after disk issues).
630 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
632 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
635 """Shutdown the device.
637 This is a no-op for the LV device type, as we don't deactivate the
643 def GetSyncStatus(self):
644 """Returns the sync status of the device.
646 If this device is a mirroring device, this function returns the
647 status of the mirror.
649 For logical volumes, sync_percent and estimated_time are always
650 None (no recovery in progress, as we don't handle the mirrored LV
651 case). The is_degraded parameter is the inverse of the ldisk
654 For the ldisk parameter, we check if the logical volume has the
655 'virtual' type, which means it's not backed by existing storage
656 anymore (read from it return I/O error). This happens after a
657 physical disk failure and subsequent 'vgreduce --removemissing' on
660 The status was already read in Attach, so we just return it.
662 @rtype: objects.BlockDevStatus
666 ldisk_status = constants.LDS_FAULTY
668 ldisk_status = constants.LDS_OKAY
670 return objects.BlockDevStatus(dev_path=self.dev_path,
675 is_degraded=self._degraded,
676 ldisk_status=ldisk_status)
678 def Open(self, force=False):
679 """Make the device ready for I/O.
681 This is a no-op for the LV device type.
687 """Notifies that the device will no longer be used for I/O.
689 This is a no-op for the LV device type.
694 def Snapshot(self, size):
695 """Create a snapshot copy of an lvm block device.
698 snap_name = self._lv_name + ".snap"
700 # remove existing snapshot if found
701 snap = LogicalVolume((self._vg_name, snap_name), None, size)
702 _IgnoreError(snap.Remove)
704 vg_info = self.GetVGInfo([self._vg_name])
706 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
707 free_size, _, _ = vg_info[0]
709 _ThrowError("Not enough free space: required %s,"
710 " available %s", size, free_size)
712 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
713 "-n%s" % snap_name, self.dev_path])
715 _ThrowError("command: %s error: %s - %s",
716 result.cmd, result.fail_reason, result.output)
720 def SetInfo(self, text):
721 """Update metadata with info text.
724 BlockDev.SetInfo(self, text)
726 # Replace invalid characters
727 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
728 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
730 # Only up to 128 characters are allowed
733 result = utils.RunCmd(["lvchange", "--addtag", text,
736 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
739 def Grow(self, amount):
740 """Grow the logical volume.
743 if self.pe_size is None or self.stripe_count is None:
744 if not self.Attach():
745 _ThrowError("Can't attach to LV during Grow()")
746 full_stripe_size = self.pe_size * self.stripe_count
747 rest = amount % full_stripe_size
749 amount += full_stripe_size - rest
750 # we try multiple algorithms since the 'best' ones might not have
751 # space available in the right place, but later ones might (since
752 # they have less constraints); also note that only recent LVM
754 for alloc_policy in "contiguous", "cling", "normal":
755 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
756 "-L", "+%dm" % amount, self.dev_path])
757 if not result.failed:
759 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
762 class DRBD8Status(object):
763 """A DRBD status representation class.
765 Note that this doesn't support unconfigured devices (cs:Unconfigured).
768 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
769 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
770 "\s+ds:([^/]+)/(\S+)\s+.*$")
771 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
772 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
774 CS_UNCONFIGURED = "Unconfigured"
775 CS_STANDALONE = "StandAlone"
776 CS_WFCONNECTION = "WFConnection"
777 CS_WFREPORTPARAMS = "WFReportParams"
778 CS_CONNECTED = "Connected"
779 CS_STARTINGSYNCS = "StartingSyncS"
780 CS_STARTINGSYNCT = "StartingSyncT"
781 CS_WFBITMAPS = "WFBitMapS"
782 CS_WFBITMAPT = "WFBitMapT"
783 CS_WFSYNCUUID = "WFSyncUUID"
784 CS_SYNCSOURCE = "SyncSource"
785 CS_SYNCTARGET = "SyncTarget"
786 CS_PAUSEDSYNCS = "PausedSyncS"
787 CS_PAUSEDSYNCT = "PausedSyncT"
788 CSET_SYNC = frozenset([
801 DS_DISKLESS = "Diskless"
802 DS_ATTACHING = "Attaching" # transient state
803 DS_FAILED = "Failed" # transient state, next: diskless
804 DS_NEGOTIATING = "Negotiating" # transient state
805 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
806 DS_OUTDATED = "Outdated"
807 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
808 DS_CONSISTENT = "Consistent"
809 DS_UPTODATE = "UpToDate" # normal state
811 RO_PRIMARY = "Primary"
812 RO_SECONDARY = "Secondary"
813 RO_UNKNOWN = "Unknown"
815 def __init__(self, procline):
816 u = self.UNCONF_RE.match(procline)
818 self.cstatus = self.CS_UNCONFIGURED
819 self.lrole = self.rrole = self.ldisk = self.rdisk = None
821 m = self.LINE_RE.match(procline)
823 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
824 self.cstatus = m.group(1)
825 self.lrole = m.group(2)
826 self.rrole = m.group(3)
827 self.ldisk = m.group(4)
828 self.rdisk = m.group(5)
830 # end reading of data from the LINE_RE or UNCONF_RE
832 self.is_standalone = self.cstatus == self.CS_STANDALONE
833 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
834 self.is_connected = self.cstatus == self.CS_CONNECTED
835 self.is_primary = self.lrole == self.RO_PRIMARY
836 self.is_secondary = self.lrole == self.RO_SECONDARY
837 self.peer_primary = self.rrole == self.RO_PRIMARY
838 self.peer_secondary = self.rrole == self.RO_SECONDARY
839 self.both_primary = self.is_primary and self.peer_primary
840 self.both_secondary = self.is_secondary and self.peer_secondary
842 self.is_diskless = self.ldisk == self.DS_DISKLESS
843 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
845 self.is_in_resync = self.cstatus in self.CSET_SYNC
846 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
848 m = self.SYNC_RE.match(procline)
850 self.sync_percent = float(m.group(1))
851 hours = int(m.group(2))
852 minutes = int(m.group(3))
853 seconds = int(m.group(4))
854 self.est_time = hours * 3600 + minutes * 60 + seconds
856 # we have (in this if branch) no percent information, but if
857 # we're resyncing we need to 'fake' a sync percent information,
858 # as this is how cmdlib determines if it makes sense to wait for
860 if self.is_in_resync:
861 self.sync_percent = 0
863 self.sync_percent = None
867 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
870 This class contains a few bits of common functionality between the
871 0.7 and 8.x versions of DRBD.
874 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
875 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
876 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
877 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
880 _ST_UNCONFIGURED = "Unconfigured"
881 _ST_WFCONNECTION = "WFConnection"
882 _ST_CONNECTED = "Connected"
884 _STATUS_FILE = "/proc/drbd"
885 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
888 def _GetProcData(filename=_STATUS_FILE):
889 """Return data from /proc/drbd.
893 data = utils.ReadFile(filename).splitlines()
894 except EnvironmentError, err:
895 if err.errno == errno.ENOENT:
896 _ThrowError("The file %s cannot be opened, check if the module"
897 " is loaded (%s)", filename, str(err))
899 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
901 _ThrowError("Can't read any data from %s", filename)
905 def _MassageProcData(cls, data):
906 """Transform the output of _GetProdData into a nicer form.
908 @return: a dictionary of minor: joined lines from /proc/drbd
913 old_minor = old_line = None
915 if not line: # completely empty lines, as can be returned by drbd8.0+
917 lresult = cls._VALID_LINE_RE.match(line)
918 if lresult is not None:
919 if old_minor is not None:
920 results[old_minor] = old_line
921 old_minor = int(lresult.group(1))
924 if old_minor is not None:
925 old_line += " " + line.strip()
927 if old_minor is not None:
928 results[old_minor] = old_line
932 def _GetVersion(cls, proc_data):
933 """Return the DRBD version.
935 This will return a dict with keys:
941 - proto2 (only on drbd > 8.2.X)
944 first_line = proc_data[0].strip()
945 version = cls._VERSION_RE.match(first_line)
947 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
950 values = version.groups()
951 retval = {'k_major': int(values[0]),
952 'k_minor': int(values[1]),
953 'k_point': int(values[2]),
954 'api': int(values[3]),
955 'proto': int(values[4]),
957 if values[5] is not None:
958 retval['proto2'] = values[5]
963 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
964 """Returns DRBD usermode_helper currently set.
968 helper = utils.ReadFile(filename).splitlines()[0]
969 except EnvironmentError, err:
970 if err.errno == errno.ENOENT:
971 _ThrowError("The file %s cannot be opened, check if the module"
972 " is loaded (%s)", filename, str(err))
974 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
976 _ThrowError("Can't read any data from %s", filename)
981 """Return the path to a drbd device for a given minor.
984 return "/dev/drbd%d" % minor
987 def GetUsedDevs(cls):
988 """Compute the list of used DRBD devices.
991 data = cls._GetProcData()
995 match = cls._VALID_LINE_RE.match(line)
998 minor = int(match.group(1))
999 state = match.group(2)
1000 if state == cls._ST_UNCONFIGURED:
1002 used_devs[minor] = state, line
1006 def _SetFromMinor(self, minor):
1007 """Set our parameters based on the given minor.
1009 This sets our minor variable and our dev_path.
1013 self.minor = self.dev_path = None
1014 self.attached = False
1017 self.dev_path = self._DevPath(minor)
1018 self.attached = True
1021 def _CheckMetaSize(meta_device):
1022 """Check if the given meta device looks like a valid one.
1024 This currently only check the size, which must be around
1028 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1030 _ThrowError("Failed to get device size: %s - %s",
1031 result.fail_reason, result.output)
1033 sectors = int(result.stdout)
1034 except (TypeError, ValueError):
1035 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1036 num_bytes = sectors * 512
1037 if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1038 _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1039 # the maximum *valid* size of the meta device when living on top
1040 # of LVM is hard to compute: it depends on the number of stripes
1041 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1042 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1043 # size meta device; as such, we restrict it to 1GB (a little bit
1044 # too generous, but making assumptions about PE size is hard)
1045 if num_bytes > 1024 * 1024 * 1024:
1046 _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1048 def Rename(self, new_id):
1051 This is not supported for drbd devices.
1054 raise errors.ProgrammerError("Can't rename a drbd device")
1057 class DRBD8(BaseDRBD):
1058 """DRBD v8.x block device.
1060 This implements the local host part of the DRBD device, i.e. it
1061 doesn't do anything to the supposed peer. If you need a fully
1062 connected DRBD pair, you need to use this class on both hosts.
1064 The unique_id for the drbd device is the (local_ip, local_port,
1065 remote_ip, remote_port) tuple, and it must have two children: the
1066 data device and the meta_device. The meta device is checked for
1067 valid size and is zeroed on create.
1074 _NET_RECONFIG_TIMEOUT = 60
1076 def __init__(self, unique_id, children, size):
1077 if children and children.count(None) > 0:
1079 if len(children) not in (0, 2):
1080 raise ValueError("Invalid configuration data %s" % str(children))
1081 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1082 raise ValueError("Invalid configuration data %s" % str(unique_id))
1083 (self._lhost, self._lport,
1084 self._rhost, self._rport,
1085 self._aminor, self._secret) = unique_id
1087 if not _CanReadDevice(children[1].dev_path):
1088 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1090 super(DRBD8, self).__init__(unique_id, children, size)
1091 self.major = self._DRBD_MAJOR
1092 version = self._GetVersion(self._GetProcData())
1093 if version['k_major'] != 8 :
1094 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1095 " usage: kernel is %s.%s, ganeti wants 8.x",
1096 version['k_major'], version['k_minor'])
1098 if (self._lhost is not None and self._lhost == self._rhost and
1099 self._lport == self._rport):
1100 raise ValueError("Invalid configuration data, same local/remote %s" %
1105 def _InitMeta(cls, minor, dev_path):
1106 """Initialize a meta device.
1108 This will not work if the given minor is in use.
1111 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1112 "v08", dev_path, "0", "create-md"])
1114 _ThrowError("Can't initialize meta device: %s", result.output)
1117 def _FindUnusedMinor(cls):
1118 """Find an unused DRBD device.
1120 This is specific to 8.x as the minors are allocated dynamically,
1121 so non-existing numbers up to a max minor count are actually free.
1124 data = cls._GetProcData()
1128 match = cls._UNUSED_LINE_RE.match(line)
1130 return int(match.group(1))
1131 match = cls._VALID_LINE_RE.match(line)
1133 minor = int(match.group(1))
1134 highest = max(highest, minor)
1135 if highest is None: # there are no minors in use at all
1137 if highest >= cls._MAX_MINORS:
1138 logging.error("Error: no free drbd minors!")
1139 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1143 def _GetShowParser(cls):
1144 """Return a parser for `drbd show` output.
1146 This will either create or return an already-create parser for the
1147 output of the command `drbd show`.
1150 if cls._PARSE_SHOW is not None:
1151 return cls._PARSE_SHOW
1154 lbrace = pyp.Literal("{").suppress()
1155 rbrace = pyp.Literal("}").suppress()
1156 lbracket = pyp.Literal("[").suppress()
1157 rbracket = pyp.Literal("]").suppress()
1158 semi = pyp.Literal(";").suppress()
1159 colon = pyp.Literal(":").suppress()
1160 # this also converts the value to an int
1161 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1163 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1164 defa = pyp.Literal("_is_default").suppress()
1165 dbl_quote = pyp.Literal('"').suppress()
1167 keyword = pyp.Word(pyp.alphanums + '-')
1170 value = pyp.Word(pyp.alphanums + '_-/.:')
1171 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1172 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1173 pyp.Word(pyp.nums + ".") + colon + number)
1174 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1175 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1176 pyp.Optional(rbracket) + colon + number)
1177 # meta device, extended syntax
1178 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1179 # device name, extended syntax
1180 device_value = pyp.Literal("minor").suppress() + number
1183 stmt = (~rbrace + keyword + ~lbrace +
1184 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1186 pyp.Optional(defa) + semi +
1187 pyp.Optional(pyp.restOfLine).suppress())
1190 section_name = pyp.Word(pyp.alphas + '_')
1191 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1193 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1196 cls._PARSE_SHOW = bnf
1201 def _GetShowData(cls, minor):
1202 """Return the `drbdsetup show` data for a minor.
1205 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1207 logging.error("Can't display the drbd config: %s - %s",
1208 result.fail_reason, result.output)
1210 return result.stdout
1213 def _GetDevInfo(cls, out):
1214 """Parse details about a given DRBD minor.
1216 This return, if available, the local backing device (as a path)
1217 and the local and remote (ip, port) information from a string
1218 containing the output of the `drbdsetup show` command as returned
1226 bnf = cls._GetShowParser()
1230 results = bnf.parseString(out)
1231 except pyp.ParseException, err:
1232 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1234 # and massage the results into our desired format
1235 for section in results:
1237 if sname == "_this_host":
1238 for lst in section[1:]:
1239 if lst[0] == "disk":
1240 data["local_dev"] = lst[1]
1241 elif lst[0] == "meta-disk":
1242 data["meta_dev"] = lst[1]
1243 data["meta_index"] = lst[2]
1244 elif lst[0] == "address":
1245 data["local_addr"] = tuple(lst[1:])
1246 elif sname == "_remote_host":
1247 for lst in section[1:]:
1248 if lst[0] == "address":
1249 data["remote_addr"] = tuple(lst[1:])
1252 def _MatchesLocal(self, info):
1253 """Test if our local config matches with an existing device.
1255 The parameter should be as returned from `_GetDevInfo()`. This
1256 method tests if our local backing device is the same as the one in
1257 the info parameter, in effect testing if we look like the given
1262 backend, meta = self._children
1264 backend = meta = None
1266 if backend is not None:
1267 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1269 retval = ("local_dev" not in info)
1271 if meta is not None:
1272 retval = retval and ("meta_dev" in info and
1273 info["meta_dev"] == meta.dev_path)
1274 retval = retval and ("meta_index" in info and
1275 info["meta_index"] == 0)
1277 retval = retval and ("meta_dev" not in info and
1278 "meta_index" not in info)
1281 def _MatchesNet(self, info):
1282 """Test if our network config matches with an existing device.
1284 The parameter should be as returned from `_GetDevInfo()`. This
1285 method tests if our network configuration is the same as the one
1286 in the info parameter, in effect testing if we look like the given
1290 if (((self._lhost is None and not ("local_addr" in info)) and
1291 (self._rhost is None and not ("remote_addr" in info)))):
1294 if self._lhost is None:
1297 if not ("local_addr" in info and
1298 "remote_addr" in info):
1301 retval = (info["local_addr"] == (self._lhost, self._lport))
1302 retval = (retval and
1303 info["remote_addr"] == (self._rhost, self._rport))
1307 def _AssembleLocal(cls, minor, backend, meta, size):
1308 """Configure the local part of a DRBD device.
1311 args = ["drbdsetup", cls._DevPath(minor), "disk",
1316 args.extend(["-d", "%sm" % size])
1317 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1318 version = cls._GetVersion(cls._GetProcData())
1319 # various DRBD versions support different disk barrier options;
1320 # what we aim here is to revert back to the 'drain' method of
1321 # disk flushes and to disable metadata barriers, in effect going
1322 # back to pre-8.0.7 behaviour
1323 vmaj = version['k_major']
1324 vmin = version['k_minor']
1325 vrel = version['k_point']
1327 if vmin == 0: # 8.0.x
1329 args.extend(['-i', '-m'])
1330 elif vmin == 2: # 8.2.x
1332 args.extend(['-i', '-m'])
1333 elif vmaj >= 3: # 8.3.x or newer
1334 args.extend(['-i', '-a', 'm'])
1335 result = utils.RunCmd(args)
1337 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1340 def _AssembleNet(cls, minor, net_info, protocol,
1341 dual_pri=False, hmac=None, secret=None):
1342 """Configure the network part of the device.
1345 lhost, lport, rhost, rport = net_info
1346 if None in net_info:
1347 # we don't want network connection and actually want to make
1349 cls._ShutdownNet(minor)
1352 # Workaround for a race condition. When DRBD is doing its dance to
1353 # establish a connection with its peer, it also sends the
1354 # synchronization speed over the wire. In some cases setting the
1355 # sync speed only after setting up both sides can race with DRBD
1356 # connecting, hence we set it here before telling DRBD anything
1358 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1360 if netutils.IP6Address.IsValid(lhost):
1361 if not netutils.IP6Address.IsValid(rhost):
1362 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1363 (minor, lhost, rhost))
1365 elif netutils.IP4Address.IsValid(lhost):
1366 if not netutils.IP4Address.IsValid(rhost):
1367 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1368 (minor, lhost, rhost))
1371 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1373 args = ["drbdsetup", cls._DevPath(minor), "net",
1374 "%s:%s:%s" % (family, lhost, lport),
1375 "%s:%s:%s" % (family, rhost, rport), protocol,
1376 "-A", "discard-zero-changes",
1383 args.extend(["-a", hmac, "-x", secret])
1384 result = utils.RunCmd(args)
1386 _ThrowError("drbd%d: can't setup network: %s - %s",
1387 minor, result.fail_reason, result.output)
1389 def _CheckNetworkConfig():
1390 info = cls._GetDevInfo(cls._GetShowData(minor))
1391 if not "local_addr" in info or not "remote_addr" in info:
1392 raise utils.RetryAgain()
1394 if (info["local_addr"] != (lhost, lport) or
1395 info["remote_addr"] != (rhost, rport)):
1396 raise utils.RetryAgain()
1399 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1400 except utils.RetryTimeout:
1401 _ThrowError("drbd%d: timeout while configuring network", minor)
1403 def AddChildren(self, devices):
1404 """Add a disk to the DRBD device.
1407 if self.minor is None:
1408 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1410 if len(devices) != 2:
1411 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1412 info = self._GetDevInfo(self._GetShowData(self.minor))
1413 if "local_dev" in info:
1414 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1415 backend, meta = devices
1416 if backend.dev_path is None or meta.dev_path is None:
1417 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1420 self._CheckMetaSize(meta.dev_path)
1421 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1423 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1424 self._children = devices
1426 def RemoveChildren(self, devices):
1427 """Detach the drbd device from local storage.
1430 if self.minor is None:
1431 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1433 # early return if we don't actually have backing storage
1434 info = self._GetDevInfo(self._GetShowData(self.minor))
1435 if "local_dev" not in info:
1437 if len(self._children) != 2:
1438 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1440 if self._children.count(None) == 2: # we don't actually have children :)
1441 logging.warning("drbd%d: requested detach while detached", self.minor)
1443 if len(devices) != 2:
1444 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1445 for child, dev in zip(self._children, devices):
1446 if dev != child.dev_path:
1447 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1448 " RemoveChildren", self.minor, dev, child.dev_path)
1450 self._ShutdownLocal(self.minor)
1454 def _SetMinorSyncSpeed(cls, minor, kbytes):
1455 """Set the speed of the DRBD syncer.
1457 This is the low-level implementation.
1460 @param minor: the drbd minor whose settings we change
1462 @param kbytes: the speed in kbytes/second
1464 @return: the success of the operation
1467 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1468 "-r", "%d" % kbytes, "--create-device"])
1470 logging.error("Can't change syncer rate: %s - %s",
1471 result.fail_reason, result.output)
1472 return not result.failed
1474 def SetSyncSpeed(self, kbytes):
1475 """Set the speed of the DRBD syncer.
1478 @param kbytes: the speed in kbytes/second
1480 @return: the success of the operation
1483 if self.minor is None:
1484 logging.info("Not attached during SetSyncSpeed")
1486 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1487 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1489 def GetProcStatus(self):
1490 """Return device data from /proc.
1493 if self.minor is None:
1494 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1495 proc_info = self._MassageProcData(self._GetProcData())
1496 if self.minor not in proc_info:
1497 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1498 return DRBD8Status(proc_info[self.minor])
1500 def GetSyncStatus(self):
1501 """Returns the sync status of the device.
1504 If sync_percent is None, it means all is ok
1505 If estimated_time is None, it means we can't estimate
1506 the time needed, otherwise it's the time left in seconds.
1509 We set the is_degraded parameter to True on two conditions:
1510 network not connected or local disk missing.
1512 We compute the ldisk parameter based on whether we have a local
1515 @rtype: objects.BlockDevStatus
1518 if self.minor is None and not self.Attach():
1519 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1521 stats = self.GetProcStatus()
1522 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1524 if stats.is_disk_uptodate:
1525 ldisk_status = constants.LDS_OKAY
1526 elif stats.is_diskless:
1527 ldisk_status = constants.LDS_FAULTY
1529 ldisk_status = constants.LDS_UNKNOWN
1531 return objects.BlockDevStatus(dev_path=self.dev_path,
1534 sync_percent=stats.sync_percent,
1535 estimated_time=stats.est_time,
1536 is_degraded=is_degraded,
1537 ldisk_status=ldisk_status)
1539 def Open(self, force=False):
1540 """Make the local state primary.
1542 If the 'force' parameter is given, the '-o' option is passed to
1543 drbdsetup. Since this is a potentially dangerous operation, the
1544 force flag should be only given after creation, when it actually
1548 if self.minor is None and not self.Attach():
1549 logging.error("DRBD cannot attach to a device during open")
1551 cmd = ["drbdsetup", self.dev_path, "primary"]
1554 result = utils.RunCmd(cmd)
1556 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1560 """Make the local state secondary.
1562 This will, of course, fail if the device is in use.
1565 if self.minor is None and not self.Attach():
1566 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1567 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1569 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1570 self.minor, result.output)
1572 def DisconnectNet(self):
1573 """Removes network configuration.
1575 This method shutdowns the network side of the device.
1577 The method will wait up to a hardcoded timeout for the device to
1578 go into standalone after the 'disconnect' command before
1579 re-configuring it, as sometimes it takes a while for the
1580 disconnect to actually propagate and thus we might issue a 'net'
1581 command while the device is still connected. If the device will
1582 still be attached to the network and we time out, we raise an
1586 if self.minor is None:
1587 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1589 if None in (self._lhost, self._lport, self._rhost, self._rport):
1590 _ThrowError("drbd%d: DRBD disk missing network info in"
1591 " DisconnectNet()", self.minor)
1593 class _DisconnectStatus:
1594 def __init__(self, ever_disconnected):
1595 self.ever_disconnected = ever_disconnected
1597 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1599 def _WaitForDisconnect():
1600 if self.GetProcStatus().is_standalone:
1603 # retry the disconnect, it seems possible that due to a well-time
1604 # disconnect on the peer, my disconnect command might be ignored and
1606 dstatus.ever_disconnected = \
1607 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1609 raise utils.RetryAgain()
1612 start_time = time.time()
1615 # Start delay at 100 milliseconds and grow up to 2 seconds
1616 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1617 self._NET_RECONFIG_TIMEOUT)
1618 except utils.RetryTimeout:
1619 if dstatus.ever_disconnected:
1620 msg = ("drbd%d: device did not react to the"
1621 " 'disconnect' command in a timely manner")
1623 msg = "drbd%d: can't shutdown network, even after multiple retries"
1625 _ThrowError(msg, self.minor)
1627 reconfig_time = time.time() - start_time
1628 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1629 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1630 self.minor, reconfig_time)
1632 def AttachNet(self, multimaster):
1633 """Reconnects the network.
1635 This method connects the network side of the device with a
1636 specified multi-master flag. The device needs to be 'Standalone'
1637 but have valid network configuration data.
1640 - multimaster: init the network in dual-primary mode
1643 if self.minor is None:
1644 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1646 if None in (self._lhost, self._lport, self._rhost, self._rport):
1647 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1649 status = self.GetProcStatus()
1651 if not status.is_standalone:
1652 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1654 self._AssembleNet(self.minor,
1655 (self._lhost, self._lport, self._rhost, self._rport),
1656 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1657 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1660 """Check if our minor is configured.
1662 This doesn't do any device configurations - it only checks if the
1663 minor is in a state different from Unconfigured.
1665 Note that this function will not change the state of the system in
1666 any way (except in case of side-effects caused by reading from
1670 used_devs = self.GetUsedDevs()
1671 if self._aminor in used_devs:
1672 minor = self._aminor
1676 self._SetFromMinor(minor)
1677 return minor is not None
1680 """Assemble the drbd.
1683 - if we have a configured device, we try to ensure that it matches
1685 - if not, we create it from zero
1688 super(DRBD8, self).Assemble()
1691 if self.minor is None:
1692 # local device completely unconfigured
1693 self._FastAssemble()
1695 # we have to recheck the local and network status and try to fix
1697 self._SlowAssemble()
1699 def _SlowAssemble(self):
1700 """Assembles the DRBD device from a (partially) configured device.
1702 In case of partially attached (local device matches but no network
1703 setup), we perform the network attach. If successful, we re-test
1704 the attach if can return success.
1707 # TODO: Rewrite to not use a for loop just because there is 'break'
1708 # pylint: disable-msg=W0631
1709 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1710 for minor in (self._aminor,):
1711 info = self._GetDevInfo(self._GetShowData(minor))
1712 match_l = self._MatchesLocal(info)
1713 match_r = self._MatchesNet(info)
1715 if match_l and match_r:
1716 # everything matches
1719 if match_l and not match_r and "local_addr" not in info:
1720 # disk matches, but not attached to network, attach and recheck
1721 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1722 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1723 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1726 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1727 " show' disagrees", minor)
1729 if match_r and "local_dev" not in info:
1730 # no local disk, but network attached and it matches
1731 self._AssembleLocal(minor, self._children[0].dev_path,
1732 self._children[1].dev_path, self.size)
1733 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1736 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1737 " show' disagrees", minor)
1739 # this case must be considered only if we actually have local
1740 # storage, i.e. not in diskless mode, because all diskless
1741 # devices are equal from the point of view of local
1743 if (match_l and "local_dev" in info and
1744 not match_r and "local_addr" in info):
1745 # strange case - the device network part points to somewhere
1746 # else, even though its local storage is ours; as we own the
1747 # drbd space, we try to disconnect from the remote peer and
1748 # reconnect to our correct one
1750 self._ShutdownNet(minor)
1751 except errors.BlockDeviceError, err:
1752 _ThrowError("drbd%d: device has correct local storage, wrong"
1753 " remote peer and is unable to disconnect in order"
1754 " to attach to the correct peer: %s", minor, str(err))
1755 # note: _AssembleNet also handles the case when we don't want
1756 # local storage (i.e. one or more of the _[lr](host|port) is
1758 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1759 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1760 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1763 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1764 " show' disagrees", minor)
1769 self._SetFromMinor(minor)
1771 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1774 def _FastAssemble(self):
1775 """Assemble the drbd device from zero.
1777 This is run when in Assemble we detect our minor is unused.
1780 minor = self._aminor
1781 if self._children and self._children[0] and self._children[1]:
1782 self._AssembleLocal(minor, self._children[0].dev_path,
1783 self._children[1].dev_path, self.size)
1784 if self._lhost and self._lport and self._rhost and self._rport:
1785 self._AssembleNet(minor,
1786 (self._lhost, self._lport, self._rhost, self._rport),
1787 constants.DRBD_NET_PROTOCOL,
1788 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1789 self._SetFromMinor(minor)
1792 def _ShutdownLocal(cls, minor):
1793 """Detach from the local device.
1795 I/Os will continue to be served from the remote device. If we
1796 don't have a remote device, this operation will fail.
1799 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1801 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1804 def _ShutdownNet(cls, minor):
1805 """Disconnect from the remote peer.
1807 This fails if we don't have a local device.
1810 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1812 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1815 def _ShutdownAll(cls, minor):
1816 """Deactivate the device.
1818 This will, of course, fail if the device is in use.
1821 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1823 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1824 minor, result.output)
1827 """Shutdown the DRBD device.
1830 if self.minor is None and not self.Attach():
1831 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1835 self.dev_path = None
1836 self._ShutdownAll(minor)
1839 """Stub remove for DRBD devices.
1845 def Create(cls, unique_id, children, size):
1846 """Create a new DRBD8 device.
1848 Since DRBD devices are not created per se, just assembled, this
1849 function only initializes the metadata.
1852 if len(children) != 2:
1853 raise errors.ProgrammerError("Invalid setup for the drbd device")
1854 # check that the minor is unused
1855 aminor = unique_id[4]
1856 proc_info = cls._MassageProcData(cls._GetProcData())
1857 if aminor in proc_info:
1858 status = DRBD8Status(proc_info[aminor])
1859 in_use = status.is_in_use
1863 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1866 if not meta.Attach():
1867 _ThrowError("drbd%d: can't attach to meta device '%s'",
1869 cls._CheckMetaSize(meta.dev_path)
1870 cls._InitMeta(aminor, meta.dev_path)
1871 return cls(unique_id, children, size)
1873 def Grow(self, amount):
1874 """Resize the DRBD device and its backing storage.
1877 if self.minor is None:
1878 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1879 if len(self._children) != 2 or None in self._children:
1880 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1881 self._children[0].Grow(amount)
1882 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1883 "%dm" % (self.size + amount)])
1885 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1888 class FileStorage(BlockDev):
1891 This class represents the a file storage backend device.
1893 The unique_id for the file device is a (file_driver, file_path) tuple.
1896 def __init__(self, unique_id, children, size):
1897 """Initalizes a file device backend.
1901 raise errors.BlockDeviceError("Invalid setup for file device")
1902 super(FileStorage, self).__init__(unique_id, children, size)
1903 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1904 raise ValueError("Invalid configuration data %s" % str(unique_id))
1905 self.driver = unique_id[0]
1906 self.dev_path = unique_id[1]
1910 """Assemble the device.
1912 Checks whether the file device exists, raises BlockDeviceError otherwise.
1915 if not os.path.exists(self.dev_path):
1916 _ThrowError("File device '%s' does not exist" % self.dev_path)
1919 """Shutdown the device.
1921 This is a no-op for the file type, as we don't deactivate
1922 the file on shutdown.
1927 def Open(self, force=False):
1928 """Make the device ready for I/O.
1930 This is a no-op for the file type.
1936 """Notifies that the device will no longer be used for I/O.
1938 This is a no-op for the file type.
1944 """Remove the file backing the block device.
1947 @return: True if the removal was successful
1951 os.remove(self.dev_path)
1952 except OSError, err:
1953 if err.errno != errno.ENOENT:
1954 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1956 def Rename(self, new_id):
1957 """Renames the file.
1960 # TODO: implement rename for file-based storage
1961 _ThrowError("Rename is not supported for file-based storage")
1963 def Grow(self, amount):
1966 @param amount: the amount (in mebibytes) to grow with
1969 # Check that the file exists
1971 current_size = self.GetActualSize()
1972 new_size = current_size + amount * 1024 * 1024
1973 assert new_size > current_size, "Cannot Grow with a negative amount"
1975 f = open(self.dev_path, "a+")
1976 f.truncate(new_size)
1978 except EnvironmentError, err:
1979 _ThrowError("Error in file growth: %", str(err))
1982 """Attach to an existing file.
1984 Check if this file already exists.
1987 @return: True if file exists
1990 self.attached = os.path.exists(self.dev_path)
1991 return self.attached
1993 def GetActualSize(self):
1994 """Return the actual disk size.
1996 @note: the device needs to be active when this is called
1999 assert self.attached, "BlockDevice not attached in GetActualSize()"
2001 st = os.stat(self.dev_path)
2003 except OSError, err:
2004 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2007 def Create(cls, unique_id, children, size):
2008 """Create a new file.
2010 @param size: the size of file in MiB
2012 @rtype: L{bdev.FileStorage}
2013 @return: an instance of FileStorage
2016 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2017 raise ValueError("Invalid configuration data %s" % str(unique_id))
2018 dev_path = unique_id[1]
2020 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2021 f = os.fdopen(fd, "w")
2022 f.truncate(size * 1024 * 1024)
2024 except EnvironmentError, err:
2025 if err.errno == errno.EEXIST:
2026 _ThrowError("File already existing: %s", dev_path)
2027 _ThrowError("Error in file creation: %", str(err))
2029 return FileStorage(unique_id, children, size)
2033 constants.LD_LV: LogicalVolume,
2034 constants.LD_DRBD8: DRBD8,
2037 if constants.ENABLE_FILE_STORAGE:
2038 DEV_MAP[constants.LD_FILE] = FileStorage
2041 def FindDevice(dev_type, unique_id, children, size):
2042 """Search for an existing, assembled device.
2044 This will succeed only if the device exists and is assembled, but it
2045 does not do any actions in order to activate the device.
2048 if dev_type not in DEV_MAP:
2049 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2050 device = DEV_MAP[dev_type](unique_id, children, size)
2051 if not device.attached:
2056 def Assemble(dev_type, unique_id, children, size):
2057 """Try to attach or assemble an existing device.
2059 This will attach to assemble the device, as needed, to bring it
2060 fully up. It must be safe to run on already-assembled devices.
2063 if dev_type not in DEV_MAP:
2064 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2065 device = DEV_MAP[dev_type](unique_id, children, size)
2070 def Create(dev_type, unique_id, children, size):
2074 if dev_type not in DEV_MAP:
2075 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2076 device = DEV_MAP[dev_type].Create(unique_id, children, size)