4 # Copyright (C) 2006, 2007 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
38 # Size of reads in _CanReadDevice
39 _DEVICE_READ_SIZE = 128 * 1024
42 def _IgnoreError(fn, *args, **kwargs):
43 """Executes the given function, ignoring BlockDeviceErrors.
45 This is used in order to simplify the execution of cleanup or
49 @return: True when fn didn't raise an exception, False otherwise
55 except errors.BlockDeviceError, err:
56 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
60 def _ThrowError(msg, *args):
61 """Log an error to the node daemon and the raise an exception.
64 @param msg: the text of the exception
65 @raise errors.BlockDeviceError
71 raise errors.BlockDeviceError(msg)
74 def _CanReadDevice(path):
75 """Check if we can read from the given device.
77 This tries to read the first 128k of the device.
81 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
83 except EnvironmentError:
84 logging.warning("Can't read from device %s", path, exc_info=True)
88 class BlockDev(object):
89 """Block device abstract class.
91 A block device can be in the following states:
92 - not existing on the system, and by `Create()` it goes into:
93 - existing but not setup/not active, and by `Assemble()` goes into:
94 - active read-write and by `Open()` it goes into
95 - online (=used, or ready for use)
97 A device can also be online but read-only, however we are not using
98 the readonly state (LV has it, if needed in the future) and we are
99 usually looking at this like at a stack, so it's easier to
100 conceptualise the transition from not-existing to online and back
103 The many different states of the device are due to the fact that we
104 need to cover many device types:
105 - logical volumes are created, lvchange -a y $lv, and used
106 - drbd devices are attached to a local disk/remote peer and made primary
108 A block device is identified by three items:
109 - the /dev path of the device (dynamic)
110 - a unique ID of the device (static)
111 - it's major/minor pair (dynamic)
113 Not all devices implement both the first two as distinct items. LVM
114 logical volumes have their unique ID (the pair volume group, logical
115 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
116 the /dev path is again dynamic and the unique id is the pair (host1,
117 dev1), (host2, dev2).
119 You can get to a device in two ways:
120 - creating the (real) device, which returns you
121 an attached instance (lvcreate)
122 - attaching of a python instance to an existing (real) device
124 The second point, the attachement to a device, is different
125 depending on whether the device is assembled or not. At init() time,
126 we search for a device with the same unique_id as us. If found,
127 good. It also means that the device is already assembled. If not,
128 after assembly we'll have our correct major/minor.
131 def __init__(self, unique_id, children, size):
132 self._children = children
134 self.unique_id = unique_id
137 self.attached = False
141 """Assemble the device from its components.
143 Implementations of this method by child classes must ensure that:
144 - after the device has been assembled, it knows its major/minor
145 numbers; this allows other devices (usually parents) to probe
146 correctly for their children
147 - calling this method on an existing, in-use device is safe
148 - if the device is already configured (and in an OK state),
149 this method is idempotent
155 """Find a device which matches our config and attach to it.
158 raise NotImplementedError
161 """Notifies that the device will no longer be used for I/O.
164 raise NotImplementedError
167 def Create(cls, unique_id, children, size):
168 """Create the device.
170 If the device cannot be created, it will return None
171 instead. Error messages go to the logging system.
173 Note that for some devices, the unique_id is used, and for other,
174 the children. The idea is that these two, taken together, are
175 enough for both creation and assembly (later).
178 raise NotImplementedError
181 """Remove this device.
183 This makes sense only for some of the device types: LV and file
184 storage. Also note that if the device can't attach, the removal
188 raise NotImplementedError
190 def Rename(self, new_id):
191 """Rename this device.
193 This may or may not make sense for a given device type.
196 raise NotImplementedError
198 def Open(self, force=False):
199 """Make the device ready for use.
201 This makes the device ready for I/O. For now, just the DRBD
204 The force parameter signifies that if the device has any kind of
205 --force thing, it should be used, we know what we are doing.
208 raise NotImplementedError
211 """Shut down the device, freeing its children.
213 This undoes the `Assemble()` work, except for the child
214 assembling; as such, the children on the device are still
215 assembled after this call.
218 raise NotImplementedError
220 def SetSyncSpeed(self, speed):
221 """Adjust the sync speed of the mirror.
223 In case this is not a mirroring device, this is no-op.
228 for child in self._children:
229 result = result and child.SetSyncSpeed(speed)
232 def GetSyncStatus(self):
233 """Returns the sync status of the device.
235 If this device is a mirroring device, this function returns the
236 status of the mirror.
238 If sync_percent is None, it means the device is not syncing.
240 If estimated_time is None, it means we can't estimate
241 the time needed, otherwise it's the time left in seconds.
243 If is_degraded is True, it means the device is missing
244 redundancy. This is usually a sign that something went wrong in
245 the device setup, if sync_percent is None.
247 The ldisk parameter represents the degradation of the local
248 data. This is only valid for some devices, the rest will always
249 return False (not degraded).
251 @rtype: objects.BlockDevStatus
254 return objects.BlockDevStatus(dev_path=self.dev_path,
260 ldisk_status=constants.LDS_OKAY)
262 def CombinedSyncStatus(self):
263 """Calculate the mirror status recursively for our children.
265 The return value is the same as for `GetSyncStatus()` except the
266 minimum percent and maximum time are calculated across our
269 @rtype: objects.BlockDevStatus
272 status = self.GetSyncStatus()
274 min_percent = status.sync_percent
275 max_time = status.estimated_time
276 is_degraded = status.is_degraded
277 ldisk_status = status.ldisk_status
280 for child in self._children:
281 child_status = child.GetSyncStatus()
283 if min_percent is None:
284 min_percent = child_status.sync_percent
285 elif child_status.sync_percent is not None:
286 min_percent = min(min_percent, child_status.sync_percent)
289 max_time = child_status.estimated_time
290 elif child_status.estimated_time is not None:
291 max_time = max(max_time, child_status.estimated_time)
293 is_degraded = is_degraded or child_status.is_degraded
295 if ldisk_status is None:
296 ldisk_status = child_status.ldisk_status
297 elif child_status.ldisk_status is not None:
298 ldisk_status = max(ldisk_status, child_status.ldisk_status)
300 return objects.BlockDevStatus(dev_path=self.dev_path,
303 sync_percent=min_percent,
304 estimated_time=max_time,
305 is_degraded=is_degraded,
306 ldisk_status=ldisk_status)
309 def SetInfo(self, text):
310 """Update metadata with info text.
312 Only supported for some device types.
315 for child in self._children:
318 def Grow(self, amount):
319 """Grow the block device.
321 @param amount: the amount (in mebibytes) to grow with
324 raise NotImplementedError
326 def GetActualSize(self):
327 """Return the actual disk size.
329 @note: the device needs to be active when this is called
332 assert self.attached, "BlockDevice not attached in GetActualSize()"
333 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
335 _ThrowError("blockdev failed (%s): %s",
336 result.fail_reason, result.output)
338 sz = int(result.output.strip())
339 except (ValueError, TypeError), err:
340 _ThrowError("Failed to parse blockdev output: %s", str(err))
344 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
345 (self.__class__, self.unique_id, self._children,
346 self.major, self.minor, self.dev_path))
349 class LogicalVolume(BlockDev):
350 """Logical Volume block device.
353 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
354 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
355 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
357 def __init__(self, unique_id, children, size):
358 """Attaches to a LV device.
360 The unique_id is a tuple (vg_name, lv_name)
363 super(LogicalVolume, self).__init__(unique_id, children, size)
364 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
365 raise ValueError("Invalid configuration data %s" % str(unique_id))
366 self._vg_name, self._lv_name = unique_id
367 self._ValidateName(self._vg_name)
368 self._ValidateName(self._lv_name)
369 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
370 self._degraded = True
371 self.major = self.minor = self.pe_size = self.stripe_count = None
375 def Create(cls, unique_id, children, size):
376 """Create a new logical volume.
379 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
380 raise errors.ProgrammerError("Invalid configuration data %s" %
382 vg_name, lv_name = unique_id
383 cls._ValidateName(vg_name)
384 cls._ValidateName(lv_name)
385 pvs_info = cls.GetPVInfo([vg_name])
387 _ThrowError("Can't compute PV info for vg %s", vg_name)
391 pvlist = [ pv[1] for pv in pvs_info ]
392 if compat.any(pvlist, lambda v: ":" in v):
393 _ThrowError("Some of your PVs have the invalid character ':' in their"
394 " name, this is not supported - please filter them out"
395 " in lvm.conf using either 'filter' or 'preferred_names'")
396 free_size = sum([ pv[0] for pv in pvs_info ])
397 current_pvs = len(pvlist)
398 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
400 # The size constraint should have been checked from the master before
401 # calling the create function.
403 _ThrowError("Not enough free space: required %s,"
404 " available %s", size, free_size)
405 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
406 # If the free space is not well distributed, we won't be able to
407 # create an optimally-striped volume; in that case, we want to try
408 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
410 for stripes_arg in range(stripes, 0, -1):
411 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
412 if not result.failed:
415 _ThrowError("LV create failed (%s): %s",
416 result.fail_reason, result.output)
417 return LogicalVolume(unique_id, children, size)
420 def GetPVInfo(vg_names, filter_allocatable=True):
421 """Get the free space info for PVs in a volume group.
423 @param vg_names: list of volume group names, if empty all will be returned
424 @param filter_allocatable: whether to skip over unallocatable PVs
427 @return: list of tuples (free_space, name) with free_space in mebibytes
431 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
432 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
433 "--separator=%s" % sep ]
434 result = utils.RunCmd(command)
436 logging.error("Can't get the PV information: %s - %s",
437 result.fail_reason, result.output)
440 for line in result.stdout.splitlines():
441 fields = line.strip().split(sep)
443 logging.error("Can't parse pvs output: line '%s'", line)
445 # (possibly) skip over pvs which are not allocatable
446 if filter_allocatable and fields[3][0] != 'a':
448 # (possibly) skip over pvs which are not in the right volume group(s)
449 if vg_names and fields[1] not in vg_names:
451 data.append((float(fields[2]), fields[0], fields[1]))
456 def _ValidateName(cls, name):
457 """Validates that a given name is valid as VG or LV name.
459 The list of valid characters and restricted names is taken out of
460 the lvm(8) manpage, with the simplification that we enforce both
461 VG and LV restrictions on the names.
464 if (not cls._VALID_NAME_RE.match(name) or
465 name in cls._INVALID_NAMES or
466 compat.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
467 _ThrowError("Invalid LVM name '%s'", name)
470 """Remove this logical volume.
473 if not self.minor and not self.Attach():
474 # the LV does not exist
476 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
477 (self._vg_name, self._lv_name)])
479 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
481 def Rename(self, new_id):
482 """Rename this logical volume.
485 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
486 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
487 new_vg, new_name = new_id
488 if new_vg != self._vg_name:
489 raise errors.ProgrammerError("Can't move a logical volume across"
490 " volume groups (from %s to to %s)" %
491 (self._vg_name, new_vg))
492 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
494 _ThrowError("Failed to rename the logical volume: %s", result.output)
495 self._lv_name = new_name
496 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
499 """Attach to an existing LV.
501 This method will try to see if an existing and active LV exists
502 which matches our name. If so, its major/minor will be
506 self.attached = False
507 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
508 "--units=m", "--nosuffix",
509 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
510 "vg_extent_size,stripes", self.dev_path])
512 logging.error("Can't find LV %s: %s, %s",
513 self.dev_path, result.fail_reason, result.output)
515 # the output can (and will) have multiple lines for multi-segment
516 # LVs, as the 'stripes' parameter is a segment one, so we take
517 # only the last entry, which is the one we're interested in; note
518 # that with LVM2 anyway the 'stripes' value must be constant
519 # across segments, so this is a no-op actually
520 out = result.stdout.splitlines()
521 if not out: # totally empty result? splitlines() returns at least
522 # one line for any non-empty string
523 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
525 out = out[-1].strip().rstrip(',')
528 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
531 status, major, minor, pe_size, stripes = out
533 logging.error("lvs lv_attr is not 6 characters (%s)", status)
539 except (TypeError, ValueError), err:
540 logging.error("lvs major/minor cannot be parsed: %s", str(err))
543 pe_size = int(float(pe_size))
544 except (TypeError, ValueError), err:
545 logging.error("Can't parse vg extent size: %s", err)
549 stripes = int(stripes)
550 except (TypeError, ValueError), err:
551 logging.error("Can't parse the number of stripes: %s", err)
556 self.pe_size = pe_size
557 self.stripe_count = stripes
558 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
564 """Assemble the device.
566 We always run `lvchange -ay` on the LV to ensure it's active before
567 use, as there were cases when xenvg was not active after boot
568 (also possibly after disk issues).
571 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
573 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
576 """Shutdown the device.
578 This is a no-op for the LV device type, as we don't deactivate the
584 def GetSyncStatus(self):
585 """Returns the sync status of the device.
587 If this device is a mirroring device, this function returns the
588 status of the mirror.
590 For logical volumes, sync_percent and estimated_time are always
591 None (no recovery in progress, as we don't handle the mirrored LV
592 case). The is_degraded parameter is the inverse of the ldisk
595 For the ldisk parameter, we check if the logical volume has the
596 'virtual' type, which means it's not backed by existing storage
597 anymore (read from it return I/O error). This happens after a
598 physical disk failure and subsequent 'vgreduce --removemissing' on
601 The status was already read in Attach, so we just return it.
603 @rtype: objects.BlockDevStatus
607 ldisk_status = constants.LDS_FAULTY
609 ldisk_status = constants.LDS_OKAY
611 return objects.BlockDevStatus(dev_path=self.dev_path,
616 is_degraded=self._degraded,
617 ldisk_status=ldisk_status)
619 def Open(self, force=False):
620 """Make the device ready for I/O.
622 This is a no-op for the LV device type.
628 """Notifies that the device will no longer be used for I/O.
630 This is a no-op for the LV device type.
635 def Snapshot(self, size):
636 """Create a snapshot copy of an lvm block device.
639 snap_name = self._lv_name + ".snap"
641 # remove existing snapshot if found
642 snap = LogicalVolume((self._vg_name, snap_name), None, size)
643 _IgnoreError(snap.Remove)
645 pvs_info = self.GetPVInfo([self._vg_name])
647 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
650 free_size, _, _ = pvs_info[0]
652 _ThrowError("Not enough free space: required %s,"
653 " available %s", size, free_size)
655 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
656 "-n%s" % snap_name, self.dev_path])
658 _ThrowError("command: %s error: %s - %s",
659 result.cmd, result.fail_reason, result.output)
663 def SetInfo(self, text):
664 """Update metadata with info text.
667 BlockDev.SetInfo(self, text)
669 # Replace invalid characters
670 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
671 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
673 # Only up to 128 characters are allowed
676 result = utils.RunCmd(["lvchange", "--addtag", text,
679 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
682 def Grow(self, amount):
683 """Grow the logical volume.
686 if self.pe_size is None or self.stripe_count is None:
687 if not self.Attach():
688 _ThrowError("Can't attach to LV during Grow()")
689 full_stripe_size = self.pe_size * self.stripe_count
690 rest = amount % full_stripe_size
692 amount += full_stripe_size - rest
693 # we try multiple algorithms since the 'best' ones might not have
694 # space available in the right place, but later ones might (since
695 # they have less constraints); also note that only recent LVM
697 for alloc_policy in "contiguous", "cling", "normal":
698 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
699 "-L", "+%dm" % amount, self.dev_path])
700 if not result.failed:
702 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
705 class DRBD8Status(object):
706 """A DRBD status representation class.
708 Note that this doesn't support unconfigured devices (cs:Unconfigured).
711 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
712 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
713 "\s+ds:([^/]+)/(\S+)\s+.*$")
714 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
715 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
717 CS_UNCONFIGURED = "Unconfigured"
718 CS_STANDALONE = "StandAlone"
719 CS_WFCONNECTION = "WFConnection"
720 CS_WFREPORTPARAMS = "WFReportParams"
721 CS_CONNECTED = "Connected"
722 CS_STARTINGSYNCS = "StartingSyncS"
723 CS_STARTINGSYNCT = "StartingSyncT"
724 CS_WFBITMAPS = "WFBitMapS"
725 CS_WFBITMAPT = "WFBitMapT"
726 CS_WFSYNCUUID = "WFSyncUUID"
727 CS_SYNCSOURCE = "SyncSource"
728 CS_SYNCTARGET = "SyncTarget"
729 CS_PAUSEDSYNCS = "PausedSyncS"
730 CS_PAUSEDSYNCT = "PausedSyncT"
731 CSET_SYNC = frozenset([
744 DS_DISKLESS = "Diskless"
745 DS_ATTACHING = "Attaching" # transient state
746 DS_FAILED = "Failed" # transient state, next: diskless
747 DS_NEGOTIATING = "Negotiating" # transient state
748 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
749 DS_OUTDATED = "Outdated"
750 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
751 DS_CONSISTENT = "Consistent"
752 DS_UPTODATE = "UpToDate" # normal state
754 RO_PRIMARY = "Primary"
755 RO_SECONDARY = "Secondary"
756 RO_UNKNOWN = "Unknown"
758 def __init__(self, procline):
759 u = self.UNCONF_RE.match(procline)
761 self.cstatus = self.CS_UNCONFIGURED
762 self.lrole = self.rrole = self.ldisk = self.rdisk = None
764 m = self.LINE_RE.match(procline)
766 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
767 self.cstatus = m.group(1)
768 self.lrole = m.group(2)
769 self.rrole = m.group(3)
770 self.ldisk = m.group(4)
771 self.rdisk = m.group(5)
773 # end reading of data from the LINE_RE or UNCONF_RE
775 self.is_standalone = self.cstatus == self.CS_STANDALONE
776 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
777 self.is_connected = self.cstatus == self.CS_CONNECTED
778 self.is_primary = self.lrole == self.RO_PRIMARY
779 self.is_secondary = self.lrole == self.RO_SECONDARY
780 self.peer_primary = self.rrole == self.RO_PRIMARY
781 self.peer_secondary = self.rrole == self.RO_SECONDARY
782 self.both_primary = self.is_primary and self.peer_primary
783 self.both_secondary = self.is_secondary and self.peer_secondary
785 self.is_diskless = self.ldisk == self.DS_DISKLESS
786 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
788 self.is_in_resync = self.cstatus in self.CSET_SYNC
789 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
791 m = self.SYNC_RE.match(procline)
793 self.sync_percent = float(m.group(1))
794 hours = int(m.group(2))
795 minutes = int(m.group(3))
796 seconds = int(m.group(4))
797 self.est_time = hours * 3600 + minutes * 60 + seconds
799 # we have (in this if branch) no percent information, but if
800 # we're resyncing we need to 'fake' a sync percent information,
801 # as this is how cmdlib determines if it makes sense to wait for
803 if self.is_in_resync:
804 self.sync_percent = 0
806 self.sync_percent = None
810 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
813 This class contains a few bits of common functionality between the
814 0.7 and 8.x versions of DRBD.
817 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
818 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
819 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
820 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
823 _ST_UNCONFIGURED = "Unconfigured"
824 _ST_WFCONNECTION = "WFConnection"
825 _ST_CONNECTED = "Connected"
827 _STATUS_FILE = "/proc/drbd"
830 def _GetProcData(filename=_STATUS_FILE):
831 """Return data from /proc/drbd.
835 data = utils.ReadFile(filename).splitlines()
836 except EnvironmentError, err:
837 if err.errno == errno.ENOENT:
838 _ThrowError("The file %s cannot be opened, check if the module"
839 " is loaded (%s)", filename, str(err))
841 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
843 _ThrowError("Can't read any data from %s", filename)
847 def _MassageProcData(cls, data):
848 """Transform the output of _GetProdData into a nicer form.
850 @return: a dictionary of minor: joined lines from /proc/drbd
855 old_minor = old_line = None
857 if not line: # completely empty lines, as can be returned by drbd8.0+
859 lresult = cls._VALID_LINE_RE.match(line)
860 if lresult is not None:
861 if old_minor is not None:
862 results[old_minor] = old_line
863 old_minor = int(lresult.group(1))
866 if old_minor is not None:
867 old_line += " " + line.strip()
869 if old_minor is not None:
870 results[old_minor] = old_line
874 def _GetVersion(cls):
875 """Return the DRBD version.
877 This will return a dict with keys:
883 - proto2 (only on drbd > 8.2.X)
886 proc_data = cls._GetProcData()
887 first_line = proc_data[0].strip()
888 version = cls._VERSION_RE.match(first_line)
890 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
893 values = version.groups()
894 retval = {'k_major': int(values[0]),
895 'k_minor': int(values[1]),
896 'k_point': int(values[2]),
897 'api': int(values[3]),
898 'proto': int(values[4]),
900 if values[5] is not None:
901 retval['proto2'] = values[5]
907 """Return the path to a drbd device for a given minor.
910 return "/dev/drbd%d" % minor
913 def GetUsedDevs(cls):
914 """Compute the list of used DRBD devices.
917 data = cls._GetProcData()
921 match = cls._VALID_LINE_RE.match(line)
924 minor = int(match.group(1))
925 state = match.group(2)
926 if state == cls._ST_UNCONFIGURED:
928 used_devs[minor] = state, line
932 def _SetFromMinor(self, minor):
933 """Set our parameters based on the given minor.
935 This sets our minor variable and our dev_path.
939 self.minor = self.dev_path = None
940 self.attached = False
943 self.dev_path = self._DevPath(minor)
947 def _CheckMetaSize(meta_device):
948 """Check if the given meta device looks like a valid one.
950 This currently only check the size, which must be around
954 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
956 _ThrowError("Failed to get device size: %s - %s",
957 result.fail_reason, result.output)
959 sectors = int(result.stdout)
960 except (TypeError, ValueError):
961 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
962 num_bytes = sectors * 512
963 if num_bytes < 128 * 1024 * 1024: # less than 128MiB
964 _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
965 # the maximum *valid* size of the meta device when living on top
966 # of LVM is hard to compute: it depends on the number of stripes
967 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
968 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
969 # size meta device; as such, we restrict it to 1GB (a little bit
970 # too generous, but making assumptions about PE size is hard)
971 if num_bytes > 1024 * 1024 * 1024:
972 _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
974 def Rename(self, new_id):
977 This is not supported for drbd devices.
980 raise errors.ProgrammerError("Can't rename a drbd device")
983 class DRBD8(BaseDRBD):
984 """DRBD v8.x block device.
986 This implements the local host part of the DRBD device, i.e. it
987 doesn't do anything to the supposed peer. If you need a fully
988 connected DRBD pair, you need to use this class on both hosts.
990 The unique_id for the drbd device is the (local_ip, local_port,
991 remote_ip, remote_port) tuple, and it must have two children: the
992 data device and the meta_device. The meta device is checked for
993 valid size and is zeroed on create.
1000 _NET_RECONFIG_TIMEOUT = 60
1002 def __init__(self, unique_id, children, size):
1003 if children and children.count(None) > 0:
1005 if len(children) not in (0, 2):
1006 raise ValueError("Invalid configuration data %s" % str(children))
1007 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1008 raise ValueError("Invalid configuration data %s" % str(unique_id))
1009 (self._lhost, self._lport,
1010 self._rhost, self._rport,
1011 self._aminor, self._secret) = unique_id
1013 if not _CanReadDevice(children[1].dev_path):
1014 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1016 super(DRBD8, self).__init__(unique_id, children, size)
1017 self.major = self._DRBD_MAJOR
1018 version = self._GetVersion()
1019 if version['k_major'] != 8 :
1020 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1021 " usage: kernel is %s.%s, ganeti wants 8.x",
1022 version['k_major'], version['k_minor'])
1024 if (self._lhost is not None and self._lhost == self._rhost and
1025 self._lport == self._rport):
1026 raise ValueError("Invalid configuration data, same local/remote %s" %
1031 def _InitMeta(cls, minor, dev_path):
1032 """Initialize a meta device.
1034 This will not work if the given minor is in use.
1037 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1038 "v08", dev_path, "0", "create-md"])
1040 _ThrowError("Can't initialize meta device: %s", result.output)
1043 def _FindUnusedMinor(cls):
1044 """Find an unused DRBD device.
1046 This is specific to 8.x as the minors are allocated dynamically,
1047 so non-existing numbers up to a max minor count are actually free.
1050 data = cls._GetProcData()
1054 match = cls._UNUSED_LINE_RE.match(line)
1056 return int(match.group(1))
1057 match = cls._VALID_LINE_RE.match(line)
1059 minor = int(match.group(1))
1060 highest = max(highest, minor)
1061 if highest is None: # there are no minors in use at all
1063 if highest >= cls._MAX_MINORS:
1064 logging.error("Error: no free drbd minors!")
1065 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1069 def _GetShowParser(cls):
1070 """Return a parser for `drbd show` output.
1072 This will either create or return an already-create parser for the
1073 output of the command `drbd show`.
1076 if cls._PARSE_SHOW is not None:
1077 return cls._PARSE_SHOW
1080 lbrace = pyp.Literal("{").suppress()
1081 rbrace = pyp.Literal("}").suppress()
1082 semi = pyp.Literal(";").suppress()
1083 # this also converts the value to an int
1084 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1086 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1087 defa = pyp.Literal("_is_default").suppress()
1088 dbl_quote = pyp.Literal('"').suppress()
1090 keyword = pyp.Word(pyp.alphanums + '-')
1093 value = pyp.Word(pyp.alphanums + '_-/.:')
1094 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1095 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1096 pyp.Optional(pyp.Literal("ipv6")).suppress())
1097 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1098 pyp.Literal(':').suppress() + number)
1099 # meta device, extended syntax
1100 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1101 number + pyp.Word(']').suppress())
1102 # device name, extended syntax
1103 device_value = pyp.Literal("minor").suppress() + number
1106 stmt = (~rbrace + keyword + ~lbrace +
1107 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1109 pyp.Optional(defa) + semi +
1110 pyp.Optional(pyp.restOfLine).suppress())
1113 section_name = pyp.Word(pyp.alphas + '_')
1114 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1116 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1119 cls._PARSE_SHOW = bnf
1124 def _GetShowData(cls, minor):
1125 """Return the `drbdsetup show` data for a minor.
1128 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1130 logging.error("Can't display the drbd config: %s - %s",
1131 result.fail_reason, result.output)
1133 return result.stdout
1136 def _GetDevInfo(cls, out):
1137 """Parse details about a given DRBD minor.
1139 This return, if available, the local backing device (as a path)
1140 and the local and remote (ip, port) information from a string
1141 containing the output of the `drbdsetup show` command as returned
1149 bnf = cls._GetShowParser()
1153 results = bnf.parseString(out)
1154 except pyp.ParseException, err:
1155 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1157 # and massage the results into our desired format
1158 for section in results:
1160 if sname == "_this_host":
1161 for lst in section[1:]:
1162 if lst[0] == "disk":
1163 data["local_dev"] = lst[1]
1164 elif lst[0] == "meta-disk":
1165 data["meta_dev"] = lst[1]
1166 data["meta_index"] = lst[2]
1167 elif lst[0] == "address":
1168 data["local_addr"] = tuple(lst[1:])
1169 elif sname == "_remote_host":
1170 for lst in section[1:]:
1171 if lst[0] == "address":
1172 data["remote_addr"] = tuple(lst[1:])
1175 def _MatchesLocal(self, info):
1176 """Test if our local config matches with an existing device.
1178 The parameter should be as returned from `_GetDevInfo()`. This
1179 method tests if our local backing device is the same as the one in
1180 the info parameter, in effect testing if we look like the given
1185 backend, meta = self._children
1187 backend = meta = None
1189 if backend is not None:
1190 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1192 retval = ("local_dev" not in info)
1194 if meta is not None:
1195 retval = retval and ("meta_dev" in info and
1196 info["meta_dev"] == meta.dev_path)
1197 retval = retval and ("meta_index" in info and
1198 info["meta_index"] == 0)
1200 retval = retval and ("meta_dev" not in info and
1201 "meta_index" not in info)
1204 def _MatchesNet(self, info):
1205 """Test if our network config matches with an existing device.
1207 The parameter should be as returned from `_GetDevInfo()`. This
1208 method tests if our network configuration is the same as the one
1209 in the info parameter, in effect testing if we look like the given
1213 if (((self._lhost is None and not ("local_addr" in info)) and
1214 (self._rhost is None and not ("remote_addr" in info)))):
1217 if self._lhost is None:
1220 if not ("local_addr" in info and
1221 "remote_addr" in info):
1224 retval = (info["local_addr"] == (self._lhost, self._lport))
1225 retval = (retval and
1226 info["remote_addr"] == (self._rhost, self._rport))
1230 def _AssembleLocal(cls, minor, backend, meta, size):
1231 """Configure the local part of a DRBD device.
1234 args = ["drbdsetup", cls._DevPath(minor), "disk",
1239 args.extend(["-d", "%sm" % size])
1240 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1241 version = cls._GetVersion()
1242 # various DRBD versions support different disk barrier options;
1243 # what we aim here is to revert back to the 'drain' method of
1244 # disk flushes and to disable metadata barriers, in effect going
1245 # back to pre-8.0.7 behaviour
1246 vmaj = version['k_major']
1247 vmin = version['k_minor']
1248 vrel = version['k_point']
1250 if vmin == 0: # 8.0.x
1252 args.extend(['-i', '-m'])
1253 elif vmin == 2: # 8.2.x
1255 args.extend(['-i', '-m'])
1256 elif vmaj >= 3: # 8.3.x or newer
1257 args.extend(['-i', '-a', 'm'])
1258 result = utils.RunCmd(args)
1260 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1263 def _AssembleNet(cls, minor, net_info, protocol,
1264 dual_pri=False, hmac=None, secret=None):
1265 """Configure the network part of the device.
1268 lhost, lport, rhost, rport = net_info
1269 if None in net_info:
1270 # we don't want network connection and actually want to make
1272 cls._ShutdownNet(minor)
1275 # Workaround for a race condition. When DRBD is doing its dance to
1276 # establish a connection with its peer, it also sends the
1277 # synchronization speed over the wire. In some cases setting the
1278 # sync speed only after setting up both sides can race with DRBD
1279 # connecting, hence we set it here before telling DRBD anything
1281 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1283 args = ["drbdsetup", cls._DevPath(minor), "net",
1284 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1285 "-A", "discard-zero-changes",
1292 args.extend(["-a", hmac, "-x", secret])
1293 result = utils.RunCmd(args)
1295 _ThrowError("drbd%d: can't setup network: %s - %s",
1296 minor, result.fail_reason, result.output)
1298 def _CheckNetworkConfig():
1299 info = cls._GetDevInfo(cls._GetShowData(minor))
1300 if not "local_addr" in info or not "remote_addr" in info:
1301 raise utils.RetryAgain()
1303 if (info["local_addr"] != (lhost, lport) or
1304 info["remote_addr"] != (rhost, rport)):
1305 raise utils.RetryAgain()
1308 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1309 except utils.RetryTimeout:
1310 _ThrowError("drbd%d: timeout while configuring network", minor)
1312 def AddChildren(self, devices):
1313 """Add a disk to the DRBD device.
1316 if self.minor is None:
1317 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1319 if len(devices) != 2:
1320 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1321 info = self._GetDevInfo(self._GetShowData(self.minor))
1322 if "local_dev" in info:
1323 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1324 backend, meta = devices
1325 if backend.dev_path is None or meta.dev_path is None:
1326 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1329 self._CheckMetaSize(meta.dev_path)
1330 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1332 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1333 self._children = devices
1335 def RemoveChildren(self, devices):
1336 """Detach the drbd device from local storage.
1339 if self.minor is None:
1340 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1342 # early return if we don't actually have backing storage
1343 info = self._GetDevInfo(self._GetShowData(self.minor))
1344 if "local_dev" not in info:
1346 if len(self._children) != 2:
1347 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1349 if self._children.count(None) == 2: # we don't actually have children :)
1350 logging.warning("drbd%d: requested detach while detached", self.minor)
1352 if len(devices) != 2:
1353 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1354 for child, dev in zip(self._children, devices):
1355 if dev != child.dev_path:
1356 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1357 " RemoveChildren", self.minor, dev, child.dev_path)
1359 self._ShutdownLocal(self.minor)
1363 def _SetMinorSyncSpeed(cls, minor, kbytes):
1364 """Set the speed of the DRBD syncer.
1366 This is the low-level implementation.
1369 @param minor: the drbd minor whose settings we change
1371 @param kbytes: the speed in kbytes/second
1373 @return: the success of the operation
1376 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1377 "-r", "%d" % kbytes, "--create-device"])
1379 logging.error("Can't change syncer rate: %s - %s",
1380 result.fail_reason, result.output)
1381 return not result.failed
1383 def SetSyncSpeed(self, kbytes):
1384 """Set the speed of the DRBD syncer.
1387 @param kbytes: the speed in kbytes/second
1389 @return: the success of the operation
1392 if self.minor is None:
1393 logging.info("Not attached during SetSyncSpeed")
1395 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1396 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1398 def GetProcStatus(self):
1399 """Return device data from /proc.
1402 if self.minor is None:
1403 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1404 proc_info = self._MassageProcData(self._GetProcData())
1405 if self.minor not in proc_info:
1406 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1407 return DRBD8Status(proc_info[self.minor])
1409 def GetSyncStatus(self):
1410 """Returns the sync status of the device.
1413 If sync_percent is None, it means all is ok
1414 If estimated_time is None, it means we can't estimate
1415 the time needed, otherwise it's the time left in seconds.
1418 We set the is_degraded parameter to True on two conditions:
1419 network not connected or local disk missing.
1421 We compute the ldisk parameter based on whether we have a local
1424 @rtype: objects.BlockDevStatus
1427 if self.minor is None and not self.Attach():
1428 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1430 stats = self.GetProcStatus()
1431 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1433 if stats.is_disk_uptodate:
1434 ldisk_status = constants.LDS_OKAY
1435 elif stats.is_diskless:
1436 ldisk_status = constants.LDS_FAULTY
1438 ldisk_status = constants.LDS_UNKNOWN
1440 return objects.BlockDevStatus(dev_path=self.dev_path,
1443 sync_percent=stats.sync_percent,
1444 estimated_time=stats.est_time,
1445 is_degraded=is_degraded,
1446 ldisk_status=ldisk_status)
1448 def Open(self, force=False):
1449 """Make the local state primary.
1451 If the 'force' parameter is given, the '-o' option is passed to
1452 drbdsetup. Since this is a potentially dangerous operation, the
1453 force flag should be only given after creation, when it actually
1457 if self.minor is None and not self.Attach():
1458 logging.error("DRBD cannot attach to a device during open")
1460 cmd = ["drbdsetup", self.dev_path, "primary"]
1463 result = utils.RunCmd(cmd)
1465 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1469 """Make the local state secondary.
1471 This will, of course, fail if the device is in use.
1474 if self.minor is None and not self.Attach():
1475 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1476 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1478 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1479 self.minor, result.output)
1481 def DisconnectNet(self):
1482 """Removes network configuration.
1484 This method shutdowns the network side of the device.
1486 The method will wait up to a hardcoded timeout for the device to
1487 go into standalone after the 'disconnect' command before
1488 re-configuring it, as sometimes it takes a while for the
1489 disconnect to actually propagate and thus we might issue a 'net'
1490 command while the device is still connected. If the device will
1491 still be attached to the network and we time out, we raise an
1495 if self.minor is None:
1496 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1498 if None in (self._lhost, self._lport, self._rhost, self._rport):
1499 _ThrowError("drbd%d: DRBD disk missing network info in"
1500 " DisconnectNet()", self.minor)
1502 class _DisconnectStatus:
1503 def __init__(self, ever_disconnected):
1504 self.ever_disconnected = ever_disconnected
1506 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1508 def _WaitForDisconnect():
1509 if self.GetProcStatus().is_standalone:
1512 # retry the disconnect, it seems possible that due to a well-time
1513 # disconnect on the peer, my disconnect command might be ignored and
1515 dstatus.ever_disconnected = \
1516 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1518 raise utils.RetryAgain()
1521 start_time = time.time()
1524 # Start delay at 100 milliseconds and grow up to 2 seconds
1525 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1526 self._NET_RECONFIG_TIMEOUT)
1527 except utils.RetryTimeout:
1528 if dstatus.ever_disconnected:
1529 msg = ("drbd%d: device did not react to the"
1530 " 'disconnect' command in a timely manner")
1532 msg = "drbd%d: can't shutdown network, even after multiple retries"
1534 _ThrowError(msg, self.minor)
1536 reconfig_time = time.time() - start_time
1537 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1538 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1539 self.minor, reconfig_time)
1541 def AttachNet(self, multimaster):
1542 """Reconnects the network.
1544 This method connects the network side of the device with a
1545 specified multi-master flag. The device needs to be 'Standalone'
1546 but have valid network configuration data.
1549 - multimaster: init the network in dual-primary mode
1552 if self.minor is None:
1553 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1555 if None in (self._lhost, self._lport, self._rhost, self._rport):
1556 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1558 status = self.GetProcStatus()
1560 if not status.is_standalone:
1561 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1563 self._AssembleNet(self.minor,
1564 (self._lhost, self._lport, self._rhost, self._rport),
1565 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1566 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1569 """Check if our minor is configured.
1571 This doesn't do any device configurations - it only checks if the
1572 minor is in a state different from Unconfigured.
1574 Note that this function will not change the state of the system in
1575 any way (except in case of side-effects caused by reading from
1579 used_devs = self.GetUsedDevs()
1580 if self._aminor in used_devs:
1581 minor = self._aminor
1585 self._SetFromMinor(minor)
1586 return minor is not None
1589 """Assemble the drbd.
1592 - if we have a configured device, we try to ensure that it matches
1594 - if not, we create it from zero
1597 super(DRBD8, self).Assemble()
1600 if self.minor is None:
1601 # local device completely unconfigured
1602 self._FastAssemble()
1604 # we have to recheck the local and network status and try to fix
1606 self._SlowAssemble()
1608 def _SlowAssemble(self):
1609 """Assembles the DRBD device from a (partially) configured device.
1611 In case of partially attached (local device matches but no network
1612 setup), we perform the network attach. If successful, we re-test
1613 the attach if can return success.
1616 # TODO: Rewrite to not use a for loop just because there is 'break'
1617 # pylint: disable-msg=W0631
1618 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1619 for minor in (self._aminor,):
1620 info = self._GetDevInfo(self._GetShowData(minor))
1621 match_l = self._MatchesLocal(info)
1622 match_r = self._MatchesNet(info)
1624 if match_l and match_r:
1625 # everything matches
1628 if match_l and not match_r and "local_addr" not in info:
1629 # disk matches, but not attached to network, attach and recheck
1630 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1631 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1632 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1635 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1636 " show' disagrees", minor)
1638 if match_r and "local_dev" not in info:
1639 # no local disk, but network attached and it matches
1640 self._AssembleLocal(minor, self._children[0].dev_path,
1641 self._children[1].dev_path, self.size)
1642 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1645 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1646 " show' disagrees", minor)
1648 # this case must be considered only if we actually have local
1649 # storage, i.e. not in diskless mode, because all diskless
1650 # devices are equal from the point of view of local
1652 if (match_l and "local_dev" in info and
1653 not match_r and "local_addr" in info):
1654 # strange case - the device network part points to somewhere
1655 # else, even though its local storage is ours; as we own the
1656 # drbd space, we try to disconnect from the remote peer and
1657 # reconnect to our correct one
1659 self._ShutdownNet(minor)
1660 except errors.BlockDeviceError, err:
1661 _ThrowError("drbd%d: device has correct local storage, wrong"
1662 " remote peer and is unable to disconnect in order"
1663 " to attach to the correct peer: %s", minor, str(err))
1664 # note: _AssembleNet also handles the case when we don't want
1665 # local storage (i.e. one or more of the _[lr](host|port) is
1667 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1668 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1669 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1672 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1673 " show' disagrees", minor)
1678 self._SetFromMinor(minor)
1680 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1683 def _FastAssemble(self):
1684 """Assemble the drbd device from zero.
1686 This is run when in Assemble we detect our minor is unused.
1689 minor = self._aminor
1690 if self._children and self._children[0] and self._children[1]:
1691 self._AssembleLocal(minor, self._children[0].dev_path,
1692 self._children[1].dev_path, self.size)
1693 if self._lhost and self._lport and self._rhost and self._rport:
1694 self._AssembleNet(minor,
1695 (self._lhost, self._lport, self._rhost, self._rport),
1696 constants.DRBD_NET_PROTOCOL,
1697 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1698 self._SetFromMinor(minor)
1701 def _ShutdownLocal(cls, minor):
1702 """Detach from the local device.
1704 I/Os will continue to be served from the remote device. If we
1705 don't have a remote device, this operation will fail.
1708 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1710 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1713 def _ShutdownNet(cls, minor):
1714 """Disconnect from the remote peer.
1716 This fails if we don't have a local device.
1719 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1721 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1724 def _ShutdownAll(cls, minor):
1725 """Deactivate the device.
1727 This will, of course, fail if the device is in use.
1730 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1732 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1733 minor, result.output)
1736 """Shutdown the DRBD device.
1739 if self.minor is None and not self.Attach():
1740 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1744 self.dev_path = None
1745 self._ShutdownAll(minor)
1748 """Stub remove for DRBD devices.
1754 def Create(cls, unique_id, children, size):
1755 """Create a new DRBD8 device.
1757 Since DRBD devices are not created per se, just assembled, this
1758 function only initializes the metadata.
1761 if len(children) != 2:
1762 raise errors.ProgrammerError("Invalid setup for the drbd device")
1763 # check that the minor is unused
1764 aminor = unique_id[4]
1765 proc_info = cls._MassageProcData(cls._GetProcData())
1766 if aminor in proc_info:
1767 status = DRBD8Status(proc_info[aminor])
1768 in_use = status.is_in_use
1772 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1775 if not meta.Attach():
1776 _ThrowError("drbd%d: can't attach to meta device '%s'",
1778 cls._CheckMetaSize(meta.dev_path)
1779 cls._InitMeta(aminor, meta.dev_path)
1780 return cls(unique_id, children, size)
1782 def Grow(self, amount):
1783 """Resize the DRBD device and its backing storage.
1786 if self.minor is None:
1787 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1788 if len(self._children) != 2 or None in self._children:
1789 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1790 self._children[0].Grow(amount)
1791 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1792 "%dm" % (self.size + amount)])
1794 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1797 class FileStorage(BlockDev):
1800 This class represents the a file storage backend device.
1802 The unique_id for the file device is a (file_driver, file_path) tuple.
1805 def __init__(self, unique_id, children, size):
1806 """Initalizes a file device backend.
1810 raise errors.BlockDeviceError("Invalid setup for file device")
1811 super(FileStorage, self).__init__(unique_id, children, size)
1812 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1813 raise ValueError("Invalid configuration data %s" % str(unique_id))
1814 self.driver = unique_id[0]
1815 self.dev_path = unique_id[1]
1819 """Assemble the device.
1821 Checks whether the file device exists, raises BlockDeviceError otherwise.
1824 if not os.path.exists(self.dev_path):
1825 _ThrowError("File device '%s' does not exist" % self.dev_path)
1828 """Shutdown the device.
1830 This is a no-op for the file type, as we don't deactivate
1831 the file on shutdown.
1836 def Open(self, force=False):
1837 """Make the device ready for I/O.
1839 This is a no-op for the file type.
1845 """Notifies that the device will no longer be used for I/O.
1847 This is a no-op for the file type.
1853 """Remove the file backing the block device.
1856 @return: True if the removal was successful
1860 os.remove(self.dev_path)
1861 except OSError, err:
1862 if err.errno != errno.ENOENT:
1863 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1865 def Rename(self, new_id):
1866 """Renames the file.
1869 # TODO: implement rename for file-based storage
1870 _ThrowError("Rename is not supported for file-based storage")
1872 def Grow(self, amount):
1875 @param amount: the amount (in mebibytes) to grow with
1878 # Check that the file exists
1880 current_size = self.GetActualSize()
1881 new_size = current_size + amount * 1024 * 1024
1882 assert new_size > current_size, "Cannot Grow with a negative amount"
1884 f = open(self.dev_path, "a+")
1885 f.truncate(new_size)
1887 except EnvironmentError, err:
1888 _ThrowError("Error in file growth: %", str(err))
1891 """Attach to an existing file.
1893 Check if this file already exists.
1896 @return: True if file exists
1899 self.attached = os.path.exists(self.dev_path)
1900 return self.attached
1902 def GetActualSize(self):
1903 """Return the actual disk size.
1905 @note: the device needs to be active when this is called
1908 assert self.attached, "BlockDevice not attached in GetActualSize()"
1910 st = os.stat(self.dev_path)
1912 except OSError, err:
1913 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1916 def Create(cls, unique_id, children, size):
1917 """Create a new file.
1919 @param size: the size of file in MiB
1921 @rtype: L{bdev.FileStorage}
1922 @return: an instance of FileStorage
1925 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1926 raise ValueError("Invalid configuration data %s" % str(unique_id))
1927 dev_path = unique_id[1]
1929 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
1930 f = os.fdopen(fd, "w")
1931 f.truncate(size * 1024 * 1024)
1933 except EnvironmentError, err:
1934 if err.errno == errno.EEXIST:
1935 _ThrowError("File already existing: %s", dev_path)
1936 _ThrowError("Error in file creation: %", str(err))
1938 return FileStorage(unique_id, children, size)
1942 constants.LD_LV: LogicalVolume,
1943 constants.LD_DRBD8: DRBD8,
1946 if constants.ENABLE_FILE_STORAGE:
1947 DEV_MAP[constants.LD_FILE] = FileStorage
1950 def FindDevice(dev_type, unique_id, children, size):
1951 """Search for an existing, assembled device.
1953 This will succeed only if the device exists and is assembled, but it
1954 does not do any actions in order to activate the device.
1957 if dev_type not in DEV_MAP:
1958 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1959 device = DEV_MAP[dev_type](unique_id, children, size)
1960 if not device.attached:
1965 def Assemble(dev_type, unique_id, children, size):
1966 """Try to attach or assemble an existing device.
1968 This will attach to assemble the device, as needed, to bring it
1969 fully up. It must be safe to run on already-assembled devices.
1972 if dev_type not in DEV_MAP:
1973 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1974 device = DEV_MAP[dev_type](unique_id, children, size)
1979 def Create(dev_type, unique_id, children, size):
1983 if dev_type not in DEV_MAP:
1984 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1985 device = DEV_MAP[dev_type].Create(unique_id, children, size)