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 free_size = sum([ pv[0] for pv in pvs_info ])
366 current_pvs = len(pvlist)
367 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
369 # The size constraint should have been checked from the master before
370 # calling the create function.
372 _ThrowError("Not enough free space: required %s,"
373 " available %s", size, free_size)
374 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
375 # If the free space is not well distributed, we won't be able to
376 # create an optimally-striped volume; in that case, we want to try
377 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
379 for stripes_arg in range(stripes, 0, -1):
380 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
381 if not result.failed:
384 _ThrowError("LV create failed (%s): %s",
385 result.fail_reason, result.output)
386 return LogicalVolume(unique_id, children, size)
389 def GetPVInfo(vg_name):
390 """Get the free space info for PVs in a volume group.
392 @param vg_name: the volume group name
395 @return: list of tuples (free_space, name) with free_space in mebibytes
398 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
399 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
401 result = utils.RunCmd(command)
403 logging.error("Can't get the PV information: %s - %s",
404 result.fail_reason, result.output)
407 for line in result.stdout.splitlines():
408 fields = line.strip().split(':')
410 logging.error("Can't parse pvs output: line '%s'", line)
412 # skip over pvs from another vg or ones which are not allocatable
413 if fields[1] != vg_name or fields[3][0] != 'a':
415 data.append((float(fields[2]), fields[0]))
420 """Remove this logical volume.
423 if not self.minor and not self.Attach():
424 # the LV does not exist
426 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
427 (self._vg_name, self._lv_name)])
429 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
431 def Rename(self, new_id):
432 """Rename this logical volume.
435 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
436 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
437 new_vg, new_name = new_id
438 if new_vg != self._vg_name:
439 raise errors.ProgrammerError("Can't move a logical volume across"
440 " volume groups (from %s to to %s)" %
441 (self._vg_name, new_vg))
442 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
444 _ThrowError("Failed to rename the logical volume: %s", result.output)
445 self._lv_name = new_name
446 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
449 """Attach to an existing LV.
451 This method will try to see if an existing and active LV exists
452 which matches our name. If so, its major/minor will be
456 self.attached = False
457 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
458 "--units=m", "--nosuffix",
459 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
460 "vg_extent_size,stripes", self.dev_path])
462 logging.error("Can't find LV %s: %s, %s",
463 self.dev_path, result.fail_reason, result.output)
465 # the output can (and will) have multiple lines for multi-segment
466 # LVs, as the 'stripes' parameter is a segment one, so we take
467 # only the last entry, which is the one we're interested in; note
468 # that with LVM2 anyway the 'stripes' value must be constant
469 # across segments, so this is a no-op actually
470 out = result.stdout.splitlines()
471 if not out: # totally empty result? splitlines() returns at least
472 # one line for any non-empty string
473 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
475 out = out[-1].strip().rstrip(',')
478 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
481 status, major, minor, pe_size, stripes = out
483 logging.error("lvs lv_attr is not 6 characters (%s)", status)
489 except ValueError, err:
490 logging.error("lvs major/minor cannot be parsed: %s", str(err))
493 pe_size = int(float(pe_size))
494 except (TypeError, ValueError), err:
495 logging.error("Can't parse vg extent size: %s", err)
499 stripes = int(stripes)
500 except (TypeError, ValueError), err:
501 logging.error("Can't parse the number of stripes: %s", err)
506 self.pe_size = pe_size
507 self.stripe_count = stripes
508 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
514 """Assemble the device.
516 We always run `lvchange -ay` on the LV to ensure it's active before
517 use, as there were cases when xenvg was not active after boot
518 (also possibly after disk issues).
521 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
523 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
526 """Shutdown the device.
528 This is a no-op for the LV device type, as we don't deactivate the
534 def GetSyncStatus(self):
535 """Returns the sync status of the device.
537 If this device is a mirroring device, this function returns the
538 status of the mirror.
540 For logical volumes, sync_percent and estimated_time are always
541 None (no recovery in progress, as we don't handle the mirrored LV
542 case). The is_degraded parameter is the inverse of the ldisk
545 For the ldisk parameter, we check if the logical volume has the
546 'virtual' type, which means it's not backed by existing storage
547 anymore (read from it return I/O error). This happens after a
548 physical disk failure and subsequent 'vgreduce --removemissing' on
551 The status was already read in Attach, so we just return it.
553 @rtype: objects.BlockDevStatus
557 ldisk_status = constants.LDS_FAULTY
559 ldisk_status = constants.LDS_OKAY
561 return objects.BlockDevStatus(dev_path=self.dev_path,
566 is_degraded=self._degraded,
567 ldisk_status=ldisk_status)
569 def Open(self, force=False):
570 """Make the device ready for I/O.
572 This is a no-op for the LV device type.
578 """Notifies that the device will no longer be used for I/O.
580 This is a no-op for the LV device type.
585 def Snapshot(self, size):
586 """Create a snapshot copy of an lvm block device.
589 snap_name = self._lv_name + ".snap"
591 # remove existing snapshot if found
592 snap = LogicalVolume((self._vg_name, snap_name), None, size)
593 _IgnoreError(snap.Remove)
595 pvs_info = self.GetPVInfo(self._vg_name)
597 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
600 free_size, pv_name = pvs_info[0]
602 _ThrowError("Not enough free space: required %s,"
603 " available %s", size, free_size)
605 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
606 "-n%s" % snap_name, self.dev_path])
608 _ThrowError("command: %s error: %s - %s",
609 result.cmd, result.fail_reason, result.output)
613 def SetInfo(self, text):
614 """Update metadata with info text.
617 BlockDev.SetInfo(self, text)
619 # Replace invalid characters
620 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
621 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
623 # Only up to 128 characters are allowed
626 result = utils.RunCmd(["lvchange", "--addtag", text,
629 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
632 def Grow(self, amount):
633 """Grow the logical volume.
636 if self.pe_size is None or self.stripe_count is None:
637 if not self.Attach():
638 _ThrowError("Can't attach to LV during Grow()")
639 full_stripe_size = self.pe_size * self.stripe_count
640 rest = amount % full_stripe_size
642 amount += full_stripe_size - rest
643 # we try multiple algorithms since the 'best' ones might not have
644 # space available in the right place, but later ones might (since
645 # they have less constraints); also note that only recent LVM
647 for alloc_policy in "contiguous", "cling", "normal":
648 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
649 "-L", "+%dm" % amount, self.dev_path])
650 if not result.failed:
652 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
655 class DRBD8Status(object):
656 """A DRBD status representation class.
658 Note that this doesn't support unconfigured devices (cs:Unconfigured).
661 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
662 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
663 "\s+ds:([^/]+)/(\S+)\s+.*$")
664 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
665 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
667 CS_UNCONFIGURED = "Unconfigured"
668 CS_STANDALONE = "StandAlone"
669 CS_WFCONNECTION = "WFConnection"
670 CS_WFREPORTPARAMS = "WFReportParams"
671 CS_CONNECTED = "Connected"
672 CS_STARTINGSYNCS = "StartingSyncS"
673 CS_STARTINGSYNCT = "StartingSyncT"
674 CS_WFBITMAPS = "WFBitMapS"
675 CS_WFBITMAPT = "WFBitMapT"
676 CS_WFSYNCUUID = "WFSyncUUID"
677 CS_SYNCSOURCE = "SyncSource"
678 CS_SYNCTARGET = "SyncTarget"
679 CS_PAUSEDSYNCS = "PausedSyncS"
680 CS_PAUSEDSYNCT = "PausedSyncT"
681 CSET_SYNC = frozenset([
694 DS_DISKLESS = "Diskless"
695 DS_ATTACHING = "Attaching" # transient state
696 DS_FAILED = "Failed" # transient state, next: diskless
697 DS_NEGOTIATING = "Negotiating" # transient state
698 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
699 DS_OUTDATED = "Outdated"
700 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
701 DS_CONSISTENT = "Consistent"
702 DS_UPTODATE = "UpToDate" # normal state
704 RO_PRIMARY = "Primary"
705 RO_SECONDARY = "Secondary"
706 RO_UNKNOWN = "Unknown"
708 def __init__(self, procline):
709 u = self.UNCONF_RE.match(procline)
711 self.cstatus = self.CS_UNCONFIGURED
712 self.lrole = self.rrole = self.ldisk = self.rdisk = None
714 m = self.LINE_RE.match(procline)
716 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
717 self.cstatus = m.group(1)
718 self.lrole = m.group(2)
719 self.rrole = m.group(3)
720 self.ldisk = m.group(4)
721 self.rdisk = m.group(5)
723 # end reading of data from the LINE_RE or UNCONF_RE
725 self.is_standalone = self.cstatus == self.CS_STANDALONE
726 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
727 self.is_connected = self.cstatus == self.CS_CONNECTED
728 self.is_primary = self.lrole == self.RO_PRIMARY
729 self.is_secondary = self.lrole == self.RO_SECONDARY
730 self.peer_primary = self.rrole == self.RO_PRIMARY
731 self.peer_secondary = self.rrole == self.RO_SECONDARY
732 self.both_primary = self.is_primary and self.peer_primary
733 self.both_secondary = self.is_secondary and self.peer_secondary
735 self.is_diskless = self.ldisk == self.DS_DISKLESS
736 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
738 self.is_in_resync = self.cstatus in self.CSET_SYNC
739 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
741 m = self.SYNC_RE.match(procline)
743 self.sync_percent = float(m.group(1))
744 hours = int(m.group(2))
745 minutes = int(m.group(3))
746 seconds = int(m.group(4))
747 self.est_time = hours * 3600 + minutes * 60 + seconds
749 # we have (in this if branch) no percent information, but if
750 # we're resyncing we need to 'fake' a sync percent information,
751 # as this is how cmdlib determines if it makes sense to wait for
753 if self.is_in_resync:
754 self.sync_percent = 0
756 self.sync_percent = None
760 class BaseDRBD(BlockDev):
763 This class contains a few bits of common functionality between the
764 0.7 and 8.x versions of DRBD.
767 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
768 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
771 _ST_UNCONFIGURED = "Unconfigured"
772 _ST_WFCONNECTION = "WFConnection"
773 _ST_CONNECTED = "Connected"
775 _STATUS_FILE = "/proc/drbd"
778 def _GetProcData(filename=_STATUS_FILE):
779 """Return data from /proc/drbd.
783 data = utils.ReadFile(filename).splitlines()
784 except EnvironmentError, err:
785 if err.errno == errno.ENOENT:
786 _ThrowError("The file %s cannot be opened, check if the module"
787 " is loaded (%s)", filename, str(err))
789 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
791 _ThrowError("Can't read any data from %s", filename)
795 def _MassageProcData(data):
796 """Transform the output of _GetProdData into a nicer form.
798 @return: a dictionary of minor: joined lines from /proc/drbd
802 lmatch = re.compile("^ *([0-9]+):.*$")
804 old_minor = old_line = None
806 if not line: # completely empty lines, as can be returned by drbd8.0+
808 lresult = lmatch.match(line)
809 if lresult is not None:
810 if old_minor is not None:
811 results[old_minor] = old_line
812 old_minor = int(lresult.group(1))
815 if old_minor is not None:
816 old_line += " " + line.strip()
818 if old_minor is not None:
819 results[old_minor] = old_line
823 def _GetVersion(cls):
824 """Return the DRBD version.
826 This will return a dict with keys:
832 - proto2 (only on drbd > 8.2.X)
835 proc_data = cls._GetProcData()
836 first_line = proc_data[0].strip()
837 version = cls._VERSION_RE.match(first_line)
839 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
842 values = version.groups()
843 retval = {'k_major': int(values[0]),
844 'k_minor': int(values[1]),
845 'k_point': int(values[2]),
846 'api': int(values[3]),
847 'proto': int(values[4]),
849 if values[5] is not None:
850 retval['proto2'] = values[5]
856 """Return the path to a drbd device for a given minor.
859 return "/dev/drbd%d" % minor
862 def GetUsedDevs(cls):
863 """Compute the list of used DRBD devices.
866 data = cls._GetProcData()
869 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
871 match = valid_line.match(line)
874 minor = int(match.group(1))
875 state = match.group(2)
876 if state == cls._ST_UNCONFIGURED:
878 used_devs[minor] = state, line
882 def _SetFromMinor(self, minor):
883 """Set our parameters based on the given minor.
885 This sets our minor variable and our dev_path.
889 self.minor = self.dev_path = None
890 self.attached = False
893 self.dev_path = self._DevPath(minor)
897 def _CheckMetaSize(meta_device):
898 """Check if the given meta device looks like a valid one.
900 This currently only check the size, which must be around
904 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
906 _ThrowError("Failed to get device size: %s - %s",
907 result.fail_reason, result.output)
909 sectors = int(result.stdout)
911 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
912 bytes = sectors * 512
913 if bytes < 128 * 1024 * 1024: # less than 128MiB
914 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
915 # the maximum *valid* size of the meta device when living on top
916 # of LVM is hard to compute: it depends on the number of stripes
917 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
918 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
919 # size meta device; as such, we restrict it to 1GB (a little bit
920 # too generous, but making assumptions about PE size is hard)
921 if bytes > 1024 * 1024 * 1024:
922 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
924 def Rename(self, new_id):
927 This is not supported for drbd devices.
930 raise errors.ProgrammerError("Can't rename a drbd device")
933 class DRBD8(BaseDRBD):
934 """DRBD v8.x block device.
936 This implements the local host part of the DRBD device, i.e. it
937 doesn't do anything to the supposed peer. If you need a fully
938 connected DRBD pair, you need to use this class on both hosts.
940 The unique_id for the drbd device is the (local_ip, local_port,
941 remote_ip, remote_port) tuple, and it must have two children: the
942 data device and the meta_device. The meta device is checked for
943 valid size and is zeroed on create.
950 _NET_RECONFIG_TIMEOUT = 60
952 def __init__(self, unique_id, children, size):
953 if children and children.count(None) > 0:
955 super(DRBD8, self).__init__(unique_id, children, size)
956 self.major = self._DRBD_MAJOR
957 version = self._GetVersion()
958 if version['k_major'] != 8 :
959 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
960 " usage: kernel is %s.%s, ganeti wants 8.x",
961 version['k_major'], version['k_minor'])
963 if len(children) not in (0, 2):
964 raise ValueError("Invalid configuration data %s" % str(children))
965 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
966 raise ValueError("Invalid configuration data %s" % str(unique_id))
967 (self._lhost, self._lport,
968 self._rhost, self._rport,
969 self._aminor, self._secret) = unique_id
970 if (self._lhost is not None and self._lhost == self._rhost and
971 self._lport == self._rport):
972 raise ValueError("Invalid configuration data, same local/remote %s" %
977 def _InitMeta(cls, minor, dev_path):
978 """Initialize a meta device.
980 This will not work if the given minor is in use.
983 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
984 "v08", dev_path, "0", "create-md"])
986 _ThrowError("Can't initialize meta device: %s", result.output)
989 def _FindUnusedMinor(cls):
990 """Find an unused DRBD device.
992 This is specific to 8.x as the minors are allocated dynamically,
993 so non-existing numbers up to a max minor count are actually free.
996 data = cls._GetProcData()
998 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
999 used_line = re.compile("^ *([0-9]+): cs:")
1002 match = unused_line.match(line)
1004 return int(match.group(1))
1005 match = used_line.match(line)
1007 minor = int(match.group(1))
1008 highest = max(highest, minor)
1009 if highest is None: # there are no minors in use at all
1011 if highest >= cls._MAX_MINORS:
1012 logging.error("Error: no free drbd minors!")
1013 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1017 def _GetShowParser(cls):
1018 """Return a parser for `drbd show` output.
1020 This will either create or return an already-create parser for the
1021 output of the command `drbd show`.
1024 if cls._PARSE_SHOW is not None:
1025 return cls._PARSE_SHOW
1028 lbrace = pyp.Literal("{").suppress()
1029 rbrace = pyp.Literal("}").suppress()
1030 semi = pyp.Literal(";").suppress()
1031 # this also converts the value to an int
1032 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1034 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1035 defa = pyp.Literal("_is_default").suppress()
1036 dbl_quote = pyp.Literal('"').suppress()
1038 keyword = pyp.Word(pyp.alphanums + '-')
1041 value = pyp.Word(pyp.alphanums + '_-/.:')
1042 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1043 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1044 pyp.Optional(pyp.Literal("ipv6")).suppress())
1045 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1046 pyp.Literal(':').suppress() + number)
1047 # meta device, extended syntax
1048 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1049 number + pyp.Word(']').suppress())
1050 # device name, extended syntax
1051 device_value = pyp.Literal("minor").suppress() + number
1054 stmt = (~rbrace + keyword + ~lbrace +
1055 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1057 pyp.Optional(defa) + semi +
1058 pyp.Optional(pyp.restOfLine).suppress())
1061 section_name = pyp.Word(pyp.alphas + '_')
1062 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1064 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1067 cls._PARSE_SHOW = bnf
1072 def _GetShowData(cls, minor):
1073 """Return the `drbdsetup show` data for a minor.
1076 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1078 logging.error("Can't display the drbd config: %s - %s",
1079 result.fail_reason, result.output)
1081 return result.stdout
1084 def _GetDevInfo(cls, out):
1085 """Parse details about a given DRBD minor.
1087 This return, if available, the local backing device (as a path)
1088 and the local and remote (ip, port) information from a string
1089 containing the output of the `drbdsetup show` command as returned
1097 bnf = cls._GetShowParser()
1101 results = bnf.parseString(out)
1102 except pyp.ParseException, err:
1103 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1105 # and massage the results into our desired format
1106 for section in results:
1108 if sname == "_this_host":
1109 for lst in section[1:]:
1110 if lst[0] == "disk":
1111 data["local_dev"] = lst[1]
1112 elif lst[0] == "meta-disk":
1113 data["meta_dev"] = lst[1]
1114 data["meta_index"] = lst[2]
1115 elif lst[0] == "address":
1116 data["local_addr"] = tuple(lst[1:])
1117 elif sname == "_remote_host":
1118 for lst in section[1:]:
1119 if lst[0] == "address":
1120 data["remote_addr"] = tuple(lst[1:])
1123 def _MatchesLocal(self, info):
1124 """Test if our local config matches with an existing device.
1126 The parameter should be as returned from `_GetDevInfo()`. This
1127 method tests if our local backing device is the same as the one in
1128 the info parameter, in effect testing if we look like the given
1133 backend, meta = self._children
1135 backend = meta = None
1137 if backend is not None:
1138 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1140 retval = ("local_dev" not in info)
1142 if meta is not None:
1143 retval = retval and ("meta_dev" in info and
1144 info["meta_dev"] == meta.dev_path)
1145 retval = retval and ("meta_index" in info and
1146 info["meta_index"] == 0)
1148 retval = retval and ("meta_dev" not in info and
1149 "meta_index" not in info)
1152 def _MatchesNet(self, info):
1153 """Test if our network config matches with an existing device.
1155 The parameter should be as returned from `_GetDevInfo()`. This
1156 method tests if our network configuration is the same as the one
1157 in the info parameter, in effect testing if we look like the given
1161 if (((self._lhost is None and not ("local_addr" in info)) and
1162 (self._rhost is None and not ("remote_addr" in info)))):
1165 if self._lhost is None:
1168 if not ("local_addr" in info and
1169 "remote_addr" in info):
1172 retval = (info["local_addr"] == (self._lhost, self._lport))
1173 retval = (retval and
1174 info["remote_addr"] == (self._rhost, self._rport))
1178 def _AssembleLocal(cls, minor, backend, meta, size):
1179 """Configure the local part of a DRBD device.
1182 args = ["drbdsetup", cls._DevPath(minor), "disk",
1187 args.extend(["-d", "%sm" % size])
1188 result = utils.RunCmd(args)
1190 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1193 def _AssembleNet(cls, minor, net_info, protocol,
1194 dual_pri=False, hmac=None, secret=None):
1195 """Configure the network part of the device.
1198 lhost, lport, rhost, rport = net_info
1199 if None in net_info:
1200 # we don't want network connection and actually want to make
1202 cls._ShutdownNet(minor)
1205 # Workaround for a race condition. When DRBD is doing its dance to
1206 # establish a connection with its peer, it also sends the
1207 # synchronization speed over the wire. In some cases setting the
1208 # sync speed only after setting up both sides can race with DRBD
1209 # connecting, hence we set it here before telling DRBD anything
1211 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1213 args = ["drbdsetup", cls._DevPath(minor), "net",
1214 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1215 "-A", "discard-zero-changes",
1222 args.extend(["-a", hmac, "-x", secret])
1223 result = utils.RunCmd(args)
1225 _ThrowError("drbd%d: can't setup network: %s - %s",
1226 minor, result.fail_reason, result.output)
1228 timeout = time.time() + 10
1230 while time.time() < timeout:
1231 info = cls._GetDevInfo(cls._GetShowData(minor))
1232 if not "local_addr" in info or not "remote_addr" in info:
1235 if (info["local_addr"] != (lhost, lport) or
1236 info["remote_addr"] != (rhost, rport)):
1242 _ThrowError("drbd%d: timeout while configuring network", minor)
1244 def AddChildren(self, devices):
1245 """Add a disk to the DRBD device.
1248 if self.minor is None:
1249 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1251 if len(devices) != 2:
1252 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1253 info = self._GetDevInfo(self._GetShowData(self.minor))
1254 if "local_dev" in info:
1255 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1256 backend, meta = devices
1257 if backend.dev_path is None or meta.dev_path is None:
1258 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1261 self._CheckMetaSize(meta.dev_path)
1262 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1264 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1265 self._children = devices
1267 def RemoveChildren(self, devices):
1268 """Detach the drbd device from local storage.
1271 if self.minor is None:
1272 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1274 # early return if we don't actually have backing storage
1275 info = self._GetDevInfo(self._GetShowData(self.minor))
1276 if "local_dev" not in info:
1278 if len(self._children) != 2:
1279 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1281 if self._children.count(None) == 2: # we don't actually have children :)
1282 logging.warning("drbd%d: requested detach while detached", self.minor)
1284 if len(devices) != 2:
1285 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1286 for child, dev in zip(self._children, devices):
1287 if dev != child.dev_path:
1288 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1289 " RemoveChildren", self.minor, dev, child.dev_path)
1291 self._ShutdownLocal(self.minor)
1295 def _SetMinorSyncSpeed(cls, minor, kbytes):
1296 """Set the speed of the DRBD syncer.
1298 This is the low-level implementation.
1301 @param minor: the drbd minor whose settings we change
1303 @param kbytes: the speed in kbytes/second
1305 @return: the success of the operation
1308 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1309 "-r", "%d" % kbytes, "--create-device"])
1311 logging.error("Can't change syncer rate: %s - %s",
1312 result.fail_reason, result.output)
1313 return not result.failed
1315 def SetSyncSpeed(self, kbytes):
1316 """Set the speed of the DRBD syncer.
1319 @param kbytes: the speed in kbytes/second
1321 @return: the success of the operation
1324 if self.minor is None:
1325 logging.info("Not attached during SetSyncSpeed")
1327 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1328 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1330 def GetProcStatus(self):
1331 """Return device data from /proc.
1334 if self.minor is None:
1335 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1336 proc_info = self._MassageProcData(self._GetProcData())
1337 if self.minor not in proc_info:
1338 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1339 return DRBD8Status(proc_info[self.minor])
1341 def GetSyncStatus(self):
1342 """Returns the sync status of the device.
1345 If sync_percent is None, it means all is ok
1346 If estimated_time is None, it means we can't estimate
1347 the time needed, otherwise it's the time left in seconds.
1350 We set the is_degraded parameter to True on two conditions:
1351 network not connected or local disk missing.
1353 We compute the ldisk parameter based on whether we have a local
1356 @rtype: objects.BlockDevStatus
1359 if self.minor is None and not self.Attach():
1360 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1362 stats = self.GetProcStatus()
1363 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1365 if stats.is_disk_uptodate:
1366 ldisk_status = constants.LDS_OKAY
1367 elif stats.is_diskless:
1368 ldisk_status = constants.LDS_FAULTY
1370 ldisk_status = constants.LDS_UNKNOWN
1372 return objects.BlockDevStatus(dev_path=self.dev_path,
1375 sync_percent=stats.sync_percent,
1376 estimated_time=stats.est_time,
1377 is_degraded=is_degraded,
1378 ldisk_status=ldisk_status)
1380 def Open(self, force=False):
1381 """Make the local state primary.
1383 If the 'force' parameter is given, the '-o' option is passed to
1384 drbdsetup. Since this is a potentially dangerous operation, the
1385 force flag should be only given after creation, when it actually
1389 if self.minor is None and not self.Attach():
1390 logging.error("DRBD cannot attach to a device during open")
1392 cmd = ["drbdsetup", self.dev_path, "primary"]
1395 result = utils.RunCmd(cmd)
1397 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1401 """Make the local state secondary.
1403 This will, of course, fail if the device is in use.
1406 if self.minor is None and not self.Attach():
1407 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1408 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1410 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1411 self.minor, result.output)
1413 def DisconnectNet(self):
1414 """Removes network configuration.
1416 This method shutdowns the network side of the device.
1418 The method will wait up to a hardcoded timeout for the device to
1419 go into standalone after the 'disconnect' command before
1420 re-configuring it, as sometimes it takes a while for the
1421 disconnect to actually propagate and thus we might issue a 'net'
1422 command while the device is still connected. If the device will
1423 still be attached to the network and we time out, we raise an
1427 if self.minor is None:
1428 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1430 if None in (self._lhost, self._lport, self._rhost, self._rport):
1431 _ThrowError("drbd%d: DRBD disk missing network info in"
1432 " DisconnectNet()", self.minor)
1434 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1435 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1436 sleep_time = 0.100 # we start the retry time at 100 milliseconds
1437 while time.time() < timeout_limit:
1438 status = self.GetProcStatus()
1439 if status.is_standalone:
1441 # retry the disconnect, it seems possible that due to a
1442 # well-time disconnect on the peer, my disconnect command might
1443 # be ignored and forgotten
1444 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1446 time.sleep(sleep_time)
1447 sleep_time = min(2, sleep_time * 1.5)
1449 if not status.is_standalone:
1450 if ever_disconnected:
1451 msg = ("drbd%d: device did not react to the"
1452 " 'disconnect' command in a timely manner")
1454 msg = "drbd%d: can't shutdown network, even after multiple retries"
1455 _ThrowError(msg, self.minor)
1457 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1458 if reconfig_time > 15: # hardcoded alert limit
1459 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1460 self.minor, reconfig_time)
1462 def AttachNet(self, multimaster):
1463 """Reconnects the network.
1465 This method connects the network side of the device with a
1466 specified multi-master flag. The device needs to be 'Standalone'
1467 but have valid network configuration data.
1470 - multimaster: init the network in dual-primary mode
1473 if self.minor is None:
1474 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1476 if None in (self._lhost, self._lport, self._rhost, self._rport):
1477 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1479 status = self.GetProcStatus()
1481 if not status.is_standalone:
1482 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1484 self._AssembleNet(self.minor,
1485 (self._lhost, self._lport, self._rhost, self._rport),
1486 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1487 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1490 """Check if our minor is configured.
1492 This doesn't do any device configurations - it only checks if the
1493 minor is in a state different from Unconfigured.
1495 Note that this function will not change the state of the system in
1496 any way (except in case of side-effects caused by reading from
1500 used_devs = self.GetUsedDevs()
1501 if self._aminor in used_devs:
1502 minor = self._aminor
1506 self._SetFromMinor(minor)
1507 return minor is not None
1510 """Assemble the drbd.
1513 - if we have a configured device, we try to ensure that it matches
1515 - if not, we create it from zero
1518 super(DRBD8, self).Assemble()
1521 if self.minor is None:
1522 # local device completely unconfigured
1523 self._FastAssemble()
1525 # we have to recheck the local and network status and try to fix
1527 self._SlowAssemble()
1529 def _SlowAssemble(self):
1530 """Assembles the DRBD device from a (partially) configured device.
1532 In case of partially attached (local device matches but no network
1533 setup), we perform the network attach. If successful, we re-test
1534 the attach if can return success.
1537 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1538 for minor in (self._aminor,):
1539 info = self._GetDevInfo(self._GetShowData(minor))
1540 match_l = self._MatchesLocal(info)
1541 match_r = self._MatchesNet(info)
1543 if match_l and match_r:
1544 # everything matches
1547 if match_l and not match_r and "local_addr" not in info:
1548 # disk matches, but not attached to network, attach and recheck
1549 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1550 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1551 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1554 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1555 " show' disagrees", minor)
1557 if match_r and "local_dev" not in info:
1558 # no local disk, but network attached and it matches
1559 self._AssembleLocal(minor, self._children[0].dev_path,
1560 self._children[1].dev_path, self.size)
1561 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1564 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1565 " show' disagrees", minor)
1567 # this case must be considered only if we actually have local
1568 # storage, i.e. not in diskless mode, because all diskless
1569 # devices are equal from the point of view of local
1571 if (match_l and "local_dev" in info and
1572 not match_r and "local_addr" in info):
1573 # strange case - the device network part points to somewhere
1574 # else, even though its local storage is ours; as we own the
1575 # drbd space, we try to disconnect from the remote peer and
1576 # reconnect to our correct one
1578 self._ShutdownNet(minor)
1579 except errors.BlockDeviceError, err:
1580 _ThrowError("drbd%d: device has correct local storage, wrong"
1581 " remote peer and is unable to disconnect in order"
1582 " to attach to the correct peer: %s", minor, str(err))
1583 # note: _AssembleNet also handles the case when we don't want
1584 # local storage (i.e. one or more of the _[lr](host|port) is
1586 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1587 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1588 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1591 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1592 " show' disagrees", minor)
1597 self._SetFromMinor(minor)
1599 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1602 def _FastAssemble(self):
1603 """Assemble the drbd device from zero.
1605 This is run when in Assemble we detect our minor is unused.
1608 minor = self._aminor
1609 if self._children and self._children[0] and self._children[1]:
1610 self._AssembleLocal(minor, self._children[0].dev_path,
1611 self._children[1].dev_path, self.size)
1612 if self._lhost and self._lport and self._rhost and self._rport:
1613 self._AssembleNet(minor,
1614 (self._lhost, self._lport, self._rhost, self._rport),
1615 constants.DRBD_NET_PROTOCOL,
1616 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1617 self._SetFromMinor(minor)
1620 def _ShutdownLocal(cls, minor):
1621 """Detach from the local device.
1623 I/Os will continue to be served from the remote device. If we
1624 don't have a remote device, this operation will fail.
1627 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1629 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1632 def _ShutdownNet(cls, minor):
1633 """Disconnect from the remote peer.
1635 This fails if we don't have a local device.
1638 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1640 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1643 def _ShutdownAll(cls, minor):
1644 """Deactivate the device.
1646 This will, of course, fail if the device is in use.
1649 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1651 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1652 minor, result.output)
1655 """Shutdown the DRBD device.
1658 if self.minor is None and not self.Attach():
1659 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1663 self.dev_path = None
1664 self._ShutdownAll(minor)
1667 """Stub remove for DRBD devices.
1673 def Create(cls, unique_id, children, size):
1674 """Create a new DRBD8 device.
1676 Since DRBD devices are not created per se, just assembled, this
1677 function only initializes the metadata.
1680 if len(children) != 2:
1681 raise errors.ProgrammerError("Invalid setup for the drbd device")
1682 # check that the minor is unused
1683 aminor = unique_id[4]
1684 proc_info = cls._MassageProcData(cls._GetProcData())
1685 if aminor in proc_info:
1686 status = DRBD8Status(proc_info[aminor])
1687 in_use = status.is_in_use
1691 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1694 if not meta.Attach():
1695 _ThrowError("drbd%d: can't attach to meta device '%s'",
1697 cls._CheckMetaSize(meta.dev_path)
1698 cls._InitMeta(aminor, meta.dev_path)
1699 return cls(unique_id, children, size)
1701 def Grow(self, amount):
1702 """Resize the DRBD device and its backing storage.
1705 if self.minor is None:
1706 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1707 if len(self._children) != 2 or None in self._children:
1708 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1709 self._children[0].Grow(amount)
1710 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1711 "%dm" % (self.size + amount)])
1713 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1716 class FileStorage(BlockDev):
1719 This class represents the a file storage backend device.
1721 The unique_id for the file device is a (file_driver, file_path) tuple.
1724 def __init__(self, unique_id, children, size):
1725 """Initalizes a file device backend.
1729 raise errors.BlockDeviceError("Invalid setup for file device")
1730 super(FileStorage, self).__init__(unique_id, children, size)
1731 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1732 raise ValueError("Invalid configuration data %s" % str(unique_id))
1733 self.driver = unique_id[0]
1734 self.dev_path = unique_id[1]
1738 """Assemble the device.
1740 Checks whether the file device exists, raises BlockDeviceError otherwise.
1743 if not os.path.exists(self.dev_path):
1744 _ThrowError("File device '%s' does not exist" % self.dev_path)
1747 """Shutdown the device.
1749 This is a no-op for the file type, as we don't deactivate
1750 the file on shutdown.
1755 def Open(self, force=False):
1756 """Make the device ready for I/O.
1758 This is a no-op for the file type.
1764 """Notifies that the device will no longer be used for I/O.
1766 This is a no-op for the file type.
1772 """Remove the file backing the block device.
1775 @return: True if the removal was successful
1779 os.remove(self.dev_path)
1780 except OSError, err:
1781 if err.errno != errno.ENOENT:
1782 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1785 """Attach to an existing file.
1787 Check if this file already exists.
1790 @return: True if file exists
1793 self.attached = os.path.exists(self.dev_path)
1794 return self.attached
1796 def GetActualSize(self):
1797 """Return the actual disk size.
1799 @note: the device needs to be active when this is called
1802 assert self.attached, "BlockDevice not attached in GetActualSize()"
1804 st = os.stat(self.dev_path)
1806 except OSError, err:
1807 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1810 def Create(cls, unique_id, children, size):
1811 """Create a new file.
1813 @param size: the size of file in MiB
1815 @rtype: L{bdev.FileStorage}
1816 @return: an instance of FileStorage
1819 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1820 raise ValueError("Invalid configuration data %s" % str(unique_id))
1821 dev_path = unique_id[1]
1822 if os.path.exists(dev_path):
1823 _ThrowError("File already existing: %s", dev_path)
1825 f = open(dev_path, 'w')
1826 f.truncate(size * 1024 * 1024)
1828 except IOError, err:
1829 _ThrowError("Error in file creation: %", str(err))
1831 return FileStorage(unique_id, children, size)
1835 constants.LD_LV: LogicalVolume,
1836 constants.LD_DRBD8: DRBD8,
1837 constants.LD_FILE: FileStorage,
1841 def FindDevice(dev_type, unique_id, children, size):
1842 """Search for an existing, assembled device.
1844 This will succeed only if the device exists and is assembled, but it
1845 does not do any actions in order to activate the device.
1848 if dev_type not in DEV_MAP:
1849 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1850 device = DEV_MAP[dev_type](unique_id, children, size)
1851 if not device.attached:
1856 def Assemble(dev_type, unique_id, children, size):
1857 """Try to attach or assemble an existing device.
1859 This will attach to assemble the device, as needed, to bring it
1860 fully up. It must be safe to run on already-assembled devices.
1863 if dev_type not in DEV_MAP:
1864 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1865 device = DEV_MAP[dev_type](unique_id, children, size)
1870 def Create(dev_type, unique_id, children, size):
1874 if dev_type not in DEV_MAP:
1875 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1876 device = DEV_MAP[dev_type].Create(unique_id, children, size)