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
308 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
309 (self.__class__, self.unique_id, self._children,
310 self.major, self.minor, self.dev_path))
313 class LogicalVolume(BlockDev):
314 """Logical Volume block device.
317 def __init__(self, unique_id, children, size):
318 """Attaches to a LV device.
320 The unique_id is a tuple (vg_name, lv_name)
323 super(LogicalVolume, self).__init__(unique_id, children, size)
324 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
325 raise ValueError("Invalid configuration data %s" % str(unique_id))
326 self._vg_name, self._lv_name = unique_id
327 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
328 self._degraded = True
329 self.major = self.minor = self.pe_size = self.stripe_count = None
333 def Create(cls, unique_id, children, size):
334 """Create a new logical volume.
337 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
338 raise errors.ProgrammerError("Invalid configuration data %s" %
340 vg_name, lv_name = unique_id
341 pvs_info = cls.GetPVInfo(vg_name)
343 _ThrowError("Can't compute PV info for vg %s", vg_name)
347 pvlist = [ pv[1] for pv in pvs_info ]
348 free_size = sum([ pv[0] for pv in pvs_info ])
349 current_pvs = len(pvlist)
350 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
352 # The size constraint should have been checked from the master before
353 # calling the create function.
355 _ThrowError("Not enough free space: required %s,"
356 " available %s", size, free_size)
357 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
358 # If the free space is not well distributed, we won't be able to
359 # create an optimally-striped volume; in that case, we want to try
360 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
362 for stripes_arg in range(stripes, 0, -1):
363 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
364 if not result.failed:
367 _ThrowError("LV create failed (%s): %s",
368 result.fail_reason, result.output)
369 return LogicalVolume(unique_id, children, size)
372 def GetPVInfo(vg_name):
373 """Get the free space info for PVs in a volume group.
375 @param vg_name: the volume group name
378 @return: list of tuples (free_space, name) with free_space in mebibytes
381 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
382 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
384 result = utils.RunCmd(command)
386 logging.error("Can't get the PV information: %s - %s",
387 result.fail_reason, result.output)
390 for line in result.stdout.splitlines():
391 fields = line.strip().split(':')
393 logging.error("Can't parse pvs output: line '%s'", line)
395 # skip over pvs from another vg or ones which are not allocatable
396 if fields[1] != vg_name or fields[3][0] != 'a':
398 data.append((float(fields[2]), fields[0]))
403 """Remove this logical volume.
406 if not self.minor and not self.Attach():
407 # the LV does not exist
409 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
410 (self._vg_name, self._lv_name)])
412 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
414 def Rename(self, new_id):
415 """Rename this logical volume.
418 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
419 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
420 new_vg, new_name = new_id
421 if new_vg != self._vg_name:
422 raise errors.ProgrammerError("Can't move a logical volume across"
423 " volume groups (from %s to to %s)" %
424 (self._vg_name, new_vg))
425 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
427 _ThrowError("Failed to rename the logical volume: %s", result.output)
428 self._lv_name = new_name
429 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
432 """Attach to an existing LV.
434 This method will try to see if an existing and active LV exists
435 which matches our name. If so, its major/minor will be
439 self.attached = False
440 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
441 "--units=m", "--nosuffix",
442 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
443 "vg_extent_size,stripes", self.dev_path])
445 logging.error("Can't find LV %s: %s, %s",
446 self.dev_path, result.fail_reason, result.output)
448 # the output can (and will) have multiple lines for multi-segment
449 # LVs, as the 'stripes' parameter is a segment one, so we take
450 # only the last entry, which is the one we're interested in; note
451 # that with LVM2 anyway the 'stripes' value must be constant
452 # across segments, so this is a no-op actually
453 out = result.stdout.splitlines()
454 if not out: # totally empty result? splitlines() returns at least
455 # one line for any non-empty string
456 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
458 out = out[-1].strip().rstrip(',')
461 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
464 status, major, minor, pe_size, stripes = out
466 logging.error("lvs lv_attr is not 6 characters (%s)", status)
472 except ValueError, err:
473 logging.error("lvs major/minor cannot be parsed: %s", str(err))
476 pe_size = int(float(pe_size))
477 except (TypeError, ValueError), err:
478 logging.error("Can't parse vg extent size: %s", err)
482 stripes = int(stripes)
483 except (TypeError, ValueError), err:
484 logging.error("Can't parse the number of stripes: %s", err)
489 self.pe_size = pe_size
490 self.stripe_count = stripes
491 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
497 """Assemble the device.
499 We always run `lvchange -ay` on the LV to ensure it's active before
500 use, as there were cases when xenvg was not active after boot
501 (also possibly after disk issues).
504 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
506 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
509 """Shutdown the device.
511 This is a no-op for the LV device type, as we don't deactivate the
517 def GetSyncStatus(self):
518 """Returns the sync status of the device.
520 If this device is a mirroring device, this function returns the
521 status of the mirror.
523 For logical volumes, sync_percent and estimated_time are always
524 None (no recovery in progress, as we don't handle the mirrored LV
525 case). The is_degraded parameter is the inverse of the ldisk
528 For the ldisk parameter, we check if the logical volume has the
529 'virtual' type, which means it's not backed by existing storage
530 anymore (read from it return I/O error). This happens after a
531 physical disk failure and subsequent 'vgreduce --removemissing' on
534 The status was already read in Attach, so we just return it.
536 @rtype: objects.BlockDevStatus
540 ldisk_status = constants.LDS_FAULTY
542 ldisk_status = constants.LDS_OKAY
544 return objects.BlockDevStatus(dev_path=self.dev_path,
549 is_degraded=self._degraded,
550 ldisk_status=ldisk_status)
552 def Open(self, force=False):
553 """Make the device ready for I/O.
555 This is a no-op for the LV device type.
561 """Notifies that the device will no longer be used for I/O.
563 This is a no-op for the LV device type.
568 def Snapshot(self, size):
569 """Create a snapshot copy of an lvm block device.
572 snap_name = self._lv_name + ".snap"
574 # remove existing snapshot if found
575 snap = LogicalVolume((self._vg_name, snap_name), None, size)
576 _IgnoreError(snap.Remove)
578 pvs_info = self.GetPVInfo(self._vg_name)
580 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
583 free_size, pv_name = pvs_info[0]
585 _ThrowError("Not enough free space: required %s,"
586 " available %s", size, free_size)
588 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
589 "-n%s" % snap_name, self.dev_path])
591 _ThrowError("command: %s error: %s - %s",
592 result.cmd, result.fail_reason, result.output)
596 def SetInfo(self, text):
597 """Update metadata with info text.
600 BlockDev.SetInfo(self, text)
602 # Replace invalid characters
603 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
604 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
606 # Only up to 128 characters are allowed
609 result = utils.RunCmd(["lvchange", "--addtag", text,
612 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
615 def Grow(self, amount):
616 """Grow the logical volume.
619 if self.pe_size is None or self.stripe_count is None:
620 if not self.Attach():
621 _ThrowError("Can't attach to LV during Grow()")
622 full_stripe_size = self.pe_size * self.stripe_count
623 rest = amount % full_stripe_size
625 amount += full_stripe_size - rest
626 # we try multiple algorithms since the 'best' ones might not have
627 # space available in the right place, but later ones might (since
628 # they have less constraints); also note that only recent LVM
630 for alloc_policy in "contiguous", "cling", "normal":
631 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
632 "-L", "+%dm" % amount, self.dev_path])
633 if not result.failed:
635 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
638 class DRBD8Status(object):
639 """A DRBD status representation class.
641 Note that this doesn't support unconfigured devices (cs:Unconfigured).
644 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
645 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
646 "\s+ds:([^/]+)/(\S+)\s+.*$")
647 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
648 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
650 CS_UNCONFIGURED = "Unconfigured"
651 CS_STANDALONE = "StandAlone"
652 CS_WFCONNECTION = "WFConnection"
653 CS_WFREPORTPARAMS = "WFReportParams"
654 CS_CONNECTED = "Connected"
655 CS_STARTINGSYNCS = "StartingSyncS"
656 CS_STARTINGSYNCT = "StartingSyncT"
657 CS_WFBITMAPS = "WFBitMapS"
658 CS_WFBITMAPT = "WFBitMapT"
659 CS_WFSYNCUUID = "WFSyncUUID"
660 CS_SYNCSOURCE = "SyncSource"
661 CS_SYNCTARGET = "SyncTarget"
662 CS_PAUSEDSYNCS = "PausedSyncS"
663 CS_PAUSEDSYNCT = "PausedSyncT"
664 CSET_SYNC = frozenset([
677 DS_DISKLESS = "Diskless"
678 DS_ATTACHING = "Attaching" # transient state
679 DS_FAILED = "Failed" # transient state, next: diskless
680 DS_NEGOTIATING = "Negotiating" # transient state
681 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
682 DS_OUTDATED = "Outdated"
683 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
684 DS_CONSISTENT = "Consistent"
685 DS_UPTODATE = "UpToDate" # normal state
687 RO_PRIMARY = "Primary"
688 RO_SECONDARY = "Secondary"
689 RO_UNKNOWN = "Unknown"
691 def __init__(self, procline):
692 u = self.UNCONF_RE.match(procline)
694 self.cstatus = self.CS_UNCONFIGURED
695 self.lrole = self.rrole = self.ldisk = self.rdisk = None
697 m = self.LINE_RE.match(procline)
699 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
700 self.cstatus = m.group(1)
701 self.lrole = m.group(2)
702 self.rrole = m.group(3)
703 self.ldisk = m.group(4)
704 self.rdisk = m.group(5)
706 # end reading of data from the LINE_RE or UNCONF_RE
708 self.is_standalone = self.cstatus == self.CS_STANDALONE
709 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
710 self.is_connected = self.cstatus == self.CS_CONNECTED
711 self.is_primary = self.lrole == self.RO_PRIMARY
712 self.is_secondary = self.lrole == self.RO_SECONDARY
713 self.peer_primary = self.rrole == self.RO_PRIMARY
714 self.peer_secondary = self.rrole == self.RO_SECONDARY
715 self.both_primary = self.is_primary and self.peer_primary
716 self.both_secondary = self.is_secondary and self.peer_secondary
718 self.is_diskless = self.ldisk == self.DS_DISKLESS
719 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
721 self.is_in_resync = self.cstatus in self.CSET_SYNC
722 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
724 m = self.SYNC_RE.match(procline)
726 self.sync_percent = float(m.group(1))
727 hours = int(m.group(2))
728 minutes = int(m.group(3))
729 seconds = int(m.group(4))
730 self.est_time = hours * 3600 + minutes * 60 + seconds
732 # we have (in this if branch) no percent information, but if
733 # we're resyncing we need to 'fake' a sync percent information,
734 # as this is how cmdlib determines if it makes sense to wait for
736 if self.is_in_resync:
737 self.sync_percent = 0
739 self.sync_percent = None
743 class BaseDRBD(BlockDev):
746 This class contains a few bits of common functionality between the
747 0.7 and 8.x versions of DRBD.
750 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
751 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
754 _ST_UNCONFIGURED = "Unconfigured"
755 _ST_WFCONNECTION = "WFConnection"
756 _ST_CONNECTED = "Connected"
758 _STATUS_FILE = "/proc/drbd"
761 def _GetProcData(filename=_STATUS_FILE):
762 """Return data from /proc/drbd.
766 stat = open(filename, "r")
768 data = stat.read().splitlines()
771 except EnvironmentError, err:
772 if err.errno == errno.ENOENT:
773 _ThrowError("The file %s cannot be opened, check if the module"
774 " is loaded (%s)", filename, str(err))
776 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
778 _ThrowError("Can't read any data from %s", filename)
782 def _MassageProcData(data):
783 """Transform the output of _GetProdData into a nicer form.
785 @return: a dictionary of minor: joined lines from /proc/drbd
789 lmatch = re.compile("^ *([0-9]+):.*$")
791 old_minor = old_line = None
793 lresult = lmatch.match(line)
794 if lresult is not None:
795 if old_minor is not None:
796 results[old_minor] = old_line
797 old_minor = int(lresult.group(1))
800 if old_minor is not None:
801 old_line += " " + line.strip()
803 if old_minor is not None:
804 results[old_minor] = old_line
808 def _GetVersion(cls):
809 """Return the DRBD version.
811 This will return a dict with keys:
817 - proto2 (only on drbd > 8.2.X)
820 proc_data = cls._GetProcData()
821 first_line = proc_data[0].strip()
822 version = cls._VERSION_RE.match(first_line)
824 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
827 values = version.groups()
828 retval = {'k_major': int(values[0]),
829 'k_minor': int(values[1]),
830 'k_point': int(values[2]),
831 'api': int(values[3]),
832 'proto': int(values[4]),
834 if values[5] is not None:
835 retval['proto2'] = values[5]
841 """Return the path to a drbd device for a given minor.
844 return "/dev/drbd%d" % minor
847 def GetUsedDevs(cls):
848 """Compute the list of used DRBD devices.
851 data = cls._GetProcData()
854 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
856 match = valid_line.match(line)
859 minor = int(match.group(1))
860 state = match.group(2)
861 if state == cls._ST_UNCONFIGURED:
863 used_devs[minor] = state, line
867 def _SetFromMinor(self, minor):
868 """Set our parameters based on the given minor.
870 This sets our minor variable and our dev_path.
874 self.minor = self.dev_path = None
875 self.attached = False
878 self.dev_path = self._DevPath(minor)
882 def _CheckMetaSize(meta_device):
883 """Check if the given meta device looks like a valid one.
885 This currently only check the size, which must be around
889 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
891 _ThrowError("Failed to get device size: %s - %s",
892 result.fail_reason, result.output)
894 sectors = int(result.stdout)
896 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
897 bytes = sectors * 512
898 if bytes < 128 * 1024 * 1024: # less than 128MiB
899 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
900 # the maximum *valid* size of the meta device when living on top
901 # of LVM is hard to compute: it depends on the number of stripes
902 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
903 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
904 # size meta device; as such, we restrict it to 1GB (a little bit
905 # too generous, but making assumptions about PE size is hard)
906 if bytes > 1024 * 1024 * 1024:
907 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
909 def Rename(self, new_id):
912 This is not supported for drbd devices.
915 raise errors.ProgrammerError("Can't rename a drbd device")
918 class DRBD8(BaseDRBD):
919 """DRBD v8.x block device.
921 This implements the local host part of the DRBD device, i.e. it
922 doesn't do anything to the supposed peer. If you need a fully
923 connected DRBD pair, you need to use this class on both hosts.
925 The unique_id for the drbd device is the (local_ip, local_port,
926 remote_ip, remote_port) tuple, and it must have two children: the
927 data device and the meta_device. The meta device is checked for
928 valid size and is zeroed on create.
935 _NET_RECONFIG_TIMEOUT = 60
937 def __init__(self, unique_id, children, size):
938 if children and children.count(None) > 0:
940 super(DRBD8, self).__init__(unique_id, children, size)
941 self.major = self._DRBD_MAJOR
942 version = self._GetVersion()
943 if version['k_major'] != 8 :
944 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
945 " usage: kernel is %s.%s, ganeti wants 8.x",
946 version['k_major'], version['k_minor'])
948 if len(children) not in (0, 2):
949 raise ValueError("Invalid configuration data %s" % str(children))
950 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
951 raise ValueError("Invalid configuration data %s" % str(unique_id))
952 (self._lhost, self._lport,
953 self._rhost, self._rport,
954 self._aminor, self._secret) = unique_id
955 if (self._lhost is not None and self._lhost == self._rhost and
956 self._lport == self._rport):
957 raise ValueError("Invalid configuration data, same local/remote %s" %
962 def _InitMeta(cls, minor, dev_path):
963 """Initialize a meta device.
965 This will not work if the given minor is in use.
968 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
969 "v08", dev_path, "0", "create-md"])
971 _ThrowError("Can't initialize meta device: %s", result.output)
974 def _FindUnusedMinor(cls):
975 """Find an unused DRBD device.
977 This is specific to 8.x as the minors are allocated dynamically,
978 so non-existing numbers up to a max minor count are actually free.
981 data = cls._GetProcData()
983 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
984 used_line = re.compile("^ *([0-9]+): cs:")
987 match = unused_line.match(line)
989 return int(match.group(1))
990 match = used_line.match(line)
992 minor = int(match.group(1))
993 highest = max(highest, minor)
994 if highest is None: # there are no minors in use at all
996 if highest >= cls._MAX_MINORS:
997 logging.error("Error: no free drbd minors!")
998 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1002 def _GetShowParser(cls):
1003 """Return a parser for `drbd show` output.
1005 This will either create or return an already-create parser for the
1006 output of the command `drbd show`.
1009 if cls._PARSE_SHOW is not None:
1010 return cls._PARSE_SHOW
1013 lbrace = pyp.Literal("{").suppress()
1014 rbrace = pyp.Literal("}").suppress()
1015 semi = pyp.Literal(";").suppress()
1016 # this also converts the value to an int
1017 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1019 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1020 defa = pyp.Literal("_is_default").suppress()
1021 dbl_quote = pyp.Literal('"').suppress()
1023 keyword = pyp.Word(pyp.alphanums + '-')
1026 value = pyp.Word(pyp.alphanums + '_-/.:')
1027 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1028 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1029 pyp.Optional(pyp.Literal("ipv6")).suppress())
1030 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1031 pyp.Literal(':').suppress() + number)
1032 # meta device, extended syntax
1033 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1034 number + pyp.Word(']').suppress())
1035 # device name, extended syntax
1036 device_value = pyp.Literal("minor").suppress() + number
1039 stmt = (~rbrace + keyword + ~lbrace +
1040 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1042 pyp.Optional(defa) + semi +
1043 pyp.Optional(pyp.restOfLine).suppress())
1046 section_name = pyp.Word(pyp.alphas + '_')
1047 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1049 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1052 cls._PARSE_SHOW = bnf
1057 def _GetShowData(cls, minor):
1058 """Return the `drbdsetup show` data for a minor.
1061 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1063 logging.error("Can't display the drbd config: %s - %s",
1064 result.fail_reason, result.output)
1066 return result.stdout
1069 def _GetDevInfo(cls, out):
1070 """Parse details about a given DRBD minor.
1072 This return, if available, the local backing device (as a path)
1073 and the local and remote (ip, port) information from a string
1074 containing the output of the `drbdsetup show` command as returned
1082 bnf = cls._GetShowParser()
1086 results = bnf.parseString(out)
1087 except pyp.ParseException, err:
1088 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1090 # and massage the results into our desired format
1091 for section in results:
1093 if sname == "_this_host":
1094 for lst in section[1:]:
1095 if lst[0] == "disk":
1096 data["local_dev"] = lst[1]
1097 elif lst[0] == "meta-disk":
1098 data["meta_dev"] = lst[1]
1099 data["meta_index"] = lst[2]
1100 elif lst[0] == "address":
1101 data["local_addr"] = tuple(lst[1:])
1102 elif sname == "_remote_host":
1103 for lst in section[1:]:
1104 if lst[0] == "address":
1105 data["remote_addr"] = tuple(lst[1:])
1108 def _MatchesLocal(self, info):
1109 """Test if our local config matches with an existing device.
1111 The parameter should be as returned from `_GetDevInfo()`. This
1112 method tests if our local backing device is the same as the one in
1113 the info parameter, in effect testing if we look like the given
1118 backend, meta = self._children
1120 backend = meta = None
1122 if backend is not None:
1123 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1125 retval = ("local_dev" not in info)
1127 if meta is not None:
1128 retval = retval and ("meta_dev" in info and
1129 info["meta_dev"] == meta.dev_path)
1130 retval = retval and ("meta_index" in info and
1131 info["meta_index"] == 0)
1133 retval = retval and ("meta_dev" not in info and
1134 "meta_index" not in info)
1137 def _MatchesNet(self, info):
1138 """Test if our network config matches with an existing device.
1140 The parameter should be as returned from `_GetDevInfo()`. This
1141 method tests if our network configuration is the same as the one
1142 in the info parameter, in effect testing if we look like the given
1146 if (((self._lhost is None and not ("local_addr" in info)) and
1147 (self._rhost is None and not ("remote_addr" in info)))):
1150 if self._lhost is None:
1153 if not ("local_addr" in info and
1154 "remote_addr" in info):
1157 retval = (info["local_addr"] == (self._lhost, self._lport))
1158 retval = (retval and
1159 info["remote_addr"] == (self._rhost, self._rport))
1163 def _AssembleLocal(cls, minor, backend, meta, size):
1164 """Configure the local part of a DRBD device.
1167 args = ["drbdsetup", cls._DevPath(minor), "disk",
1172 result = utils.RunCmd(args)
1174 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1177 def _AssembleNet(cls, minor, net_info, protocol,
1178 dual_pri=False, hmac=None, secret=None):
1179 """Configure the network part of the device.
1182 lhost, lport, rhost, rport = net_info
1183 if None in net_info:
1184 # we don't want network connection and actually want to make
1186 cls._ShutdownNet(minor)
1189 # Workaround for a race condition. When DRBD is doing its dance to
1190 # establish a connection with its peer, it also sends the
1191 # synchronization speed over the wire. In some cases setting the
1192 # sync speed only after setting up both sides can race with DRBD
1193 # connecting, hence we set it here before telling DRBD anything
1195 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1197 args = ["drbdsetup", cls._DevPath(minor), "net",
1198 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1199 "-A", "discard-zero-changes",
1206 args.extend(["-a", hmac, "-x", secret])
1207 result = utils.RunCmd(args)
1209 _ThrowError("drbd%d: can't setup network: %s - %s",
1210 minor, result.fail_reason, result.output)
1212 timeout = time.time() + 10
1214 while time.time() < timeout:
1215 info = cls._GetDevInfo(cls._GetShowData(minor))
1216 if not "local_addr" in info or not "remote_addr" in info:
1219 if (info["local_addr"] != (lhost, lport) or
1220 info["remote_addr"] != (rhost, rport)):
1226 _ThrowError("drbd%d: timeout while configuring network", minor)
1228 def AddChildren(self, devices):
1229 """Add a disk to the DRBD device.
1232 if self.minor is None:
1233 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1235 if len(devices) != 2:
1236 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1237 info = self._GetDevInfo(self._GetShowData(self.minor))
1238 if "local_dev" in info:
1239 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1240 backend, meta = devices
1241 if backend.dev_path is None or meta.dev_path is None:
1242 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1245 self._CheckMetaSize(meta.dev_path)
1246 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1248 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1249 self._children = devices
1251 def RemoveChildren(self, devices):
1252 """Detach the drbd device from local storage.
1255 if self.minor is None:
1256 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1258 # early return if we don't actually have backing storage
1259 info = self._GetDevInfo(self._GetShowData(self.minor))
1260 if "local_dev" not in info:
1262 if len(self._children) != 2:
1263 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1265 if self._children.count(None) == 2: # we don't actually have children :)
1266 logging.warning("drbd%d: requested detach while detached", self.minor)
1268 if len(devices) != 2:
1269 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1270 for child, dev in zip(self._children, devices):
1271 if dev != child.dev_path:
1272 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1273 " RemoveChildren", self.minor, dev, child.dev_path)
1275 self._ShutdownLocal(self.minor)
1279 def _SetMinorSyncSpeed(cls, minor, kbytes):
1280 """Set the speed of the DRBD syncer.
1282 This is the low-level implementation.
1285 @param minor: the drbd minor whose settings we change
1287 @param kbytes: the speed in kbytes/second
1289 @return: the success of the operation
1292 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1293 "-r", "%d" % kbytes, "--create-device"])
1295 logging.error("Can't change syncer rate: %s - %s",
1296 result.fail_reason, result.output)
1297 return not result.failed
1299 def SetSyncSpeed(self, kbytes):
1300 """Set the speed of the DRBD syncer.
1303 @param kbytes: the speed in kbytes/second
1305 @return: the success of the operation
1308 if self.minor is None:
1309 logging.info("Not attached during SetSyncSpeed")
1311 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1312 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1314 def GetProcStatus(self):
1315 """Return device data from /proc.
1318 if self.minor is None:
1319 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1320 proc_info = self._MassageProcData(self._GetProcData())
1321 if self.minor not in proc_info:
1322 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1323 return DRBD8Status(proc_info[self.minor])
1325 def GetSyncStatus(self):
1326 """Returns the sync status of the device.
1329 If sync_percent is None, it means all is ok
1330 If estimated_time is None, it means we can't estimate
1331 the time needed, otherwise it's the time left in seconds.
1334 We set the is_degraded parameter to True on two conditions:
1335 network not connected or local disk missing.
1337 We compute the ldisk parameter based on whether we have a local
1340 @rtype: objects.BlockDevStatus
1343 if self.minor is None and not self.Attach():
1344 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1346 stats = self.GetProcStatus()
1347 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1349 if stats.is_disk_uptodate:
1350 ldisk_status = constants.LDS_OKAY
1351 elif stats.is_diskless:
1352 ldisk_status = constants.LDS_FAULTY
1354 ldisk_status = constants.LDS_UNKNOWN
1356 return objects.BlockDevStatus(dev_path=self.dev_path,
1359 sync_percent=stats.sync_percent,
1360 estimated_time=stats.est_time,
1361 is_degraded=is_degraded,
1362 ldisk_status=ldisk_status)
1364 def Open(self, force=False):
1365 """Make the local state primary.
1367 If the 'force' parameter is given, the '-o' option is passed to
1368 drbdsetup. Since this is a potentially dangerous operation, the
1369 force flag should be only given after creation, when it actually
1373 if self.minor is None and not self.Attach():
1374 logging.error("DRBD cannot attach to a device during open")
1376 cmd = ["drbdsetup", self.dev_path, "primary"]
1379 result = utils.RunCmd(cmd)
1381 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1385 """Make the local state secondary.
1387 This will, of course, fail if the device is in use.
1390 if self.minor is None and not self.Attach():
1391 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1392 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1394 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1395 self.minor, result.output)
1397 def DisconnectNet(self):
1398 """Removes network configuration.
1400 This method shutdowns the network side of the device.
1402 The method will wait up to a hardcoded timeout for the device to
1403 go into standalone after the 'disconnect' command before
1404 re-configuring it, as sometimes it takes a while for the
1405 disconnect to actually propagate and thus we might issue a 'net'
1406 command while the device is still connected. If the device will
1407 still be attached to the network and we time out, we raise an
1411 if self.minor is None:
1412 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1414 if None in (self._lhost, self._lport, self._rhost, self._rport):
1415 _ThrowError("drbd%d: DRBD disk missing network info in"
1416 " DisconnectNet()", self.minor)
1418 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1419 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1420 sleep_time = 0.100 # we start the retry time at 100 milliseconds
1421 while time.time() < timeout_limit:
1422 status = self.GetProcStatus()
1423 if status.is_standalone:
1425 # retry the disconnect, it seems possible that due to a
1426 # well-time disconnect on the peer, my disconnect command might
1427 # be ignored and forgotten
1428 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1430 time.sleep(sleep_time)
1431 sleep_time = min(2, sleep_time * 1.5)
1433 if not status.is_standalone:
1434 if ever_disconnected:
1435 msg = ("drbd%d: device did not react to the"
1436 " 'disconnect' command in a timely manner")
1438 msg = "drbd%d: can't shutdown network, even after multiple retries"
1439 _ThrowError(msg, self.minor)
1441 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1442 if reconfig_time > 15: # hardcoded alert limit
1443 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1444 self.minor, reconfig_time)
1446 def AttachNet(self, multimaster):
1447 """Reconnects the network.
1449 This method connects the network side of the device with a
1450 specified multi-master flag. The device needs to be 'Standalone'
1451 but have valid network configuration data.
1454 - multimaster: init the network in dual-primary mode
1457 if self.minor is None:
1458 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1460 if None in (self._lhost, self._lport, self._rhost, self._rport):
1461 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1463 status = self.GetProcStatus()
1465 if not status.is_standalone:
1466 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1468 self._AssembleNet(self.minor,
1469 (self._lhost, self._lport, self._rhost, self._rport),
1470 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1471 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1474 """Check if our minor is configured.
1476 This doesn't do any device configurations - it only checks if the
1477 minor is in a state different from Unconfigured.
1479 Note that this function will not change the state of the system in
1480 any way (except in case of side-effects caused by reading from
1484 used_devs = self.GetUsedDevs()
1485 if self._aminor in used_devs:
1486 minor = self._aminor
1490 self._SetFromMinor(minor)
1491 return minor is not None
1494 """Assemble the drbd.
1497 - if we have a configured device, we try to ensure that it matches
1499 - if not, we create it from zero
1502 super(DRBD8, self).Assemble()
1505 if self.minor is None:
1506 # local device completely unconfigured
1507 self._FastAssemble()
1509 # we have to recheck the local and network status and try to fix
1511 self._SlowAssemble()
1513 def _SlowAssemble(self):
1514 """Assembles the DRBD device from a (partially) configured device.
1516 In case of partially attached (local device matches but no network
1517 setup), we perform the network attach. If successful, we re-test
1518 the attach if can return success.
1521 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1522 for minor in (self._aminor,):
1523 info = self._GetDevInfo(self._GetShowData(minor))
1524 match_l = self._MatchesLocal(info)
1525 match_r = self._MatchesNet(info)
1527 if match_l and match_r:
1528 # everything matches
1531 if match_l and not match_r and "local_addr" not in info:
1532 # disk matches, but not attached to network, attach and recheck
1533 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1534 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1535 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1538 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1539 " show' disagrees", minor)
1541 if match_r and "local_dev" not in info:
1542 # no local disk, but network attached and it matches
1543 self._AssembleLocal(minor, self._children[0].dev_path,
1544 self._children[1].dev_path, self.size)
1545 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1548 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1549 " show' disagrees", minor)
1551 # this case must be considered only if we actually have local
1552 # storage, i.e. not in diskless mode, because all diskless
1553 # devices are equal from the point of view of local
1555 if (match_l and "local_dev" in info and
1556 not match_r and "local_addr" in info):
1557 # strange case - the device network part points to somewhere
1558 # else, even though its local storage is ours; as we own the
1559 # drbd space, we try to disconnect from the remote peer and
1560 # reconnect to our correct one
1562 self._ShutdownNet(minor)
1563 except errors.BlockDeviceError, err:
1564 _ThrowError("drbd%d: device has correct local storage, wrong"
1565 " remote peer and is unable to disconnect in order"
1566 " to attach to the correct peer: %s", minor, str(err))
1567 # note: _AssembleNet also handles the case when we don't want
1568 # local storage (i.e. one or more of the _[lr](host|port) is
1570 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1571 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1572 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1575 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1576 " show' disagrees", minor)
1581 self._SetFromMinor(minor)
1583 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1586 def _FastAssemble(self):
1587 """Assemble the drbd device from zero.
1589 This is run when in Assemble we detect our minor is unused.
1592 minor = self._aminor
1593 if self._children and self._children[0] and self._children[1]:
1594 self._AssembleLocal(minor, self._children[0].dev_path,
1595 self._children[1].dev_path, self.size)
1596 if self._lhost and self._lport and self._rhost and self._rport:
1597 self._AssembleNet(minor,
1598 (self._lhost, self._lport, self._rhost, self._rport),
1599 constants.DRBD_NET_PROTOCOL,
1600 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1601 self._SetFromMinor(minor)
1604 def _ShutdownLocal(cls, minor):
1605 """Detach from the local device.
1607 I/Os will continue to be served from the remote device. If we
1608 don't have a remote device, this operation will fail.
1611 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1613 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1616 def _ShutdownNet(cls, minor):
1617 """Disconnect from the remote peer.
1619 This fails if we don't have a local device.
1622 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1624 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1627 def _ShutdownAll(cls, minor):
1628 """Deactivate the device.
1630 This will, of course, fail if the device is in use.
1633 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1635 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1636 minor, result.output)
1639 """Shutdown the DRBD device.
1642 if self.minor is None and not self.Attach():
1643 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1647 self.dev_path = None
1648 self._ShutdownAll(minor)
1651 """Stub remove for DRBD devices.
1657 def Create(cls, unique_id, children, size):
1658 """Create a new DRBD8 device.
1660 Since DRBD devices are not created per se, just assembled, this
1661 function only initializes the metadata.
1664 if len(children) != 2:
1665 raise errors.ProgrammerError("Invalid setup for the drbd device")
1666 # check that the minor is unused
1667 aminor = unique_id[4]
1668 proc_info = cls._MassageProcData(cls._GetProcData())
1669 if aminor in proc_info:
1670 status = DRBD8Status(proc_info[aminor])
1671 in_use = status.is_in_use
1675 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1678 if not meta.Attach():
1679 _ThrowError("drbd%d: can't attach to meta device '%s'",
1681 cls._CheckMetaSize(meta.dev_path)
1682 cls._InitMeta(aminor, meta.dev_path)
1683 return cls(unique_id, children, size)
1685 def Grow(self, amount):
1686 """Resize the DRBD device and its backing storage.
1689 if self.minor is None:
1690 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1691 if len(self._children) != 2 or None in self._children:
1692 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1693 self._children[0].Grow(amount)
1694 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1695 "%dm" % (self.size + amount)])
1697 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1700 class FileStorage(BlockDev):
1703 This class represents the a file storage backend device.
1705 The unique_id for the file device is a (file_driver, file_path) tuple.
1708 def __init__(self, unique_id, children, size):
1709 """Initalizes a file device backend.
1713 raise errors.BlockDeviceError("Invalid setup for file device")
1714 super(FileStorage, self).__init__(unique_id, children, size)
1715 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1716 raise ValueError("Invalid configuration data %s" % str(unique_id))
1717 self.driver = unique_id[0]
1718 self.dev_path = unique_id[1]
1722 """Assemble the device.
1724 Checks whether the file device exists, raises BlockDeviceError otherwise.
1727 if not os.path.exists(self.dev_path):
1728 _ThrowError("File device '%s' does not exist" % self.dev_path)
1731 """Shutdown the device.
1733 This is a no-op for the file type, as we don't deactivate
1734 the file on shutdown.
1739 def Open(self, force=False):
1740 """Make the device ready for I/O.
1742 This is a no-op for the file type.
1748 """Notifies that the device will no longer be used for I/O.
1750 This is a no-op for the file type.
1756 """Remove the file backing the block device.
1759 @return: True if the removal was successful
1763 os.remove(self.dev_path)
1764 except OSError, err:
1765 if err.errno != errno.ENOENT:
1766 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1769 """Attach to an existing file.
1771 Check if this file already exists.
1774 @return: True if file exists
1777 self.attached = os.path.exists(self.dev_path)
1778 return self.attached
1781 def Create(cls, unique_id, children, size):
1782 """Create a new file.
1784 @param size: the size of file in MiB
1786 @rtype: L{bdev.FileStorage}
1787 @return: an instance of FileStorage
1790 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1791 raise ValueError("Invalid configuration data %s" % str(unique_id))
1792 dev_path = unique_id[1]
1793 if os.path.exists(dev_path):
1794 _ThrowError("File already existing: %s", dev_path)
1796 f = open(dev_path, 'w')
1797 f.truncate(size * 1024 * 1024)
1799 except IOError, err:
1800 _ThrowError("Error in file creation: %", str(err))
1802 return FileStorage(unique_id, children, size)
1806 constants.LD_LV: LogicalVolume,
1807 constants.LD_DRBD8: DRBD8,
1808 constants.LD_FILE: FileStorage,
1812 def FindDevice(dev_type, unique_id, children, size):
1813 """Search for an existing, assembled device.
1815 This will succeed only if the device exists and is assembled, but it
1816 does not do any actions in order to activate the device.
1819 if dev_type not in DEV_MAP:
1820 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1821 device = DEV_MAP[dev_type](unique_id, children, size)
1822 if not device.attached:
1827 def Assemble(dev_type, unique_id, children, size):
1828 """Try to attach or assemble an existing device.
1830 This will attach to assemble the device, as needed, to bring it
1831 fully up. It must be safe to run on already-assembled devices.
1834 if dev_type not in DEV_MAP:
1835 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1836 device = DEV_MAP[dev_type](unique_id, children, size)
1841 def Create(dev_type, unique_id, children, size):
1845 if dev_type not in DEV_MAP:
1846 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1847 device = DEV_MAP[dev_type].Create(unique_id, children, size)