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 lresult = lmatch.match(line)
807 if lresult is not None:
808 if old_minor is not None:
809 results[old_minor] = old_line
810 old_minor = int(lresult.group(1))
813 if old_minor is not None:
814 old_line += " " + line.strip()
816 if old_minor is not None:
817 results[old_minor] = old_line
821 def _GetVersion(cls):
822 """Return the DRBD version.
824 This will return a dict with keys:
830 - proto2 (only on drbd > 8.2.X)
833 proc_data = cls._GetProcData()
834 first_line = proc_data[0].strip()
835 version = cls._VERSION_RE.match(first_line)
837 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
840 values = version.groups()
841 retval = {'k_major': int(values[0]),
842 'k_minor': int(values[1]),
843 'k_point': int(values[2]),
844 'api': int(values[3]),
845 'proto': int(values[4]),
847 if values[5] is not None:
848 retval['proto2'] = values[5]
854 """Return the path to a drbd device for a given minor.
857 return "/dev/drbd%d" % minor
860 def GetUsedDevs(cls):
861 """Compute the list of used DRBD devices.
864 data = cls._GetProcData()
867 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
869 match = valid_line.match(line)
872 minor = int(match.group(1))
873 state = match.group(2)
874 if state == cls._ST_UNCONFIGURED:
876 used_devs[minor] = state, line
880 def _SetFromMinor(self, minor):
881 """Set our parameters based on the given minor.
883 This sets our minor variable and our dev_path.
887 self.minor = self.dev_path = None
888 self.attached = False
891 self.dev_path = self._DevPath(minor)
895 def _CheckMetaSize(meta_device):
896 """Check if the given meta device looks like a valid one.
898 This currently only check the size, which must be around
902 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
904 _ThrowError("Failed to get device size: %s - %s",
905 result.fail_reason, result.output)
907 sectors = int(result.stdout)
909 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
910 bytes = sectors * 512
911 if bytes < 128 * 1024 * 1024: # less than 128MiB
912 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
913 # the maximum *valid* size of the meta device when living on top
914 # of LVM is hard to compute: it depends on the number of stripes
915 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
916 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
917 # size meta device; as such, we restrict it to 1GB (a little bit
918 # too generous, but making assumptions about PE size is hard)
919 if bytes > 1024 * 1024 * 1024:
920 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
922 def Rename(self, new_id):
925 This is not supported for drbd devices.
928 raise errors.ProgrammerError("Can't rename a drbd device")
931 class DRBD8(BaseDRBD):
932 """DRBD v8.x block device.
934 This implements the local host part of the DRBD device, i.e. it
935 doesn't do anything to the supposed peer. If you need a fully
936 connected DRBD pair, you need to use this class on both hosts.
938 The unique_id for the drbd device is the (local_ip, local_port,
939 remote_ip, remote_port) tuple, and it must have two children: the
940 data device and the meta_device. The meta device is checked for
941 valid size and is zeroed on create.
948 _NET_RECONFIG_TIMEOUT = 60
950 def __init__(self, unique_id, children, size):
951 if children and children.count(None) > 0:
953 super(DRBD8, self).__init__(unique_id, children, size)
954 self.major = self._DRBD_MAJOR
955 version = self._GetVersion()
956 if version['k_major'] != 8 :
957 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
958 " usage: kernel is %s.%s, ganeti wants 8.x",
959 version['k_major'], version['k_minor'])
961 if len(children) not in (0, 2):
962 raise ValueError("Invalid configuration data %s" % str(children))
963 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
964 raise ValueError("Invalid configuration data %s" % str(unique_id))
965 (self._lhost, self._lport,
966 self._rhost, self._rport,
967 self._aminor, self._secret) = unique_id
968 if (self._lhost is not None and self._lhost == self._rhost and
969 self._lport == self._rport):
970 raise ValueError("Invalid configuration data, same local/remote %s" %
975 def _InitMeta(cls, minor, dev_path):
976 """Initialize a meta device.
978 This will not work if the given minor is in use.
981 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
982 "v08", dev_path, "0", "create-md"])
984 _ThrowError("Can't initialize meta device: %s", result.output)
987 def _FindUnusedMinor(cls):
988 """Find an unused DRBD device.
990 This is specific to 8.x as the minors are allocated dynamically,
991 so non-existing numbers up to a max minor count are actually free.
994 data = cls._GetProcData()
996 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
997 used_line = re.compile("^ *([0-9]+): cs:")
1000 match = unused_line.match(line)
1002 return int(match.group(1))
1003 match = used_line.match(line)
1005 minor = int(match.group(1))
1006 highest = max(highest, minor)
1007 if highest is None: # there are no minors in use at all
1009 if highest >= cls._MAX_MINORS:
1010 logging.error("Error: no free drbd minors!")
1011 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1015 def _GetShowParser(cls):
1016 """Return a parser for `drbd show` output.
1018 This will either create or return an already-create parser for the
1019 output of the command `drbd show`.
1022 if cls._PARSE_SHOW is not None:
1023 return cls._PARSE_SHOW
1026 lbrace = pyp.Literal("{").suppress()
1027 rbrace = pyp.Literal("}").suppress()
1028 semi = pyp.Literal(";").suppress()
1029 # this also converts the value to an int
1030 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1032 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1033 defa = pyp.Literal("_is_default").suppress()
1034 dbl_quote = pyp.Literal('"').suppress()
1036 keyword = pyp.Word(pyp.alphanums + '-')
1039 value = pyp.Word(pyp.alphanums + '_-/.:')
1040 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1041 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1042 pyp.Optional(pyp.Literal("ipv6")).suppress())
1043 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1044 pyp.Literal(':').suppress() + number)
1045 # meta device, extended syntax
1046 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1047 number + pyp.Word(']').suppress())
1048 # device name, extended syntax
1049 device_value = pyp.Literal("minor").suppress() + number
1052 stmt = (~rbrace + keyword + ~lbrace +
1053 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1055 pyp.Optional(defa) + semi +
1056 pyp.Optional(pyp.restOfLine).suppress())
1059 section_name = pyp.Word(pyp.alphas + '_')
1060 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1062 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1065 cls._PARSE_SHOW = bnf
1070 def _GetShowData(cls, minor):
1071 """Return the `drbdsetup show` data for a minor.
1074 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1076 logging.error("Can't display the drbd config: %s - %s",
1077 result.fail_reason, result.output)
1079 return result.stdout
1082 def _GetDevInfo(cls, out):
1083 """Parse details about a given DRBD minor.
1085 This return, if available, the local backing device (as a path)
1086 and the local and remote (ip, port) information from a string
1087 containing the output of the `drbdsetup show` command as returned
1095 bnf = cls._GetShowParser()
1099 results = bnf.parseString(out)
1100 except pyp.ParseException, err:
1101 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1103 # and massage the results into our desired format
1104 for section in results:
1106 if sname == "_this_host":
1107 for lst in section[1:]:
1108 if lst[0] == "disk":
1109 data["local_dev"] = lst[1]
1110 elif lst[0] == "meta-disk":
1111 data["meta_dev"] = lst[1]
1112 data["meta_index"] = lst[2]
1113 elif lst[0] == "address":
1114 data["local_addr"] = tuple(lst[1:])
1115 elif sname == "_remote_host":
1116 for lst in section[1:]:
1117 if lst[0] == "address":
1118 data["remote_addr"] = tuple(lst[1:])
1121 def _MatchesLocal(self, info):
1122 """Test if our local config matches with an existing device.
1124 The parameter should be as returned from `_GetDevInfo()`. This
1125 method tests if our local backing device is the same as the one in
1126 the info parameter, in effect testing if we look like the given
1131 backend, meta = self._children
1133 backend = meta = None
1135 if backend is not None:
1136 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1138 retval = ("local_dev" not in info)
1140 if meta is not None:
1141 retval = retval and ("meta_dev" in info and
1142 info["meta_dev"] == meta.dev_path)
1143 retval = retval and ("meta_index" in info and
1144 info["meta_index"] == 0)
1146 retval = retval and ("meta_dev" not in info and
1147 "meta_index" not in info)
1150 def _MatchesNet(self, info):
1151 """Test if our network config matches with an existing device.
1153 The parameter should be as returned from `_GetDevInfo()`. This
1154 method tests if our network configuration is the same as the one
1155 in the info parameter, in effect testing if we look like the given
1159 if (((self._lhost is None and not ("local_addr" in info)) and
1160 (self._rhost is None and not ("remote_addr" in info)))):
1163 if self._lhost is None:
1166 if not ("local_addr" in info and
1167 "remote_addr" in info):
1170 retval = (info["local_addr"] == (self._lhost, self._lport))
1171 retval = (retval and
1172 info["remote_addr"] == (self._rhost, self._rport))
1176 def _AssembleLocal(cls, minor, backend, meta, size):
1177 """Configure the local part of a DRBD device.
1180 args = ["drbdsetup", cls._DevPath(minor), "disk",
1185 args.extend(["-d", "%sm" % size])
1186 result = utils.RunCmd(args)
1188 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1191 def _AssembleNet(cls, minor, net_info, protocol,
1192 dual_pri=False, hmac=None, secret=None):
1193 """Configure the network part of the device.
1196 lhost, lport, rhost, rport = net_info
1197 if None in net_info:
1198 # we don't want network connection and actually want to make
1200 cls._ShutdownNet(minor)
1203 # Workaround for a race condition. When DRBD is doing its dance to
1204 # establish a connection with its peer, it also sends the
1205 # synchronization speed over the wire. In some cases setting the
1206 # sync speed only after setting up both sides can race with DRBD
1207 # connecting, hence we set it here before telling DRBD anything
1209 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1211 args = ["drbdsetup", cls._DevPath(minor), "net",
1212 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1213 "-A", "discard-zero-changes",
1220 args.extend(["-a", hmac, "-x", secret])
1221 result = utils.RunCmd(args)
1223 _ThrowError("drbd%d: can't setup network: %s - %s",
1224 minor, result.fail_reason, result.output)
1226 timeout = time.time() + 10
1228 while time.time() < timeout:
1229 info = cls._GetDevInfo(cls._GetShowData(minor))
1230 if not "local_addr" in info or not "remote_addr" in info:
1233 if (info["local_addr"] != (lhost, lport) or
1234 info["remote_addr"] != (rhost, rport)):
1240 _ThrowError("drbd%d: timeout while configuring network", minor)
1242 def AddChildren(self, devices):
1243 """Add a disk to the DRBD device.
1246 if self.minor is None:
1247 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1249 if len(devices) != 2:
1250 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1251 info = self._GetDevInfo(self._GetShowData(self.minor))
1252 if "local_dev" in info:
1253 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1254 backend, meta = devices
1255 if backend.dev_path is None or meta.dev_path is None:
1256 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1259 self._CheckMetaSize(meta.dev_path)
1260 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1262 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1263 self._children = devices
1265 def RemoveChildren(self, devices):
1266 """Detach the drbd device from local storage.
1269 if self.minor is None:
1270 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1272 # early return if we don't actually have backing storage
1273 info = self._GetDevInfo(self._GetShowData(self.minor))
1274 if "local_dev" not in info:
1276 if len(self._children) != 2:
1277 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1279 if self._children.count(None) == 2: # we don't actually have children :)
1280 logging.warning("drbd%d: requested detach while detached", self.minor)
1282 if len(devices) != 2:
1283 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1284 for child, dev in zip(self._children, devices):
1285 if dev != child.dev_path:
1286 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1287 " RemoveChildren", self.minor, dev, child.dev_path)
1289 self._ShutdownLocal(self.minor)
1293 def _SetMinorSyncSpeed(cls, minor, kbytes):
1294 """Set the speed of the DRBD syncer.
1296 This is the low-level implementation.
1299 @param minor: the drbd minor whose settings we change
1301 @param kbytes: the speed in kbytes/second
1303 @return: the success of the operation
1306 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1307 "-r", "%d" % kbytes, "--create-device"])
1309 logging.error("Can't change syncer rate: %s - %s",
1310 result.fail_reason, result.output)
1311 return not result.failed
1313 def SetSyncSpeed(self, kbytes):
1314 """Set the speed of the DRBD syncer.
1317 @param kbytes: the speed in kbytes/second
1319 @return: the success of the operation
1322 if self.minor is None:
1323 logging.info("Not attached during SetSyncSpeed")
1325 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1326 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1328 def GetProcStatus(self):
1329 """Return device data from /proc.
1332 if self.minor is None:
1333 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1334 proc_info = self._MassageProcData(self._GetProcData())
1335 if self.minor not in proc_info:
1336 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1337 return DRBD8Status(proc_info[self.minor])
1339 def GetSyncStatus(self):
1340 """Returns the sync status of the device.
1343 If sync_percent is None, it means all is ok
1344 If estimated_time is None, it means we can't estimate
1345 the time needed, otherwise it's the time left in seconds.
1348 We set the is_degraded parameter to True on two conditions:
1349 network not connected or local disk missing.
1351 We compute the ldisk parameter based on whether we have a local
1354 @rtype: objects.BlockDevStatus
1357 if self.minor is None and not self.Attach():
1358 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1360 stats = self.GetProcStatus()
1361 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1363 if stats.is_disk_uptodate:
1364 ldisk_status = constants.LDS_OKAY
1365 elif stats.is_diskless:
1366 ldisk_status = constants.LDS_FAULTY
1368 ldisk_status = constants.LDS_UNKNOWN
1370 return objects.BlockDevStatus(dev_path=self.dev_path,
1373 sync_percent=stats.sync_percent,
1374 estimated_time=stats.est_time,
1375 is_degraded=is_degraded,
1376 ldisk_status=ldisk_status)
1378 def Open(self, force=False):
1379 """Make the local state primary.
1381 If the 'force' parameter is given, the '-o' option is passed to
1382 drbdsetup. Since this is a potentially dangerous operation, the
1383 force flag should be only given after creation, when it actually
1387 if self.minor is None and not self.Attach():
1388 logging.error("DRBD cannot attach to a device during open")
1390 cmd = ["drbdsetup", self.dev_path, "primary"]
1393 result = utils.RunCmd(cmd)
1395 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1399 """Make the local state secondary.
1401 This will, of course, fail if the device is in use.
1404 if self.minor is None and not self.Attach():
1405 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1406 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1408 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1409 self.minor, result.output)
1411 def DisconnectNet(self):
1412 """Removes network configuration.
1414 This method shutdowns the network side of the device.
1416 The method will wait up to a hardcoded timeout for the device to
1417 go into standalone after the 'disconnect' command before
1418 re-configuring it, as sometimes it takes a while for the
1419 disconnect to actually propagate and thus we might issue a 'net'
1420 command while the device is still connected. If the device will
1421 still be attached to the network and we time out, we raise an
1425 if self.minor is None:
1426 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1428 if None in (self._lhost, self._lport, self._rhost, self._rport):
1429 _ThrowError("drbd%d: DRBD disk missing network info in"
1430 " DisconnectNet()", self.minor)
1432 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1433 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1434 sleep_time = 0.100 # we start the retry time at 100 milliseconds
1435 while time.time() < timeout_limit:
1436 status = self.GetProcStatus()
1437 if status.is_standalone:
1439 # retry the disconnect, it seems possible that due to a
1440 # well-time disconnect on the peer, my disconnect command might
1441 # be ignored and forgotten
1442 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1444 time.sleep(sleep_time)
1445 sleep_time = min(2, sleep_time * 1.5)
1447 if not status.is_standalone:
1448 if ever_disconnected:
1449 msg = ("drbd%d: device did not react to the"
1450 " 'disconnect' command in a timely manner")
1452 msg = "drbd%d: can't shutdown network, even after multiple retries"
1453 _ThrowError(msg, self.minor)
1455 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1456 if reconfig_time > 15: # hardcoded alert limit
1457 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1458 self.minor, reconfig_time)
1460 def AttachNet(self, multimaster):
1461 """Reconnects the network.
1463 This method connects the network side of the device with a
1464 specified multi-master flag. The device needs to be 'Standalone'
1465 but have valid network configuration data.
1468 - multimaster: init the network in dual-primary mode
1471 if self.minor is None:
1472 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1474 if None in (self._lhost, self._lport, self._rhost, self._rport):
1475 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1477 status = self.GetProcStatus()
1479 if not status.is_standalone:
1480 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1482 self._AssembleNet(self.minor,
1483 (self._lhost, self._lport, self._rhost, self._rport),
1484 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1485 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1488 """Check if our minor is configured.
1490 This doesn't do any device configurations - it only checks if the
1491 minor is in a state different from Unconfigured.
1493 Note that this function will not change the state of the system in
1494 any way (except in case of side-effects caused by reading from
1498 used_devs = self.GetUsedDevs()
1499 if self._aminor in used_devs:
1500 minor = self._aminor
1504 self._SetFromMinor(minor)
1505 return minor is not None
1508 """Assemble the drbd.
1511 - if we have a configured device, we try to ensure that it matches
1513 - if not, we create it from zero
1516 super(DRBD8, self).Assemble()
1519 if self.minor is None:
1520 # local device completely unconfigured
1521 self._FastAssemble()
1523 # we have to recheck the local and network status and try to fix
1525 self._SlowAssemble()
1527 def _SlowAssemble(self):
1528 """Assembles the DRBD device from a (partially) configured device.
1530 In case of partially attached (local device matches but no network
1531 setup), we perform the network attach. If successful, we re-test
1532 the attach if can return success.
1535 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1536 for minor in (self._aminor,):
1537 info = self._GetDevInfo(self._GetShowData(minor))
1538 match_l = self._MatchesLocal(info)
1539 match_r = self._MatchesNet(info)
1541 if match_l and match_r:
1542 # everything matches
1545 if match_l and not match_r and "local_addr" not in info:
1546 # disk matches, but not attached to network, attach and recheck
1547 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1548 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1549 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1552 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1553 " show' disagrees", minor)
1555 if match_r and "local_dev" not in info:
1556 # no local disk, but network attached and it matches
1557 self._AssembleLocal(minor, self._children[0].dev_path,
1558 self._children[1].dev_path, self.size)
1559 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1562 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1563 " show' disagrees", minor)
1565 # this case must be considered only if we actually have local
1566 # storage, i.e. not in diskless mode, because all diskless
1567 # devices are equal from the point of view of local
1569 if (match_l and "local_dev" in info and
1570 not match_r and "local_addr" in info):
1571 # strange case - the device network part points to somewhere
1572 # else, even though its local storage is ours; as we own the
1573 # drbd space, we try to disconnect from the remote peer and
1574 # reconnect to our correct one
1576 self._ShutdownNet(minor)
1577 except errors.BlockDeviceError, err:
1578 _ThrowError("drbd%d: device has correct local storage, wrong"
1579 " remote peer and is unable to disconnect in order"
1580 " to attach to the correct peer: %s", minor, str(err))
1581 # note: _AssembleNet also handles the case when we don't want
1582 # local storage (i.e. one or more of the _[lr](host|port) is
1584 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1585 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1586 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1589 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1590 " show' disagrees", minor)
1595 self._SetFromMinor(minor)
1597 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1600 def _FastAssemble(self):
1601 """Assemble the drbd device from zero.
1603 This is run when in Assemble we detect our minor is unused.
1606 minor = self._aminor
1607 if self._children and self._children[0] and self._children[1]:
1608 self._AssembleLocal(minor, self._children[0].dev_path,
1609 self._children[1].dev_path, self.size)
1610 if self._lhost and self._lport and self._rhost and self._rport:
1611 self._AssembleNet(minor,
1612 (self._lhost, self._lport, self._rhost, self._rport),
1613 constants.DRBD_NET_PROTOCOL,
1614 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1615 self._SetFromMinor(minor)
1618 def _ShutdownLocal(cls, minor):
1619 """Detach from the local device.
1621 I/Os will continue to be served from the remote device. If we
1622 don't have a remote device, this operation will fail.
1625 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1627 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1630 def _ShutdownNet(cls, minor):
1631 """Disconnect from the remote peer.
1633 This fails if we don't have a local device.
1636 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1638 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1641 def _ShutdownAll(cls, minor):
1642 """Deactivate the device.
1644 This will, of course, fail if the device is in use.
1647 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1649 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1650 minor, result.output)
1653 """Shutdown the DRBD device.
1656 if self.minor is None and not self.Attach():
1657 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1661 self.dev_path = None
1662 self._ShutdownAll(minor)
1665 """Stub remove for DRBD devices.
1671 def Create(cls, unique_id, children, size):
1672 """Create a new DRBD8 device.
1674 Since DRBD devices are not created per se, just assembled, this
1675 function only initializes the metadata.
1678 if len(children) != 2:
1679 raise errors.ProgrammerError("Invalid setup for the drbd device")
1680 # check that the minor is unused
1681 aminor = unique_id[4]
1682 proc_info = cls._MassageProcData(cls._GetProcData())
1683 if aminor in proc_info:
1684 status = DRBD8Status(proc_info[aminor])
1685 in_use = status.is_in_use
1689 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1692 if not meta.Attach():
1693 _ThrowError("drbd%d: can't attach to meta device '%s'",
1695 cls._CheckMetaSize(meta.dev_path)
1696 cls._InitMeta(aminor, meta.dev_path)
1697 return cls(unique_id, children, size)
1699 def Grow(self, amount):
1700 """Resize the DRBD device and its backing storage.
1703 if self.minor is None:
1704 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1705 if len(self._children) != 2 or None in self._children:
1706 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1707 self._children[0].Grow(amount)
1708 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1709 "%dm" % (self.size + amount)])
1711 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1714 class FileStorage(BlockDev):
1717 This class represents the a file storage backend device.
1719 The unique_id for the file device is a (file_driver, file_path) tuple.
1722 def __init__(self, unique_id, children, size):
1723 """Initalizes a file device backend.
1727 raise errors.BlockDeviceError("Invalid setup for file device")
1728 super(FileStorage, self).__init__(unique_id, children, size)
1729 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1730 raise ValueError("Invalid configuration data %s" % str(unique_id))
1731 self.driver = unique_id[0]
1732 self.dev_path = unique_id[1]
1736 """Assemble the device.
1738 Checks whether the file device exists, raises BlockDeviceError otherwise.
1741 if not os.path.exists(self.dev_path):
1742 _ThrowError("File device '%s' does not exist" % self.dev_path)
1745 """Shutdown the device.
1747 This is a no-op for the file type, as we don't deactivate
1748 the file on shutdown.
1753 def Open(self, force=False):
1754 """Make the device ready for I/O.
1756 This is a no-op for the file type.
1762 """Notifies that the device will no longer be used for I/O.
1764 This is a no-op for the file type.
1770 """Remove the file backing the block device.
1773 @return: True if the removal was successful
1777 os.remove(self.dev_path)
1778 except OSError, err:
1779 if err.errno != errno.ENOENT:
1780 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1783 """Attach to an existing file.
1785 Check if this file already exists.
1788 @return: True if file exists
1791 self.attached = os.path.exists(self.dev_path)
1792 return self.attached
1794 def GetActualSize(self):
1795 """Return the actual disk size.
1797 @note: the device needs to be active when this is called
1800 assert self.attached, "BlockDevice not attached in GetActualSize()"
1802 st = os.stat(self.dev_path)
1804 except OSError, err:
1805 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1808 def Create(cls, unique_id, children, size):
1809 """Create a new file.
1811 @param size: the size of file in MiB
1813 @rtype: L{bdev.FileStorage}
1814 @return: an instance of FileStorage
1817 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1818 raise ValueError("Invalid configuration data %s" % str(unique_id))
1819 dev_path = unique_id[1]
1820 if os.path.exists(dev_path):
1821 _ThrowError("File already existing: %s", dev_path)
1823 f = open(dev_path, 'w')
1824 f.truncate(size * 1024 * 1024)
1826 except IOError, err:
1827 _ThrowError("Error in file creation: %", str(err))
1829 return FileStorage(unique_id, children, size)
1833 constants.LD_LV: LogicalVolume,
1834 constants.LD_DRBD8: DRBD8,
1835 constants.LD_FILE: FileStorage,
1839 def FindDevice(dev_type, unique_id, children, size):
1840 """Search for an existing, assembled device.
1842 This will succeed only if the device exists and is assembled, but it
1843 does not do any actions in order to activate the device.
1846 if dev_type not in DEV_MAP:
1847 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1848 device = DEV_MAP[dev_type](unique_id, children, size)
1849 if not device.attached:
1854 def Assemble(dev_type, unique_id, children, size):
1855 """Try to attach or assemble an existing device.
1857 This will attach to assemble the device, as needed, to bring it
1858 fully up. It must be safe to run on already-assembled devices.
1861 if dev_type not in DEV_MAP:
1862 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1863 device = DEV_MAP[dev_type](unique_id, children, size)
1868 def Create(dev_type, unique_id, children, size):
1872 if dev_type not in DEV_MAP:
1873 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1874 device = DEV_MAP[dev_type].Create(unique_id, children, size)