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
37 def _IgnoreError(fn, *args, **kwargs):
38 """Executes the given function, ignoring BlockDeviceErrors.
40 This is used in order to simplify the execution of cleanup or
44 @return: True when fn didn't raise an exception, False otherwise
50 except errors.BlockDeviceError, err:
51 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
55 def _ThrowError(msg, *args):
56 """Log an error to the node daemon and the raise an exception.
59 @param msg: the text of the exception
60 @raise errors.BlockDeviceError
66 raise errors.BlockDeviceError(msg)
69 class BlockDev(object):
70 """Block device abstract class.
72 A block device can be in the following states:
73 - not existing on the system, and by `Create()` it goes into:
74 - existing but not setup/not active, and by `Assemble()` goes into:
75 - active read-write and by `Open()` it goes into
76 - online (=used, or ready for use)
78 A device can also be online but read-only, however we are not using
79 the readonly state (LV has it, if needed in the future) and we are
80 usually looking at this like at a stack, so it's easier to
81 conceptualise the transition from not-existing to online and back
84 The many different states of the device are due to the fact that we
85 need to cover many device types:
86 - logical volumes are created, lvchange -a y $lv, and used
87 - drbd devices are attached to a local disk/remote peer and made primary
89 A block device is identified by three items:
90 - the /dev path of the device (dynamic)
91 - a unique ID of the device (static)
92 - it's major/minor pair (dynamic)
94 Not all devices implement both the first two as distinct items. LVM
95 logical volumes have their unique ID (the pair volume group, logical
96 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
97 the /dev path is again dynamic and the unique id is the pair (host1,
100 You can get to a device in two ways:
101 - creating the (real) device, which returns you
102 an attached instance (lvcreate)
103 - attaching of a python instance to an existing (real) device
105 The second point, the attachement to a device, is different
106 depending on whether the device is assembled or not. At init() time,
107 we search for a device with the same unique_id as us. If found,
108 good. It also means that the device is already assembled. If not,
109 after assembly we'll have our correct major/minor.
112 def __init__(self, unique_id, children, size):
113 self._children = children
115 self.unique_id = unique_id
118 self.attached = False
122 """Assemble the device from its components.
124 Implementations of this method by child classes must ensure that:
125 - after the device has been assembled, it knows its major/minor
126 numbers; this allows other devices (usually parents) to probe
127 correctly for their children
128 - calling this method on an existing, in-use device is safe
129 - if the device is already configured (and in an OK state),
130 this method is idempotent
136 """Find a device which matches our config and attach to it.
139 raise NotImplementedError
142 """Notifies that the device will no longer be used for I/O.
145 raise NotImplementedError
148 def Create(cls, unique_id, children, size):
149 """Create the device.
151 If the device cannot be created, it will return None
152 instead. Error messages go to the logging system.
154 Note that for some devices, the unique_id is used, and for other,
155 the children. The idea is that these two, taken together, are
156 enough for both creation and assembly (later).
159 raise NotImplementedError
162 """Remove this device.
164 This makes sense only for some of the device types: LV and file
165 storage. Also note that if the device can't attach, the removal
169 raise NotImplementedError
171 def Rename(self, new_id):
172 """Rename this device.
174 This may or may not make sense for a given device type.
177 raise NotImplementedError
179 def Open(self, force=False):
180 """Make the device ready for use.
182 This makes the device ready for I/O. For now, just the DRBD
185 The force parameter signifies that if the device has any kind of
186 --force thing, it should be used, we know what we are doing.
189 raise NotImplementedError
192 """Shut down the device, freeing its children.
194 This undoes the `Assemble()` work, except for the child
195 assembling; as such, the children on the device are still
196 assembled after this call.
199 raise NotImplementedError
201 def SetSyncSpeed(self, speed):
202 """Adjust the sync speed of the mirror.
204 In case this is not a mirroring device, this is no-op.
209 for child in self._children:
210 result = result and child.SetSyncSpeed(speed)
213 def GetSyncStatus(self):
214 """Returns the sync status of the device.
216 If this device is a mirroring device, this function returns the
217 status of the mirror.
219 If sync_percent is None, it means the device is not syncing.
221 If estimated_time is None, it means we can't estimate
222 the time needed, otherwise it's the time left in seconds.
224 If is_degraded is True, it means the device is missing
225 redundancy. This is usually a sign that something went wrong in
226 the device setup, if sync_percent is None.
228 The ldisk parameter represents the degradation of the local
229 data. This is only valid for some devices, the rest will always
230 return False (not degraded).
232 @rtype: objects.BlockDevStatus
235 return objects.BlockDevStatus(dev_path=self.dev_path,
241 ldisk_status=constants.LDS_OKAY)
243 def CombinedSyncStatus(self):
244 """Calculate the mirror status recursively for our children.
246 The return value is the same as for `GetSyncStatus()` except the
247 minimum percent and maximum time are calculated across our
250 @rtype: objects.BlockDevStatus
253 status = self.GetSyncStatus()
255 min_percent = status.sync_percent
256 max_time = status.estimated_time
257 is_degraded = status.is_degraded
258 ldisk_status = status.ldisk_status
261 for child in self._children:
262 child_status = child.GetSyncStatus()
264 if min_percent is None:
265 min_percent = child_status.sync_percent
266 elif child_status.sync_percent is not None:
267 min_percent = min(min_percent, child_status.sync_percent)
270 max_time = child_status.estimated_time
271 elif child_status.estimated_time is not None:
272 max_time = max(max_time, child_status.estimated_time)
274 is_degraded = is_degraded or child_status.is_degraded
276 if ldisk_status is None:
277 ldisk_status = child_status.ldisk_status
278 elif child_status.ldisk_status is not None:
279 ldisk_status = max(ldisk_status, child_status.ldisk_status)
281 return objects.BlockDevStatus(dev_path=self.dev_path,
284 sync_percent=min_percent,
285 estimated_time=max_time,
286 is_degraded=is_degraded,
287 ldisk_status=ldisk_status)
290 def SetInfo(self, text):
291 """Update metadata with info text.
293 Only supported for some device types.
296 for child in self._children:
299 def Grow(self, amount):
300 """Grow the block device.
302 @param amount: the amount (in mebibytes) to grow with
305 raise NotImplementedError
307 def GetActualSize(self):
308 """Return the actual disk size.
310 @note: the device needs to be active when this is called
313 assert self.attached, "BlockDevice not attached in GetActualSize()"
314 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
316 _ThrowError("blockdev failed (%s): %s",
317 result.fail_reason, result.output)
319 sz = int(result.output.strip())
320 except (ValueError, TypeError), err:
321 _ThrowError("Failed to parse blockdev output: %s", str(err))
325 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
326 (self.__class__, self.unique_id, self._children,
327 self.major, self.minor, self.dev_path))
330 class LogicalVolume(BlockDev):
331 """Logical Volume block device.
334 def __init__(self, unique_id, children, size):
335 """Attaches to a LV device.
337 The unique_id is a tuple (vg_name, lv_name)
340 super(LogicalVolume, self).__init__(unique_id, children, size)
341 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
342 raise ValueError("Invalid configuration data %s" % str(unique_id))
343 self._vg_name, self._lv_name = unique_id
344 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
345 self._degraded = True
346 self.major = self.minor = self.pe_size = self.stripe_count = None
350 def Create(cls, unique_id, children, size):
351 """Create a new logical volume.
354 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
355 raise errors.ProgrammerError("Invalid configuration data %s" %
357 vg_name, lv_name = unique_id
358 pvs_info = cls.GetPVInfo([vg_name])
360 _ThrowError("Can't compute PV info for vg %s", vg_name)
364 pvlist = [ pv[1] for pv in pvs_info ]
365 if utils.any(pvlist, lambda v: ":" in v):
366 _ThrowError("Some of your PVs have invalid character ':'"
368 free_size = sum([ pv[0] for pv in pvs_info ])
369 current_pvs = len(pvlist)
370 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
372 # The size constraint should have been checked from the master before
373 # calling the create function.
375 _ThrowError("Not enough free space: required %s,"
376 " available %s", size, free_size)
377 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
378 # If the free space is not well distributed, we won't be able to
379 # create an optimally-striped volume; in that case, we want to try
380 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
382 for stripes_arg in range(stripes, 0, -1):
383 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
384 if not result.failed:
387 _ThrowError("LV create failed (%s): %s",
388 result.fail_reason, result.output)
389 return LogicalVolume(unique_id, children, size)
392 def GetPVInfo(vg_names, filter_allocatable=True):
393 """Get the free space info for PVs in a volume group.
395 @param vg_names: list of volume group names, if empty all will be returned
396 @param filter_allocatable: whether to skip over unallocatable PVs
399 @return: list of tuples (free_space, name) with free_space in mebibytes
403 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
404 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
405 "--separator=%s" % sep ]
406 result = utils.RunCmd(command)
408 logging.error("Can't get the PV information: %s - %s",
409 result.fail_reason, result.output)
412 for line in result.stdout.splitlines():
413 fields = line.strip().split(sep)
415 logging.error("Can't parse pvs output: line '%s'", line)
417 # (possibly) skip over pvs which are not allocatable
418 if filter_allocatable and fields[3][0] != 'a':
420 # (possibly) skip over pvs which are not in the right volume group(s)
421 if vg_names and fields[1] not in vg_names:
423 data.append((float(fields[2]), fields[0], fields[1]))
428 """Remove this logical volume.
431 if not self.minor and not self.Attach():
432 # the LV does not exist
434 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
435 (self._vg_name, self._lv_name)])
437 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
439 def Rename(self, new_id):
440 """Rename this logical volume.
443 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
444 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
445 new_vg, new_name = new_id
446 if new_vg != self._vg_name:
447 raise errors.ProgrammerError("Can't move a logical volume across"
448 " volume groups (from %s to to %s)" %
449 (self._vg_name, new_vg))
450 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
452 _ThrowError("Failed to rename the logical volume: %s", result.output)
453 self._lv_name = new_name
454 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
457 """Attach to an existing LV.
459 This method will try to see if an existing and active LV exists
460 which matches our name. If so, its major/minor will be
464 self.attached = False
465 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
466 "--units=m", "--nosuffix",
467 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
468 "vg_extent_size,stripes", self.dev_path])
470 logging.error("Can't find LV %s: %s, %s",
471 self.dev_path, result.fail_reason, result.output)
473 # the output can (and will) have multiple lines for multi-segment
474 # LVs, as the 'stripes' parameter is a segment one, so we take
475 # only the last entry, which is the one we're interested in; note
476 # that with LVM2 anyway the 'stripes' value must be constant
477 # across segments, so this is a no-op actually
478 out = result.stdout.splitlines()
479 if not out: # totally empty result? splitlines() returns at least
480 # one line for any non-empty string
481 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
483 out = out[-1].strip().rstrip(',')
486 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
489 status, major, minor, pe_size, stripes = out
491 logging.error("lvs lv_attr is not 6 characters (%s)", status)
497 except ValueError, err:
498 logging.error("lvs major/minor cannot be parsed: %s", str(err))
501 pe_size = int(float(pe_size))
502 except (TypeError, ValueError), err:
503 logging.error("Can't parse vg extent size: %s", err)
507 stripes = int(stripes)
508 except (TypeError, ValueError), err:
509 logging.error("Can't parse the number of stripes: %s", err)
514 self.pe_size = pe_size
515 self.stripe_count = stripes
516 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
522 """Assemble the device.
524 We always run `lvchange -ay` on the LV to ensure it's active before
525 use, as there were cases when xenvg was not active after boot
526 (also possibly after disk issues).
529 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
531 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
534 """Shutdown the device.
536 This is a no-op for the LV device type, as we don't deactivate the
542 def GetSyncStatus(self):
543 """Returns the sync status of the device.
545 If this device is a mirroring device, this function returns the
546 status of the mirror.
548 For logical volumes, sync_percent and estimated_time are always
549 None (no recovery in progress, as we don't handle the mirrored LV
550 case). The is_degraded parameter is the inverse of the ldisk
553 For the ldisk parameter, we check if the logical volume has the
554 'virtual' type, which means it's not backed by existing storage
555 anymore (read from it return I/O error). This happens after a
556 physical disk failure and subsequent 'vgreduce --removemissing' on
559 The status was already read in Attach, so we just return it.
561 @rtype: objects.BlockDevStatus
565 ldisk_status = constants.LDS_FAULTY
567 ldisk_status = constants.LDS_OKAY
569 return objects.BlockDevStatus(dev_path=self.dev_path,
574 is_degraded=self._degraded,
575 ldisk_status=ldisk_status)
577 def Open(self, force=False):
578 """Make the device ready for I/O.
580 This is a no-op for the LV device type.
586 """Notifies that the device will no longer be used for I/O.
588 This is a no-op for the LV device type.
593 def Snapshot(self, size):
594 """Create a snapshot copy of an lvm block device.
597 snap_name = self._lv_name + ".snap"
599 # remove existing snapshot if found
600 snap = LogicalVolume((self._vg_name, snap_name), None, size)
601 _IgnoreError(snap.Remove)
603 pvs_info = self.GetPVInfo([self._vg_name])
605 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
608 free_size, pv_name, _ = pvs_info[0]
610 _ThrowError("Not enough free space: required %s,"
611 " available %s", size, free_size)
613 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
614 "-n%s" % snap_name, self.dev_path])
616 _ThrowError("command: %s error: %s - %s",
617 result.cmd, result.fail_reason, result.output)
621 def SetInfo(self, text):
622 """Update metadata with info text.
625 BlockDev.SetInfo(self, text)
627 # Replace invalid characters
628 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
629 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
631 # Only up to 128 characters are allowed
634 result = utils.RunCmd(["lvchange", "--addtag", text,
637 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
640 def Grow(self, amount):
641 """Grow the logical volume.
644 if self.pe_size is None or self.stripe_count is None:
645 if not self.Attach():
646 _ThrowError("Can't attach to LV during Grow()")
647 full_stripe_size = self.pe_size * self.stripe_count
648 rest = amount % full_stripe_size
650 amount += full_stripe_size - rest
651 # we try multiple algorithms since the 'best' ones might not have
652 # space available in the right place, but later ones might (since
653 # they have less constraints); also note that only recent LVM
655 for alloc_policy in "contiguous", "cling", "normal":
656 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
657 "-L", "+%dm" % amount, self.dev_path])
658 if not result.failed:
660 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
663 class DRBD8Status(object):
664 """A DRBD status representation class.
666 Note that this doesn't support unconfigured devices (cs:Unconfigured).
669 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
670 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
671 "\s+ds:([^/]+)/(\S+)\s+.*$")
672 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
673 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
675 CS_UNCONFIGURED = "Unconfigured"
676 CS_STANDALONE = "StandAlone"
677 CS_WFCONNECTION = "WFConnection"
678 CS_WFREPORTPARAMS = "WFReportParams"
679 CS_CONNECTED = "Connected"
680 CS_STARTINGSYNCS = "StartingSyncS"
681 CS_STARTINGSYNCT = "StartingSyncT"
682 CS_WFBITMAPS = "WFBitMapS"
683 CS_WFBITMAPT = "WFBitMapT"
684 CS_WFSYNCUUID = "WFSyncUUID"
685 CS_SYNCSOURCE = "SyncSource"
686 CS_SYNCTARGET = "SyncTarget"
687 CS_PAUSEDSYNCS = "PausedSyncS"
688 CS_PAUSEDSYNCT = "PausedSyncT"
689 CSET_SYNC = frozenset([
702 DS_DISKLESS = "Diskless"
703 DS_ATTACHING = "Attaching" # transient state
704 DS_FAILED = "Failed" # transient state, next: diskless
705 DS_NEGOTIATING = "Negotiating" # transient state
706 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
707 DS_OUTDATED = "Outdated"
708 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
709 DS_CONSISTENT = "Consistent"
710 DS_UPTODATE = "UpToDate" # normal state
712 RO_PRIMARY = "Primary"
713 RO_SECONDARY = "Secondary"
714 RO_UNKNOWN = "Unknown"
716 def __init__(self, procline):
717 u = self.UNCONF_RE.match(procline)
719 self.cstatus = self.CS_UNCONFIGURED
720 self.lrole = self.rrole = self.ldisk = self.rdisk = None
722 m = self.LINE_RE.match(procline)
724 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
725 self.cstatus = m.group(1)
726 self.lrole = m.group(2)
727 self.rrole = m.group(3)
728 self.ldisk = m.group(4)
729 self.rdisk = m.group(5)
731 # end reading of data from the LINE_RE or UNCONF_RE
733 self.is_standalone = self.cstatus == self.CS_STANDALONE
734 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
735 self.is_connected = self.cstatus == self.CS_CONNECTED
736 self.is_primary = self.lrole == self.RO_PRIMARY
737 self.is_secondary = self.lrole == self.RO_SECONDARY
738 self.peer_primary = self.rrole == self.RO_PRIMARY
739 self.peer_secondary = self.rrole == self.RO_SECONDARY
740 self.both_primary = self.is_primary and self.peer_primary
741 self.both_secondary = self.is_secondary and self.peer_secondary
743 self.is_diskless = self.ldisk == self.DS_DISKLESS
744 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
746 self.is_in_resync = self.cstatus in self.CSET_SYNC
747 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
749 m = self.SYNC_RE.match(procline)
751 self.sync_percent = float(m.group(1))
752 hours = int(m.group(2))
753 minutes = int(m.group(3))
754 seconds = int(m.group(4))
755 self.est_time = hours * 3600 + minutes * 60 + seconds
757 # we have (in this if branch) no percent information, but if
758 # we're resyncing we need to 'fake' a sync percent information,
759 # as this is how cmdlib determines if it makes sense to wait for
761 if self.is_in_resync:
762 self.sync_percent = 0
764 self.sync_percent = None
768 class BaseDRBD(BlockDev):
771 This class contains a few bits of common functionality between the
772 0.7 and 8.x versions of DRBD.
775 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
776 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
779 _ST_UNCONFIGURED = "Unconfigured"
780 _ST_WFCONNECTION = "WFConnection"
781 _ST_CONNECTED = "Connected"
783 _STATUS_FILE = "/proc/drbd"
786 def _GetProcData(filename=_STATUS_FILE):
787 """Return data from /proc/drbd.
791 data = utils.ReadFile(filename).splitlines()
792 except EnvironmentError, err:
793 if err.errno == errno.ENOENT:
794 _ThrowError("The file %s cannot be opened, check if the module"
795 " is loaded (%s)", filename, str(err))
797 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
799 _ThrowError("Can't read any data from %s", filename)
803 def _MassageProcData(data):
804 """Transform the output of _GetProdData into a nicer form.
806 @return: a dictionary of minor: joined lines from /proc/drbd
810 lmatch = re.compile("^ *([0-9]+):.*$")
812 old_minor = old_line = None
814 if not line: # completely empty lines, as can be returned by drbd8.0+
816 lresult = lmatch.match(line)
817 if lresult is not None:
818 if old_minor is not None:
819 results[old_minor] = old_line
820 old_minor = int(lresult.group(1))
823 if old_minor is not None:
824 old_line += " " + line.strip()
826 if old_minor is not None:
827 results[old_minor] = old_line
831 def _GetVersion(cls):
832 """Return the DRBD version.
834 This will return a dict with keys:
840 - proto2 (only on drbd > 8.2.X)
843 proc_data = cls._GetProcData()
844 first_line = proc_data[0].strip()
845 version = cls._VERSION_RE.match(first_line)
847 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
850 values = version.groups()
851 retval = {'k_major': int(values[0]),
852 'k_minor': int(values[1]),
853 'k_point': int(values[2]),
854 'api': int(values[3]),
855 'proto': int(values[4]),
857 if values[5] is not None:
858 retval['proto2'] = values[5]
864 """Return the path to a drbd device for a given minor.
867 return "/dev/drbd%d" % minor
870 def GetUsedDevs(cls):
871 """Compute the list of used DRBD devices.
874 data = cls._GetProcData()
877 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
879 match = valid_line.match(line)
882 minor = int(match.group(1))
883 state = match.group(2)
884 if state == cls._ST_UNCONFIGURED:
886 used_devs[minor] = state, line
890 def _SetFromMinor(self, minor):
891 """Set our parameters based on the given minor.
893 This sets our minor variable and our dev_path.
897 self.minor = self.dev_path = None
898 self.attached = False
901 self.dev_path = self._DevPath(minor)
905 def _CheckMetaSize(meta_device):
906 """Check if the given meta device looks like a valid one.
908 This currently only check the size, which must be around
912 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
914 _ThrowError("Failed to get device size: %s - %s",
915 result.fail_reason, result.output)
917 sectors = int(result.stdout)
919 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
920 bytes = sectors * 512
921 if bytes < 128 * 1024 * 1024: # less than 128MiB
922 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
923 # the maximum *valid* size of the meta device when living on top
924 # of LVM is hard to compute: it depends on the number of stripes
925 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
926 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
927 # size meta device; as such, we restrict it to 1GB (a little bit
928 # too generous, but making assumptions about PE size is hard)
929 if bytes > 1024 * 1024 * 1024:
930 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
932 def Rename(self, new_id):
935 This is not supported for drbd devices.
938 raise errors.ProgrammerError("Can't rename a drbd device")
941 class DRBD8(BaseDRBD):
942 """DRBD v8.x block device.
944 This implements the local host part of the DRBD device, i.e. it
945 doesn't do anything to the supposed peer. If you need a fully
946 connected DRBD pair, you need to use this class on both hosts.
948 The unique_id for the drbd device is the (local_ip, local_port,
949 remote_ip, remote_port) tuple, and it must have two children: the
950 data device and the meta_device. The meta device is checked for
951 valid size and is zeroed on create.
958 _NET_RECONFIG_TIMEOUT = 60
960 def __init__(self, unique_id, children, size):
961 if children and children.count(None) > 0:
963 super(DRBD8, self).__init__(unique_id, children, size)
964 self.major = self._DRBD_MAJOR
965 version = self._GetVersion()
966 if version['k_major'] != 8 :
967 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
968 " usage: kernel is %s.%s, ganeti wants 8.x",
969 version['k_major'], version['k_minor'])
971 if len(children) not in (0, 2):
972 raise ValueError("Invalid configuration data %s" % str(children))
973 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
974 raise ValueError("Invalid configuration data %s" % str(unique_id))
975 (self._lhost, self._lport,
976 self._rhost, self._rport,
977 self._aminor, self._secret) = unique_id
978 if (self._lhost is not None and self._lhost == self._rhost and
979 self._lport == self._rport):
980 raise ValueError("Invalid configuration data, same local/remote %s" %
985 def _InitMeta(cls, minor, dev_path):
986 """Initialize a meta device.
988 This will not work if the given minor is in use.
991 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
992 "v08", dev_path, "0", "create-md"])
994 _ThrowError("Can't initialize meta device: %s", result.output)
997 def _FindUnusedMinor(cls):
998 """Find an unused DRBD device.
1000 This is specific to 8.x as the minors are allocated dynamically,
1001 so non-existing numbers up to a max minor count are actually free.
1004 data = cls._GetProcData()
1006 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1007 used_line = re.compile("^ *([0-9]+): cs:")
1010 match = unused_line.match(line)
1012 return int(match.group(1))
1013 match = used_line.match(line)
1015 minor = int(match.group(1))
1016 highest = max(highest, minor)
1017 if highest is None: # there are no minors in use at all
1019 if highest >= cls._MAX_MINORS:
1020 logging.error("Error: no free drbd minors!")
1021 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1025 def _GetShowParser(cls):
1026 """Return a parser for `drbd show` output.
1028 This will either create or return an already-create parser for the
1029 output of the command `drbd show`.
1032 if cls._PARSE_SHOW is not None:
1033 return cls._PARSE_SHOW
1036 lbrace = pyp.Literal("{").suppress()
1037 rbrace = pyp.Literal("}").suppress()
1038 semi = pyp.Literal(";").suppress()
1039 # this also converts the value to an int
1040 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1042 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1043 defa = pyp.Literal("_is_default").suppress()
1044 dbl_quote = pyp.Literal('"').suppress()
1046 keyword = pyp.Word(pyp.alphanums + '-')
1049 value = pyp.Word(pyp.alphanums + '_-/.:')
1050 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1051 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1052 pyp.Optional(pyp.Literal("ipv6")).suppress())
1053 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1054 pyp.Literal(':').suppress() + number)
1055 # meta device, extended syntax
1056 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1057 number + pyp.Word(']').suppress())
1058 # device name, extended syntax
1059 device_value = pyp.Literal("minor").suppress() + number
1062 stmt = (~rbrace + keyword + ~lbrace +
1063 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1065 pyp.Optional(defa) + semi +
1066 pyp.Optional(pyp.restOfLine).suppress())
1069 section_name = pyp.Word(pyp.alphas + '_')
1070 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1072 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1075 cls._PARSE_SHOW = bnf
1080 def _GetShowData(cls, minor):
1081 """Return the `drbdsetup show` data for a minor.
1084 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1086 logging.error("Can't display the drbd config: %s - %s",
1087 result.fail_reason, result.output)
1089 return result.stdout
1092 def _GetDevInfo(cls, out):
1093 """Parse details about a given DRBD minor.
1095 This return, if available, the local backing device (as a path)
1096 and the local and remote (ip, port) information from a string
1097 containing the output of the `drbdsetup show` command as returned
1105 bnf = cls._GetShowParser()
1109 results = bnf.parseString(out)
1110 except pyp.ParseException, err:
1111 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1113 # and massage the results into our desired format
1114 for section in results:
1116 if sname == "_this_host":
1117 for lst in section[1:]:
1118 if lst[0] == "disk":
1119 data["local_dev"] = lst[1]
1120 elif lst[0] == "meta-disk":
1121 data["meta_dev"] = lst[1]
1122 data["meta_index"] = lst[2]
1123 elif lst[0] == "address":
1124 data["local_addr"] = tuple(lst[1:])
1125 elif sname == "_remote_host":
1126 for lst in section[1:]:
1127 if lst[0] == "address":
1128 data["remote_addr"] = tuple(lst[1:])
1131 def _MatchesLocal(self, info):
1132 """Test if our local config matches with an existing device.
1134 The parameter should be as returned from `_GetDevInfo()`. This
1135 method tests if our local backing device is the same as the one in
1136 the info parameter, in effect testing if we look like the given
1141 backend, meta = self._children
1143 backend = meta = None
1145 if backend is not None:
1146 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1148 retval = ("local_dev" not in info)
1150 if meta is not None:
1151 retval = retval and ("meta_dev" in info and
1152 info["meta_dev"] == meta.dev_path)
1153 retval = retval and ("meta_index" in info and
1154 info["meta_index"] == 0)
1156 retval = retval and ("meta_dev" not in info and
1157 "meta_index" not in info)
1160 def _MatchesNet(self, info):
1161 """Test if our network config matches with an existing device.
1163 The parameter should be as returned from `_GetDevInfo()`. This
1164 method tests if our network configuration is the same as the one
1165 in the info parameter, in effect testing if we look like the given
1169 if (((self._lhost is None and not ("local_addr" in info)) and
1170 (self._rhost is None and not ("remote_addr" in info)))):
1173 if self._lhost is None:
1176 if not ("local_addr" in info and
1177 "remote_addr" in info):
1180 retval = (info["local_addr"] == (self._lhost, self._lport))
1181 retval = (retval and
1182 info["remote_addr"] == (self._rhost, self._rport))
1186 def _AssembleLocal(cls, minor, backend, meta, size):
1187 """Configure the local part of a DRBD device.
1190 args = ["drbdsetup", cls._DevPath(minor), "disk",
1195 args.extend(["-d", "%sm" % size])
1196 result = utils.RunCmd(args)
1198 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1201 def _AssembleNet(cls, minor, net_info, protocol,
1202 dual_pri=False, hmac=None, secret=None):
1203 """Configure the network part of the device.
1206 lhost, lport, rhost, rport = net_info
1207 if None in net_info:
1208 # we don't want network connection and actually want to make
1210 cls._ShutdownNet(minor)
1213 # Workaround for a race condition. When DRBD is doing its dance to
1214 # establish a connection with its peer, it also sends the
1215 # synchronization speed over the wire. In some cases setting the
1216 # sync speed only after setting up both sides can race with DRBD
1217 # connecting, hence we set it here before telling DRBD anything
1219 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1221 args = ["drbdsetup", cls._DevPath(minor), "net",
1222 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1223 "-A", "discard-zero-changes",
1230 args.extend(["-a", hmac, "-x", secret])
1231 result = utils.RunCmd(args)
1233 _ThrowError("drbd%d: can't setup network: %s - %s",
1234 minor, result.fail_reason, result.output)
1236 def _CheckNetworkConfig():
1237 info = cls._GetDevInfo(cls._GetShowData(minor))
1238 if not "local_addr" in info or not "remote_addr" in info:
1239 raise utils.RetryAgain()
1241 if (info["local_addr"] != (lhost, lport) or
1242 info["remote_addr"] != (rhost, rport)):
1243 raise utils.RetryAgain()
1246 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1247 except utils.RetryTimeout:
1248 _ThrowError("drbd%d: timeout while configuring network", minor)
1250 def AddChildren(self, devices):
1251 """Add a disk to the DRBD device.
1254 if self.minor is None:
1255 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1257 if len(devices) != 2:
1258 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1259 info = self._GetDevInfo(self._GetShowData(self.minor))
1260 if "local_dev" in info:
1261 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1262 backend, meta = devices
1263 if backend.dev_path is None or meta.dev_path is None:
1264 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1267 self._CheckMetaSize(meta.dev_path)
1268 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1270 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1271 self._children = devices
1273 def RemoveChildren(self, devices):
1274 """Detach the drbd device from local storage.
1277 if self.minor is None:
1278 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1280 # early return if we don't actually have backing storage
1281 info = self._GetDevInfo(self._GetShowData(self.minor))
1282 if "local_dev" not in info:
1284 if len(self._children) != 2:
1285 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1287 if self._children.count(None) == 2: # we don't actually have children :)
1288 logging.warning("drbd%d: requested detach while detached", self.minor)
1290 if len(devices) != 2:
1291 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1292 for child, dev in zip(self._children, devices):
1293 if dev != child.dev_path:
1294 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1295 " RemoveChildren", self.minor, dev, child.dev_path)
1297 self._ShutdownLocal(self.minor)
1301 def _SetMinorSyncSpeed(cls, minor, kbytes):
1302 """Set the speed of the DRBD syncer.
1304 This is the low-level implementation.
1307 @param minor: the drbd minor whose settings we change
1309 @param kbytes: the speed in kbytes/second
1311 @return: the success of the operation
1314 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1315 "-r", "%d" % kbytes, "--create-device"])
1317 logging.error("Can't change syncer rate: %s - %s",
1318 result.fail_reason, result.output)
1319 return not result.failed
1321 def SetSyncSpeed(self, kbytes):
1322 """Set the speed of the DRBD syncer.
1325 @param kbytes: the speed in kbytes/second
1327 @return: the success of the operation
1330 if self.minor is None:
1331 logging.info("Not attached during SetSyncSpeed")
1333 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1334 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1336 def GetProcStatus(self):
1337 """Return device data from /proc.
1340 if self.minor is None:
1341 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1342 proc_info = self._MassageProcData(self._GetProcData())
1343 if self.minor not in proc_info:
1344 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1345 return DRBD8Status(proc_info[self.minor])
1347 def GetSyncStatus(self):
1348 """Returns the sync status of the device.
1351 If sync_percent is None, it means all is ok
1352 If estimated_time is None, it means we can't estimate
1353 the time needed, otherwise it's the time left in seconds.
1356 We set the is_degraded parameter to True on two conditions:
1357 network not connected or local disk missing.
1359 We compute the ldisk parameter based on whether we have a local
1362 @rtype: objects.BlockDevStatus
1365 if self.minor is None and not self.Attach():
1366 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1368 stats = self.GetProcStatus()
1369 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1371 if stats.is_disk_uptodate:
1372 ldisk_status = constants.LDS_OKAY
1373 elif stats.is_diskless:
1374 ldisk_status = constants.LDS_FAULTY
1376 ldisk_status = constants.LDS_UNKNOWN
1378 return objects.BlockDevStatus(dev_path=self.dev_path,
1381 sync_percent=stats.sync_percent,
1382 estimated_time=stats.est_time,
1383 is_degraded=is_degraded,
1384 ldisk_status=ldisk_status)
1386 def Open(self, force=False):
1387 """Make the local state primary.
1389 If the 'force' parameter is given, the '-o' option is passed to
1390 drbdsetup. Since this is a potentially dangerous operation, the
1391 force flag should be only given after creation, when it actually
1395 if self.minor is None and not self.Attach():
1396 logging.error("DRBD cannot attach to a device during open")
1398 cmd = ["drbdsetup", self.dev_path, "primary"]
1401 result = utils.RunCmd(cmd)
1403 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1407 """Make the local state secondary.
1409 This will, of course, fail if the device is in use.
1412 if self.minor is None and not self.Attach():
1413 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1414 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1416 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1417 self.minor, result.output)
1419 def DisconnectNet(self):
1420 """Removes network configuration.
1422 This method shutdowns the network side of the device.
1424 The method will wait up to a hardcoded timeout for the device to
1425 go into standalone after the 'disconnect' command before
1426 re-configuring it, as sometimes it takes a while for the
1427 disconnect to actually propagate and thus we might issue a 'net'
1428 command while the device is still connected. If the device will
1429 still be attached to the network and we time out, we raise an
1433 if self.minor is None:
1434 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1436 if None in (self._lhost, self._lport, self._rhost, self._rport):
1437 _ThrowError("drbd%d: DRBD disk missing network info in"
1438 " DisconnectNet()", self.minor)
1440 class _DisconnectStatus:
1441 def __init__(self, ever_disconnected):
1442 self.ever_disconnected = ever_disconnected
1444 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1446 def _WaitForDisconnect():
1447 if self.GetProcStatus().is_standalone:
1450 # retry the disconnect, it seems possible that due to a well-time
1451 # disconnect on the peer, my disconnect command might be ignored and
1453 dstatus.ever_disconnected = \
1454 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1456 raise utils.RetryAgain()
1459 start_time = time.time()
1462 # Start delay at 100 milliseconds and grow up to 2 seconds
1463 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1464 self._NET_RECONFIG_TIMEOUT)
1465 except utils.RetryTimeout:
1466 if dstatus.ever_disconnected:
1467 msg = ("drbd%d: device did not react to the"
1468 " 'disconnect' command in a timely manner")
1470 msg = "drbd%d: can't shutdown network, even after multiple retries"
1472 _ThrowError(msg, self.minor)
1474 reconfig_time = time.time() - start_time
1475 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1476 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1477 self.minor, reconfig_time)
1479 def AttachNet(self, multimaster):
1480 """Reconnects the network.
1482 This method connects the network side of the device with a
1483 specified multi-master flag. The device needs to be 'Standalone'
1484 but have valid network configuration data.
1487 - multimaster: init the network in dual-primary mode
1490 if self.minor is None:
1491 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1493 if None in (self._lhost, self._lport, self._rhost, self._rport):
1494 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1496 status = self.GetProcStatus()
1498 if not status.is_standalone:
1499 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1501 self._AssembleNet(self.minor,
1502 (self._lhost, self._lport, self._rhost, self._rport),
1503 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1504 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1507 """Check if our minor is configured.
1509 This doesn't do any device configurations - it only checks if the
1510 minor is in a state different from Unconfigured.
1512 Note that this function will not change the state of the system in
1513 any way (except in case of side-effects caused by reading from
1517 used_devs = self.GetUsedDevs()
1518 if self._aminor in used_devs:
1519 minor = self._aminor
1523 self._SetFromMinor(minor)
1524 return minor is not None
1527 """Assemble the drbd.
1530 - if we have a configured device, we try to ensure that it matches
1532 - if not, we create it from zero
1535 super(DRBD8, self).Assemble()
1538 if self.minor is None:
1539 # local device completely unconfigured
1540 self._FastAssemble()
1542 # we have to recheck the local and network status and try to fix
1544 self._SlowAssemble()
1546 def _SlowAssemble(self):
1547 """Assembles the DRBD device from a (partially) configured device.
1549 In case of partially attached (local device matches but no network
1550 setup), we perform the network attach. If successful, we re-test
1551 the attach if can return success.
1554 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1555 for minor in (self._aminor,):
1556 info = self._GetDevInfo(self._GetShowData(minor))
1557 match_l = self._MatchesLocal(info)
1558 match_r = self._MatchesNet(info)
1560 if match_l and match_r:
1561 # everything matches
1564 if match_l and not match_r and "local_addr" not in info:
1565 # disk matches, but not attached to network, attach and recheck
1566 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1567 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1568 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1571 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1572 " show' disagrees", minor)
1574 if match_r and "local_dev" not in info:
1575 # no local disk, but network attached and it matches
1576 self._AssembleLocal(minor, self._children[0].dev_path,
1577 self._children[1].dev_path, self.size)
1578 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1581 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1582 " show' disagrees", minor)
1584 # this case must be considered only if we actually have local
1585 # storage, i.e. not in diskless mode, because all diskless
1586 # devices are equal from the point of view of local
1588 if (match_l and "local_dev" in info and
1589 not match_r and "local_addr" in info):
1590 # strange case - the device network part points to somewhere
1591 # else, even though its local storage is ours; as we own the
1592 # drbd space, we try to disconnect from the remote peer and
1593 # reconnect to our correct one
1595 self._ShutdownNet(minor)
1596 except errors.BlockDeviceError, err:
1597 _ThrowError("drbd%d: device has correct local storage, wrong"
1598 " remote peer and is unable to disconnect in order"
1599 " to attach to the correct peer: %s", minor, str(err))
1600 # note: _AssembleNet also handles the case when we don't want
1601 # local storage (i.e. one or more of the _[lr](host|port) is
1603 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1604 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1605 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1608 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1609 " show' disagrees", minor)
1614 self._SetFromMinor(minor)
1616 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1619 def _FastAssemble(self):
1620 """Assemble the drbd device from zero.
1622 This is run when in Assemble we detect our minor is unused.
1625 minor = self._aminor
1626 if self._children and self._children[0] and self._children[1]:
1627 self._AssembleLocal(minor, self._children[0].dev_path,
1628 self._children[1].dev_path, self.size)
1629 if self._lhost and self._lport and self._rhost and self._rport:
1630 self._AssembleNet(minor,
1631 (self._lhost, self._lport, self._rhost, self._rport),
1632 constants.DRBD_NET_PROTOCOL,
1633 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1634 self._SetFromMinor(minor)
1637 def _ShutdownLocal(cls, minor):
1638 """Detach from the local device.
1640 I/Os will continue to be served from the remote device. If we
1641 don't have a remote device, this operation will fail.
1644 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1646 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1649 def _ShutdownNet(cls, minor):
1650 """Disconnect from the remote peer.
1652 This fails if we don't have a local device.
1655 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1657 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1660 def _ShutdownAll(cls, minor):
1661 """Deactivate the device.
1663 This will, of course, fail if the device is in use.
1666 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1668 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1669 minor, result.output)
1672 """Shutdown the DRBD device.
1675 if self.minor is None and not self.Attach():
1676 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1680 self.dev_path = None
1681 self._ShutdownAll(minor)
1684 """Stub remove for DRBD devices.
1690 def Create(cls, unique_id, children, size):
1691 """Create a new DRBD8 device.
1693 Since DRBD devices are not created per se, just assembled, this
1694 function only initializes the metadata.
1697 if len(children) != 2:
1698 raise errors.ProgrammerError("Invalid setup for the drbd device")
1699 # check that the minor is unused
1700 aminor = unique_id[4]
1701 proc_info = cls._MassageProcData(cls._GetProcData())
1702 if aminor in proc_info:
1703 status = DRBD8Status(proc_info[aminor])
1704 in_use = status.is_in_use
1708 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1711 if not meta.Attach():
1712 _ThrowError("drbd%d: can't attach to meta device '%s'",
1714 cls._CheckMetaSize(meta.dev_path)
1715 cls._InitMeta(aminor, meta.dev_path)
1716 return cls(unique_id, children, size)
1718 def Grow(self, amount):
1719 """Resize the DRBD device and its backing storage.
1722 if self.minor is None:
1723 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1724 if len(self._children) != 2 or None in self._children:
1725 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1726 self._children[0].Grow(amount)
1727 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1728 "%dm" % (self.size + amount)])
1730 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1733 class FileStorage(BlockDev):
1736 This class represents the a file storage backend device.
1738 The unique_id for the file device is a (file_driver, file_path) tuple.
1741 def __init__(self, unique_id, children, size):
1742 """Initalizes a file device backend.
1746 raise errors.BlockDeviceError("Invalid setup for file device")
1747 super(FileStorage, self).__init__(unique_id, children, size)
1748 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1749 raise ValueError("Invalid configuration data %s" % str(unique_id))
1750 self.driver = unique_id[0]
1751 self.dev_path = unique_id[1]
1755 """Assemble the device.
1757 Checks whether the file device exists, raises BlockDeviceError otherwise.
1760 if not os.path.exists(self.dev_path):
1761 _ThrowError("File device '%s' does not exist" % self.dev_path)
1764 """Shutdown the device.
1766 This is a no-op for the file type, as we don't deactivate
1767 the file on shutdown.
1772 def Open(self, force=False):
1773 """Make the device ready for I/O.
1775 This is a no-op for the file type.
1781 """Notifies that the device will no longer be used for I/O.
1783 This is a no-op for the file type.
1789 """Remove the file backing the block device.
1792 @return: True if the removal was successful
1796 os.remove(self.dev_path)
1797 except OSError, err:
1798 if err.errno != errno.ENOENT:
1799 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1802 """Attach to an existing file.
1804 Check if this file already exists.
1807 @return: True if file exists
1810 self.attached = os.path.exists(self.dev_path)
1811 return self.attached
1813 def GetActualSize(self):
1814 """Return the actual disk size.
1816 @note: the device needs to be active when this is called
1819 assert self.attached, "BlockDevice not attached in GetActualSize()"
1821 st = os.stat(self.dev_path)
1823 except OSError, err:
1824 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1827 def Create(cls, unique_id, children, size):
1828 """Create a new file.
1830 @param size: the size of file in MiB
1832 @rtype: L{bdev.FileStorage}
1833 @return: an instance of FileStorage
1836 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1837 raise ValueError("Invalid configuration data %s" % str(unique_id))
1838 dev_path = unique_id[1]
1839 if os.path.exists(dev_path):
1840 _ThrowError("File already existing: %s", dev_path)
1842 f = open(dev_path, 'w')
1843 f.truncate(size * 1024 * 1024)
1845 except IOError, err:
1846 _ThrowError("Error in file creation: %", str(err))
1848 return FileStorage(unique_id, children, size)
1852 constants.LD_LV: LogicalVolume,
1853 constants.LD_DRBD8: DRBD8,
1854 constants.LD_FILE: FileStorage,
1858 def FindDevice(dev_type, unique_id, children, size):
1859 """Search for an existing, assembled device.
1861 This will succeed only if the device exists and is assembled, but it
1862 does not do any actions in order to activate the device.
1865 if dev_type not in DEV_MAP:
1866 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1867 device = DEV_MAP[dev_type](unique_id, children, size)
1868 if not device.attached:
1873 def Assemble(dev_type, unique_id, children, size):
1874 """Try to attach or assemble an existing device.
1876 This will attach to assemble the device, as needed, to bring it
1877 fully up. It must be safe to run on already-assembled devices.
1880 if dev_type not in DEV_MAP:
1881 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1882 device = DEV_MAP[dev_type](unique_id, children, size)
1887 def Create(dev_type, unique_id, children, size):
1891 if dev_type not in DEV_MAP:
1892 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1893 device = DEV_MAP[dev_type].Create(unique_id, children, size)