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
36 def _IgnoreError(fn, *args, **kwargs):
37 """Executes the given function, ignoring BlockDeviceErrors.
39 This is used in order to simplify the execution of cleanup or
43 @return: True when fn didn't raise an exception, False otherwise
49 except errors.BlockDeviceError, err:
50 logging.warning("Caught BlockDeviceError but ignoring: %s" % str(err))
54 def _ThrowError(msg, *args):
55 """Log an error to the node daemon and the raise an exception.
58 @param msg: the text of the exception
59 @raise errors.BlockDeviceError
65 raise errors.BlockDeviceError(msg)
68 class BlockDev(object):
69 """Block device abstract class.
71 A block device can be in the following states:
72 - not existing on the system, and by `Create()` it goes into:
73 - existing but not setup/not active, and by `Assemble()` goes into:
74 - active read-write and by `Open()` it goes into
75 - online (=used, or ready for use)
77 A device can also be online but read-only, however we are not using
78 the readonly state (LV has it, if needed in the future) and we are
79 usually looking at this like at a stack, so it's easier to
80 conceptualise the transition from not-existing to online and back
83 The many different states of the device are due to the fact that we
84 need to cover many device types:
85 - logical volumes are created, lvchange -a y $lv, and used
86 - drbd devices are attached to a local disk/remote peer and made primary
88 A block device is identified by three items:
89 - the /dev path of the device (dynamic)
90 - a unique ID of the device (static)
91 - it's major/minor pair (dynamic)
93 Not all devices implement both the first two as distinct items. LVM
94 logical volumes have their unique ID (the pair volume group, logical
95 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
96 the /dev path is again dynamic and the unique id is the pair (host1,
99 You can get to a device in two ways:
100 - creating the (real) device, which returns you
101 an attached instance (lvcreate)
102 - attaching of a python instance to an existing (real) device
104 The second point, the attachement to a device, is different
105 depending on whether the device is assembled or not. At init() time,
106 we search for a device with the same unique_id as us. If found,
107 good. It also means that the device is already assembled. If not,
108 after assembly we'll have our correct major/minor.
111 def __init__(self, unique_id, children, size):
112 self._children = children
114 self.unique_id = unique_id
117 self.attached = False
121 """Assemble the device from its components.
123 Implementations of this method by child classes must ensure that:
124 - after the device has been assembled, it knows its major/minor
125 numbers; this allows other devices (usually parents) to probe
126 correctly for their children
127 - calling this method on an existing, in-use device is safe
128 - if the device is already configured (and in an OK state),
129 this method is idempotent
135 """Find a device which matches our config and attach to it.
138 raise NotImplementedError
141 """Notifies that the device will no longer be used for I/O.
144 raise NotImplementedError
147 def Create(cls, unique_id, children, size):
148 """Create the device.
150 If the device cannot be created, it will return None
151 instead. Error messages go to the logging system.
153 Note that for some devices, the unique_id is used, and for other,
154 the children. The idea is that these two, taken together, are
155 enough for both creation and assembly (later).
158 raise NotImplementedError
161 """Remove this device.
163 This makes sense only for some of the device types: LV and file
164 storeage. Also note that if the device can't attach, the removal
168 raise NotImplementedError
170 def Rename(self, new_id):
171 """Rename this device.
173 This may or may not make sense for a given device type.
176 raise NotImplementedError
178 def Open(self, force=False):
179 """Make the device ready for use.
181 This makes the device ready for I/O. For now, just the DRBD
184 The force parameter signifies that if the device has any kind of
185 --force thing, it should be used, we know what we are doing.
188 raise NotImplementedError
191 """Shut down the device, freeing its children.
193 This undoes the `Assemble()` work, except for the child
194 assembling; as such, the children on the device are still
195 assembled after this call.
198 raise NotImplementedError
200 def SetSyncSpeed(self, speed):
201 """Adjust the sync speed of the mirror.
203 In case this is not a mirroring device, this is no-op.
208 for child in self._children:
209 result = result and child.SetSyncSpeed(speed)
212 def GetSyncStatus(self):
213 """Returns the sync status of the device.
215 If this device is a mirroring device, this function returns the
216 status of the mirror.
218 If sync_percent is None, it means the device is not syncing.
220 If estimated_time is None, it means we can't estimate
221 the time needed, otherwise it's the time left in seconds.
223 If is_degraded is True, it means the device is missing
224 redundancy. This is usually a sign that something went wrong in
225 the device setup, if sync_percent is None.
227 The ldisk parameter represents the degradation of the local
228 data. This is only valid for some devices, the rest will always
229 return False (not degraded).
232 @return: (sync_percent, estimated_time, is_degraded, ldisk)
235 return None, None, False, False
238 def CombinedSyncStatus(self):
239 """Calculate the mirror status recursively for our children.
241 The return value is the same as for `GetSyncStatus()` except the
242 minimum percent and maximum time are calculated across our
246 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
248 for child in self._children:
249 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
250 if min_percent is None:
251 min_percent = c_percent
252 elif c_percent is not None:
253 min_percent = min(min_percent, c_percent)
256 elif c_time is not None:
257 max_time = max(max_time, c_time)
258 is_degraded = is_degraded or c_degraded
259 ldisk = ldisk or c_ldisk
260 return min_percent, max_time, is_degraded, ldisk
263 def SetInfo(self, text):
264 """Update metadata with info text.
266 Only supported for some device types.
269 for child in self._children:
272 def Grow(self, amount):
273 """Grow the block device.
275 @param amount: the amount (in mebibytes) to grow with
278 raise NotImplementedError
280 def GetActualSize(self):
281 """Return the actual disk size.
283 @note: the device needs to be active when this is called
286 assert self.attached, "BlockDevice not attached in GetActualSize()"
287 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
289 _ThrowError("blockdev failed (%s): %s",
290 result.fail_reason, result.output)
292 sz = int(result.output.strip())
293 except (ValueError, TypeError), err:
294 _ThrowError("Failed to parse blockdev output: %s", str(err))
298 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
299 (self.__class__, self.unique_id, self._children,
300 self.major, self.minor, self.dev_path))
303 class LogicalVolume(BlockDev):
304 """Logical Volume block device.
307 def __init__(self, unique_id, children, size):
308 """Attaches to a LV device.
310 The unique_id is a tuple (vg_name, lv_name)
313 super(LogicalVolume, self).__init__(unique_id, children, size)
314 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
315 raise ValueError("Invalid configuration data %s" % str(unique_id))
316 self._vg_name, self._lv_name = unique_id
317 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
318 self._degraded = True
319 self.major = self.minor = self.pe_size = self.stripe_count = None
323 def Create(cls, unique_id, children, size):
324 """Create a new logical volume.
327 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
328 raise errors.ProgrammerError("Invalid configuration data %s" %
330 vg_name, lv_name = unique_id
331 pvs_info = cls.GetPVInfo(vg_name)
333 _ThrowError("Can't compute PV info for vg %s", vg_name)
337 pvlist = [ pv[1] for pv in pvs_info ]
338 free_size = sum([ pv[0] for pv in pvs_info ])
339 current_pvs = len(pvlist)
340 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
342 # The size constraint should have been checked from the master before
343 # calling the create function.
345 _ThrowError("Not enough free space: required %s,"
346 " available %s", size, free_size)
347 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
348 # If the free space is not well distributed, we won't be able to
349 # create an optimally-striped volume; in that case, we want to try
350 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
352 for stripes_arg in range(stripes, 0, -1):
353 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
354 if not result.failed:
357 _ThrowError("LV create failed (%s): %s",
358 result.fail_reason, result.output)
359 return LogicalVolume(unique_id, children, size)
362 def GetPVInfo(vg_name):
363 """Get the free space info for PVs in a volume group.
365 @param vg_name: the volume group name
368 @return: list of tuples (free_space, name) with free_space in mebibytes
371 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
372 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
374 result = utils.RunCmd(command)
376 logging.error("Can't get the PV information: %s - %s",
377 result.fail_reason, result.output)
380 for line in result.stdout.splitlines():
381 fields = line.strip().split(':')
383 logging.error("Can't parse pvs output: line '%s'", line)
385 # skip over pvs from another vg or ones which are not allocatable
386 if fields[1] != vg_name or fields[3][0] != 'a':
388 data.append((float(fields[2]), fields[0]))
393 """Remove this logical volume.
396 if not self.minor and not self.Attach():
397 # the LV does not exist
399 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
400 (self._vg_name, self._lv_name)])
402 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
404 def Rename(self, new_id):
405 """Rename this logical volume.
408 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
409 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
410 new_vg, new_name = new_id
411 if new_vg != self._vg_name:
412 raise errors.ProgrammerError("Can't move a logical volume across"
413 " volume groups (from %s to to %s)" %
414 (self._vg_name, new_vg))
415 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
417 _ThrowError("Failed to rename the logical volume: %s", result.output)
418 self._lv_name = new_name
419 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
422 """Attach to an existing LV.
424 This method will try to see if an existing and active LV exists
425 which matches our name. If so, its major/minor will be
429 self.attached = False
430 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
431 "--units=m", "--nosuffix",
432 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
433 "vg_extent_size,stripes", self.dev_path])
435 logging.error("Can't find LV %s: %s, %s",
436 self.dev_path, result.fail_reason, result.output)
438 # the output can (and will) have multiple lines for multi-segment
439 # LVs, as the 'stripes' parameter is a segment one, so we take
440 # only the last entry, which is the one we're interested in; note
441 # that with LVM2 anyway the 'stripes' value must be constant
442 # across segments, so this is a no-op actually
443 out = result.stdout.splitlines()
444 if not out: # totally empty result? splitlines() returns at least
445 # one line for any non-empty string
446 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
448 out = out[-1].strip().rstrip(',')
451 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
454 status, major, minor, pe_size, stripes = out
456 logging.error("lvs lv_attr is not 6 characters (%s)", status)
462 except ValueError, err:
463 logging.error("lvs major/minor cannot be parsed: %s", str(err))
466 pe_size = int(float(pe_size))
467 except (TypeError, ValueError), err:
468 logging.error("Can't parse vg extent size: %s", err)
472 stripes = int(stripes)
473 except (TypeError, ValueError), err:
474 logging.error("Can't parse the number of stripes: %s", err)
479 self.pe_size = pe_size
480 self.stripe_count = stripes
481 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
487 """Assemble the device.
489 We alway run `lvchange -ay` on the LV to ensure it's active before
490 use, as there were cases when xenvg was not active after boot
491 (also possibly after disk issues).
494 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
496 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
499 """Shutdown the device.
501 This is a no-op for the LV device type, as we don't deactivate the
507 def GetSyncStatus(self):
508 """Returns the sync status of the device.
510 If this device is a mirroring device, this function returns the
511 status of the mirror.
513 For logical volumes, sync_percent and estimated_time are always
514 None (no recovery in progress, as we don't handle the mirrored LV
515 case). The is_degraded parameter is the inverse of the ldisk
518 For the ldisk parameter, we check if the logical volume has the
519 'virtual' type, which means it's not backed by existing storage
520 anymore (read from it return I/O error). This happens after a
521 physical disk failure and subsequent 'vgreduce --removemissing' on
524 The status was already read in Attach, so we just return it.
527 @return: (sync_percent, estimated_time, is_degraded, ldisk)
530 return None, None, self._degraded, self._degraded
532 def Open(self, force=False):
533 """Make the device ready for I/O.
535 This is a no-op for the LV device type.
541 """Notifies that the device will no longer be used for I/O.
543 This is a no-op for the LV device type.
548 def Snapshot(self, size):
549 """Create a snapshot copy of an lvm block device.
552 snap_name = self._lv_name + ".snap"
554 # remove existing snapshot if found
555 snap = LogicalVolume((self._vg_name, snap_name), None, size)
556 _IgnoreError(snap.Remove)
558 pvs_info = self.GetPVInfo(self._vg_name)
560 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
563 free_size, pv_name = pvs_info[0]
565 _ThrowError("Not enough free space: required %s,"
566 " available %s", size, free_size)
568 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
569 "-n%s" % snap_name, self.dev_path])
571 _ThrowError("command: %s error: %s - %s",
572 result.cmd, result.fail_reason, result.output)
576 def SetInfo(self, text):
577 """Update metadata with info text.
580 BlockDev.SetInfo(self, text)
582 # Replace invalid characters
583 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
584 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
586 # Only up to 128 characters are allowed
589 result = utils.RunCmd(["lvchange", "--addtag", text,
592 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
595 def Grow(self, amount):
596 """Grow the logical volume.
599 if self.pe_size is None or self.stripe_count is None:
600 if not self.Attach():
601 _ThrowError("Can't attach to LV during Grow()")
602 full_stripe_size = self.pe_size * self.stripe_count
603 rest = amount % full_stripe_size
605 amount += full_stripe_size - rest
606 # we try multiple algorithms since the 'best' ones might not have
607 # space available in the right place, but later ones might (since
608 # they have less constraints); also note that only recent LVM
610 for alloc_policy in "contiguous", "cling", "normal":
611 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
612 "-L", "+%dm" % amount, self.dev_path])
613 if not result.failed:
615 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
618 class DRBD8Status(object):
619 """A DRBD status representation class.
621 Note that this doesn't support unconfigured devices (cs:Unconfigured).
624 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
625 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
626 "\s+ds:([^/]+)/(\S+)\s+.*$")
627 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
628 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
630 CS_UNCONFIGURED = "Unconfigured"
631 CS_STANDALONE = "StandAlone"
632 CS_WFCONNECTION = "WFConnection"
633 CS_WFREPORTPARAMS = "WFReportParams"
634 CS_CONNECTED = "Connected"
635 CS_STARTINGSYNCS = "StartingSyncS"
636 CS_STARTINGSYNCT = "StartingSyncT"
637 CS_WFBITMAPS = "WFBitMapS"
638 CS_WFBITMAPT = "WFBitMapT"
639 CS_WFSYNCUUID = "WFSyncUUID"
640 CS_SYNCSOURCE = "SyncSource"
641 CS_SYNCTARGET = "SyncTarget"
642 CS_PAUSEDSYNCS = "PausedSyncS"
643 CS_PAUSEDSYNCT = "PausedSyncT"
644 CSET_SYNC = frozenset([
657 DS_DISKLESS = "Diskless"
658 DS_ATTACHING = "Attaching" # transient state
659 DS_FAILED = "Failed" # transient state, next: diskless
660 DS_NEGOTIATING = "Negotiating" # transient state
661 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
662 DS_OUTDATED = "Outdated"
663 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
664 DS_CONSISTENT = "Consistent"
665 DS_UPTODATE = "UpToDate" # normal state
667 RO_PRIMARY = "Primary"
668 RO_SECONDARY = "Secondary"
669 RO_UNKNOWN = "Unknown"
671 def __init__(self, procline):
672 u = self.UNCONF_RE.match(procline)
674 self.cstatus = self.CS_UNCONFIGURED
675 self.lrole = self.rrole = self.ldisk = self.rdisk = None
677 m = self.LINE_RE.match(procline)
679 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
680 self.cstatus = m.group(1)
681 self.lrole = m.group(2)
682 self.rrole = m.group(3)
683 self.ldisk = m.group(4)
684 self.rdisk = m.group(5)
686 # end reading of data from the LINE_RE or UNCONF_RE
688 self.is_standalone = self.cstatus == self.CS_STANDALONE
689 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
690 self.is_connected = self.cstatus == self.CS_CONNECTED
691 self.is_primary = self.lrole == self.RO_PRIMARY
692 self.is_secondary = self.lrole == self.RO_SECONDARY
693 self.peer_primary = self.rrole == self.RO_PRIMARY
694 self.peer_secondary = self.rrole == self.RO_SECONDARY
695 self.both_primary = self.is_primary and self.peer_primary
696 self.both_secondary = self.is_secondary and self.peer_secondary
698 self.is_diskless = self.ldisk == self.DS_DISKLESS
699 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
701 self.is_in_resync = self.cstatus in self.CSET_SYNC
702 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
704 m = self.SYNC_RE.match(procline)
706 self.sync_percent = float(m.group(1))
707 hours = int(m.group(2))
708 minutes = int(m.group(3))
709 seconds = int(m.group(4))
710 self.est_time = hours * 3600 + minutes * 60 + seconds
712 # we have (in this if branch) no percent information, but if
713 # we're resyncing we need to 'fake' a sync percent information,
714 # as this is how cmdlib determines if it makes sense to wait for
716 if self.is_in_resync:
717 self.sync_percent = 0
719 self.sync_percent = None
723 class BaseDRBD(BlockDev):
726 This class contains a few bits of common functionality between the
727 0.7 and 8.x versions of DRBD.
730 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
731 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
734 _ST_UNCONFIGURED = "Unconfigured"
735 _ST_WFCONNECTION = "WFConnection"
736 _ST_CONNECTED = "Connected"
738 _STATUS_FILE = "/proc/drbd"
741 def _GetProcData(filename=_STATUS_FILE):
742 """Return data from /proc/drbd.
746 stat = open(filename, "r")
748 data = stat.read().splitlines()
751 except EnvironmentError, err:
752 if err.errno == errno.ENOENT:
753 _ThrowError("The file %s cannot be opened, check if the module"
754 " is loaded (%s)", filename, str(err))
756 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
758 _ThrowError("Can't read any data from %s", filename)
762 def _MassageProcData(data):
763 """Transform the output of _GetProdData into a nicer form.
765 @return: a dictionary of minor: joined lines from /proc/drbd
769 lmatch = re.compile("^ *([0-9]+):.*$")
771 old_minor = old_line = None
773 lresult = lmatch.match(line)
774 if lresult is not None:
775 if old_minor is not None:
776 results[old_minor] = old_line
777 old_minor = int(lresult.group(1))
780 if old_minor is not None:
781 old_line += " " + line.strip()
783 if old_minor is not None:
784 results[old_minor] = old_line
788 def _GetVersion(cls):
789 """Return the DRBD version.
791 This will return a dict with keys:
797 - proto2 (only on drbd > 8.2.X)
800 proc_data = cls._GetProcData()
801 first_line = proc_data[0].strip()
802 version = cls._VERSION_RE.match(first_line)
804 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
807 values = version.groups()
808 retval = {'k_major': int(values[0]),
809 'k_minor': int(values[1]),
810 'k_point': int(values[2]),
811 'api': int(values[3]),
812 'proto': int(values[4]),
814 if values[5] is not None:
815 retval['proto2'] = values[5]
821 """Return the path to a drbd device for a given minor.
824 return "/dev/drbd%d" % minor
827 def GetUsedDevs(cls):
828 """Compute the list of used DRBD devices.
831 data = cls._GetProcData()
834 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
836 match = valid_line.match(line)
839 minor = int(match.group(1))
840 state = match.group(2)
841 if state == cls._ST_UNCONFIGURED:
843 used_devs[minor] = state, line
847 def _SetFromMinor(self, minor):
848 """Set our parameters based on the given minor.
850 This sets our minor variable and our dev_path.
854 self.minor = self.dev_path = None
855 self.attached = False
858 self.dev_path = self._DevPath(minor)
862 def _CheckMetaSize(meta_device):
863 """Check if the given meta device looks like a valid one.
865 This currently only check the size, which must be around
869 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
871 _ThrowError("Failed to get device size: %s - %s",
872 result.fail_reason, result.output)
874 sectors = int(result.stdout)
876 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
877 bytes = sectors * 512
878 if bytes < 128 * 1024 * 1024: # less than 128MiB
879 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
880 # the maximum *valid* size of the meta device when living on top
881 # of LVM is hard to compute: it depends on the number of stripes
882 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
883 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
884 # size meta device; as such, we restrict it to 1GB (a little bit
885 # too generous, but making assumptions about PE size is hard)
886 if bytes > 1024 * 1024 * 1024:
887 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
889 def Rename(self, new_id):
892 This is not supported for drbd devices.
895 raise errors.ProgrammerError("Can't rename a drbd device")
898 class DRBD8(BaseDRBD):
899 """DRBD v8.x block device.
901 This implements the local host part of the DRBD device, i.e. it
902 doesn't do anything to the supposed peer. If you need a fully
903 connected DRBD pair, you need to use this class on both hosts.
905 The unique_id for the drbd device is the (local_ip, local_port,
906 remote_ip, remote_port) tuple, and it must have two children: the
907 data device and the meta_device. The meta device is checked for
908 valid size and is zeroed on create.
915 _NET_RECONFIG_TIMEOUT = 60
917 def __init__(self, unique_id, children, size):
918 if children and children.count(None) > 0:
920 super(DRBD8, self).__init__(unique_id, children, size)
921 self.major = self._DRBD_MAJOR
922 version = self._GetVersion()
923 if version['k_major'] != 8 :
924 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
925 " usage: kernel is %s.%s, ganeti wants 8.x",
926 version['k_major'], version['k_minor'])
928 if len(children) not in (0, 2):
929 raise ValueError("Invalid configuration data %s" % str(children))
930 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
931 raise ValueError("Invalid configuration data %s" % str(unique_id))
932 (self._lhost, self._lport,
933 self._rhost, self._rport,
934 self._aminor, self._secret) = unique_id
935 if (self._lhost is not None and self._lhost == self._rhost and
936 self._lport == self._rport):
937 raise ValueError("Invalid configuration data, same local/remote %s" %
942 def _InitMeta(cls, minor, dev_path):
943 """Initialize a meta device.
945 This will not work if the given minor is in use.
948 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
949 "v08", dev_path, "0", "create-md"])
951 _ThrowError("Can't initialize meta device: %s", result.output)
954 def _FindUnusedMinor(cls):
955 """Find an unused DRBD device.
957 This is specific to 8.x as the minors are allocated dynamically,
958 so non-existing numbers up to a max minor count are actually free.
961 data = cls._GetProcData()
963 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
964 used_line = re.compile("^ *([0-9]+): cs:")
967 match = unused_line.match(line)
969 return int(match.group(1))
970 match = used_line.match(line)
972 minor = int(match.group(1))
973 highest = max(highest, minor)
974 if highest is None: # there are no minors in use at all
976 if highest >= cls._MAX_MINORS:
977 logging.error("Error: no free drbd minors!")
978 raise errors.BlockDeviceError("Can't find a free DRBD minor")
982 def _GetShowParser(cls):
983 """Return a parser for `drbd show` output.
985 This will either create or return an already-create parser for the
986 output of the command `drbd show`.
989 if cls._PARSE_SHOW is not None:
990 return cls._PARSE_SHOW
993 lbrace = pyp.Literal("{").suppress()
994 rbrace = pyp.Literal("}").suppress()
995 semi = pyp.Literal(";").suppress()
996 # this also converts the value to an int
997 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
999 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1000 defa = pyp.Literal("_is_default").suppress()
1001 dbl_quote = pyp.Literal('"').suppress()
1003 keyword = pyp.Word(pyp.alphanums + '-')
1006 value = pyp.Word(pyp.alphanums + '_-/.:')
1007 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1008 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1009 pyp.Optional(pyp.Literal("ipv6")).suppress())
1010 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1011 pyp.Literal(':').suppress() + number)
1012 # meta device, extended syntax
1013 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1014 number + pyp.Word(']').suppress())
1015 # device name, extended syntax
1016 device_value = pyp.Literal("minor").suppress() + number
1019 stmt = (~rbrace + keyword + ~lbrace +
1020 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1022 pyp.Optional(defa) + semi +
1023 pyp.Optional(pyp.restOfLine).suppress())
1026 section_name = pyp.Word(pyp.alphas + '_')
1027 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1029 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1032 cls._PARSE_SHOW = bnf
1037 def _GetShowData(cls, minor):
1038 """Return the `drbdsetup show` data for a minor.
1041 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1043 logging.error("Can't display the drbd config: %s - %s",
1044 result.fail_reason, result.output)
1046 return result.stdout
1049 def _GetDevInfo(cls, out):
1050 """Parse details about a given DRBD minor.
1052 This return, if available, the local backing device (as a path)
1053 and the local and remote (ip, port) information from a string
1054 containing the output of the `drbdsetup show` command as returned
1062 bnf = cls._GetShowParser()
1066 results = bnf.parseString(out)
1067 except pyp.ParseException, err:
1068 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1070 # and massage the results into our desired format
1071 for section in results:
1073 if sname == "_this_host":
1074 for lst in section[1:]:
1075 if lst[0] == "disk":
1076 data["local_dev"] = lst[1]
1077 elif lst[0] == "meta-disk":
1078 data["meta_dev"] = lst[1]
1079 data["meta_index"] = lst[2]
1080 elif lst[0] == "address":
1081 data["local_addr"] = tuple(lst[1:])
1082 elif sname == "_remote_host":
1083 for lst in section[1:]:
1084 if lst[0] == "address":
1085 data["remote_addr"] = tuple(lst[1:])
1088 def _MatchesLocal(self, info):
1089 """Test if our local config matches with an existing device.
1091 The parameter should be as returned from `_GetDevInfo()`. This
1092 method tests if our local backing device is the same as the one in
1093 the info parameter, in effect testing if we look like the given
1098 backend, meta = self._children
1100 backend = meta = None
1102 if backend is not None:
1103 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1105 retval = ("local_dev" not in info)
1107 if meta is not None:
1108 retval = retval and ("meta_dev" in info and
1109 info["meta_dev"] == meta.dev_path)
1110 retval = retval and ("meta_index" in info and
1111 info["meta_index"] == 0)
1113 retval = retval and ("meta_dev" not in info and
1114 "meta_index" not in info)
1117 def _MatchesNet(self, info):
1118 """Test if our network config matches with an existing device.
1120 The parameter should be as returned from `_GetDevInfo()`. This
1121 method tests if our network configuration is the same as the one
1122 in the info parameter, in effect testing if we look like the given
1126 if (((self._lhost is None and not ("local_addr" in info)) and
1127 (self._rhost is None and not ("remote_addr" in info)))):
1130 if self._lhost is None:
1133 if not ("local_addr" in info and
1134 "remote_addr" in info):
1137 retval = (info["local_addr"] == (self._lhost, self._lport))
1138 retval = (retval and
1139 info["remote_addr"] == (self._rhost, self._rport))
1143 def _AssembleLocal(cls, minor, backend, meta, size):
1144 """Configure the local part of a DRBD device.
1147 args = ["drbdsetup", cls._DevPath(minor), "disk",
1152 args.extend(["-d", "%sm" % size])
1153 result = utils.RunCmd(args)
1155 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1158 def _AssembleNet(cls, minor, net_info, protocol,
1159 dual_pri=False, hmac=None, secret=None):
1160 """Configure the network part of the device.
1163 lhost, lport, rhost, rport = net_info
1164 if None in net_info:
1165 # we don't want network connection and actually want to make
1167 cls._ShutdownNet(minor)
1170 # Workaround for a race condition. When DRBD is doing its dance to
1171 # establish a connection with its peer, it also sends the
1172 # synchronization speed over the wire. In some cases setting the
1173 # sync speed only after setting up both sides can race with DRBD
1174 # connecting, hence we set it here before telling DRBD anything
1176 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1178 args = ["drbdsetup", cls._DevPath(minor), "net",
1179 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1180 "-A", "discard-zero-changes",
1187 args.extend(["-a", hmac, "-x", secret])
1188 result = utils.RunCmd(args)
1190 _ThrowError("drbd%d: can't setup network: %s - %s",
1191 minor, result.fail_reason, result.output)
1193 timeout = time.time() + 10
1195 while time.time() < timeout:
1196 info = cls._GetDevInfo(cls._GetShowData(minor))
1197 if not "local_addr" in info or not "remote_addr" in info:
1200 if (info["local_addr"] != (lhost, lport) or
1201 info["remote_addr"] != (rhost, rport)):
1207 _ThrowError("drbd%d: timeout while configuring network", minor)
1209 def AddChildren(self, devices):
1210 """Add a disk to the DRBD device.
1213 if self.minor is None:
1214 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1216 if len(devices) != 2:
1217 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1218 info = self._GetDevInfo(self._GetShowData(self.minor))
1219 if "local_dev" in info:
1220 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1221 backend, meta = devices
1222 if backend.dev_path is None or meta.dev_path is None:
1223 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1226 self._CheckMetaSize(meta.dev_path)
1227 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1229 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1230 self._children = devices
1232 def RemoveChildren(self, devices):
1233 """Detach the drbd device from local storage.
1236 if self.minor is None:
1237 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1239 # early return if we don't actually have backing storage
1240 info = self._GetDevInfo(self._GetShowData(self.minor))
1241 if "local_dev" not in info:
1243 if len(self._children) != 2:
1244 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1246 if self._children.count(None) == 2: # we don't actually have children :)
1247 logging.warning("drbd%d: requested detach while detached", self.minor)
1249 if len(devices) != 2:
1250 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1251 for child, dev in zip(self._children, devices):
1252 if dev != child.dev_path:
1253 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1254 " RemoveChildren", self.minor, dev, child.dev_path)
1256 self._ShutdownLocal(self.minor)
1260 def _SetMinorSyncSpeed(cls, minor, kbytes):
1261 """Set the speed of the DRBD syncer.
1263 This is the low-level implementation.
1266 @param minor: the drbd minor whose settings we change
1268 @param kbytes: the speed in kbytes/second
1270 @return: the success of the operation
1273 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1274 "-r", "%d" % kbytes, "--create-device"])
1276 logging.error("Can't change syncer rate: %s - %s",
1277 result.fail_reason, result.output)
1278 return not result.failed
1280 def SetSyncSpeed(self, kbytes):
1281 """Set the speed of the DRBD syncer.
1284 @param kbytes: the speed in kbytes/second
1286 @return: the success of the operation
1289 if self.minor is None:
1290 logging.info("Not attached during SetSyncSpeed")
1292 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1293 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1295 def GetProcStatus(self):
1296 """Return device data from /proc.
1299 if self.minor is None:
1300 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1301 proc_info = self._MassageProcData(self._GetProcData())
1302 if self.minor not in proc_info:
1303 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1304 return DRBD8Status(proc_info[self.minor])
1306 def GetSyncStatus(self):
1307 """Returns the sync status of the device.
1310 If sync_percent is None, it means all is ok
1311 If estimated_time is None, it means we can't esimate
1312 the time needed, otherwise it's the time left in seconds.
1315 We set the is_degraded parameter to True on two conditions:
1316 network not connected or local disk missing.
1318 We compute the ldisk parameter based on wheter we have a local
1322 @return: (sync_percent, estimated_time, is_degraded, ldisk)
1325 if self.minor is None and not self.Attach():
1326 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1327 stats = self.GetProcStatus()
1328 ldisk = not stats.is_disk_uptodate
1329 is_degraded = not stats.is_connected
1330 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1332 def Open(self, force=False):
1333 """Make the local state primary.
1335 If the 'force' parameter is given, the '-o' option is passed to
1336 drbdsetup. Since this is a potentially dangerous operation, the
1337 force flag should be only given after creation, when it actually
1341 if self.minor is None and not self.Attach():
1342 logging.error("DRBD cannot attach to a device during open")
1344 cmd = ["drbdsetup", self.dev_path, "primary"]
1347 result = utils.RunCmd(cmd)
1349 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1353 """Make the local state secondary.
1355 This will, of course, fail if the device is in use.
1358 if self.minor is None and not self.Attach():
1359 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1360 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1362 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1363 self.minor, result.output)
1365 def DisconnectNet(self):
1366 """Removes network configuration.
1368 This method shutdowns the network side of the device.
1370 The method will wait up to a hardcoded timeout for the device to
1371 go into standalone after the 'disconnect' command before
1372 re-configuring it, as sometimes it takes a while for the
1373 disconnect to actually propagate and thus we might issue a 'net'
1374 command while the device is still connected. If the device will
1375 still be attached to the network and we time out, we raise an
1379 if self.minor is None:
1380 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1382 if None in (self._lhost, self._lport, self._rhost, self._rport):
1383 _ThrowError("drbd%d: DRBD disk missing network info in"
1384 " DisconnectNet()", self.minor)
1386 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1387 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1388 sleep_time = 0.100 # we start the retry time at 100 miliseconds
1389 while time.time() < timeout_limit:
1390 status = self.GetProcStatus()
1391 if status.is_standalone:
1393 # retry the disconnect, it seems possible that due to a
1394 # well-time disconnect on the peer, my disconnect command might
1395 # be ingored and forgotten
1396 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1398 time.sleep(sleep_time)
1399 sleep_time = min(2, sleep_time * 1.5)
1401 if not status.is_standalone:
1402 if ever_disconnected:
1403 msg = ("drbd%d: device did not react to the"
1404 " 'disconnect' command in a timely manner")
1406 msg = "drbd%d: can't shutdown network, even after multiple retries"
1407 _ThrowError(msg, self.minor)
1409 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1410 if reconfig_time > 15: # hardcoded alert limit
1411 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1412 self.minor, reconfig_time)
1414 def AttachNet(self, multimaster):
1415 """Reconnects the network.
1417 This method connects the network side of the device with a
1418 specified multi-master flag. The device needs to be 'Standalone'
1419 but have valid network configuration data.
1422 - multimaster: init the network in dual-primary mode
1425 if self.minor is None:
1426 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1428 if None in (self._lhost, self._lport, self._rhost, self._rport):
1429 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1431 status = self.GetProcStatus()
1433 if not status.is_standalone:
1434 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1436 self._AssembleNet(self.minor,
1437 (self._lhost, self._lport, self._rhost, self._rport),
1438 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1439 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1442 """Check if our minor is configured.
1444 This doesn't do any device configurations - it only checks if the
1445 minor is in a state different from Unconfigured.
1447 Note that this function will not change the state of the system in
1448 any way (except in case of side-effects caused by reading from
1452 used_devs = self.GetUsedDevs()
1453 if self._aminor in used_devs:
1454 minor = self._aminor
1458 self._SetFromMinor(minor)
1459 return minor is not None
1462 """Assemble the drbd.
1465 - if we have a configured device, we try to ensure that it matches
1467 - if not, we create it from zero
1470 super(DRBD8, self).Assemble()
1473 if self.minor is None:
1474 # local device completely unconfigured
1475 self._FastAssemble()
1477 # we have to recheck the local and network status and try to fix
1479 self._SlowAssemble()
1481 def _SlowAssemble(self):
1482 """Assembles the DRBD device from a (partially) configured device.
1484 In case of partially attached (local device matches but no network
1485 setup), we perform the network attach. If successful, we re-test
1486 the attach if can return success.
1489 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1490 for minor in (self._aminor,):
1491 info = self._GetDevInfo(self._GetShowData(minor))
1492 match_l = self._MatchesLocal(info)
1493 match_r = self._MatchesNet(info)
1495 if match_l and match_r:
1496 # everything matches
1499 if match_l and not match_r and "local_addr" not in info:
1500 # disk matches, but not attached to network, attach and recheck
1501 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1502 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1503 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1506 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1507 " show' disagrees", minor)
1509 if match_r and "local_dev" not in info:
1510 # no local disk, but network attached and it matches
1511 self._AssembleLocal(minor, self._children[0].dev_path,
1512 self._children[1].dev_path, self.size)
1513 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1516 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1517 " show' disagrees", minor)
1519 # this case must be considered only if we actually have local
1520 # storage, i.e. not in diskless mode, because all diskless
1521 # devices are equal from the point of view of local
1523 if (match_l and "local_dev" in info and
1524 not match_r and "local_addr" in info):
1525 # strange case - the device network part points to somewhere
1526 # else, even though its local storage is ours; as we own the
1527 # drbd space, we try to disconnect from the remote peer and
1528 # reconnect to our correct one
1530 self._ShutdownNet(minor)
1531 except errors.BlockDeviceError, err:
1532 _ThrowError("drbd%d: device has correct local storage, wrong"
1533 " remote peer and is unable to disconnect in order"
1534 " to attach to the correct peer: %s", minor, str(err))
1535 # note: _AssembleNet also handles the case when we don't want
1536 # local storage (i.e. one or more of the _[lr](host|port) is
1538 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1539 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1540 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1543 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1544 " show' disagrees", minor)
1549 self._SetFromMinor(minor)
1551 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1554 def _FastAssemble(self):
1555 """Assemble the drbd device from zero.
1557 This is run when in Assemble we detect our minor is unused.
1560 minor = self._aminor
1561 if self._children and self._children[0] and self._children[1]:
1562 self._AssembleLocal(minor, self._children[0].dev_path,
1563 self._children[1].dev_path, self.size)
1564 if self._lhost and self._lport and self._rhost and self._rport:
1565 self._AssembleNet(minor,
1566 (self._lhost, self._lport, self._rhost, self._rport),
1567 constants.DRBD_NET_PROTOCOL,
1568 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1569 self._SetFromMinor(minor)
1572 def _ShutdownLocal(cls, minor):
1573 """Detach from the local device.
1575 I/Os will continue to be served from the remote device. If we
1576 don't have a remote device, this operation will fail.
1579 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1581 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1584 def _ShutdownNet(cls, minor):
1585 """Disconnect from the remote peer.
1587 This fails if we don't have a local device.
1590 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1592 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1595 def _ShutdownAll(cls, minor):
1596 """Deactivate the device.
1598 This will, of course, fail if the device is in use.
1601 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1603 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1604 minor, result.output)
1607 """Shutdown the DRBD device.
1610 if self.minor is None and not self.Attach():
1611 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1615 self.dev_path = None
1616 self._ShutdownAll(minor)
1619 """Stub remove for DRBD devices.
1625 def Create(cls, unique_id, children, size):
1626 """Create a new DRBD8 device.
1628 Since DRBD devices are not created per se, just assembled, this
1629 function only initializes the metadata.
1632 if len(children) != 2:
1633 raise errors.ProgrammerError("Invalid setup for the drbd device")
1634 # check that the minor is unused
1635 aminor = unique_id[4]
1636 proc_info = cls._MassageProcData(cls._GetProcData())
1637 if aminor in proc_info:
1638 status = DRBD8Status(proc_info[aminor])
1639 in_use = status.is_in_use
1643 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1646 if not meta.Attach():
1647 _ThrowError("drbd%d: can't attach to meta device '%s'",
1649 cls._CheckMetaSize(meta.dev_path)
1650 cls._InitMeta(aminor, meta.dev_path)
1651 return cls(unique_id, children, size)
1653 def Grow(self, amount):
1654 """Resize the DRBD device and its backing storage.
1657 if self.minor is None:
1658 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1659 if len(self._children) != 2 or None in self._children:
1660 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1661 self._children[0].Grow(amount)
1662 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1663 "%dm" % (self.size + amount)])
1665 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1668 class FileStorage(BlockDev):
1671 This class represents the a file storage backend device.
1673 The unique_id for the file device is a (file_driver, file_path) tuple.
1676 def __init__(self, unique_id, children, size):
1677 """Initalizes a file device backend.
1681 raise errors.BlockDeviceError("Invalid setup for file device")
1682 super(FileStorage, self).__init__(unique_id, children, size)
1683 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1684 raise ValueError("Invalid configuration data %s" % str(unique_id))
1685 self.driver = unique_id[0]
1686 self.dev_path = unique_id[1]
1690 """Assemble the device.
1692 Checks whether the file device exists, raises BlockDeviceError otherwise.
1695 if not os.path.exists(self.dev_path):
1696 _ThrowError("File device '%s' does not exist" % self.dev_path)
1699 """Shutdown the device.
1701 This is a no-op for the file type, as we don't deacivate
1702 the file on shutdown.
1707 def Open(self, force=False):
1708 """Make the device ready for I/O.
1710 This is a no-op for the file type.
1716 """Notifies that the device will no longer be used for I/O.
1718 This is a no-op for the file type.
1724 """Remove the file backing the block device.
1727 @return: True if the removal was successful
1731 os.remove(self.dev_path)
1732 except OSError, err:
1733 if err.errno != errno.ENOENT:
1734 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1737 """Attach to an existing file.
1739 Check if this file already exists.
1742 @return: True if file exists
1745 self.attached = os.path.exists(self.dev_path)
1746 return self.attached
1748 def GetActualSize(self):
1749 """Return the actual disk size.
1751 @note: the device needs to be active when this is called
1754 assert self.attached, "BlockDevice not attached in GetActualSize()"
1756 st = os.stat(self.dev_path)
1758 except OSError, err:
1759 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1762 def Create(cls, unique_id, children, size):
1763 """Create a new file.
1765 @param size: the size of file in MiB
1767 @rtype: L{bdev.FileStorage}
1768 @return: an instance of FileStorage
1771 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1772 raise ValueError("Invalid configuration data %s" % str(unique_id))
1773 dev_path = unique_id[1]
1774 if os.path.exists(dev_path):
1775 _ThrowError("File already existing: %s", dev_path)
1777 f = open(dev_path, 'w')
1778 f.truncate(size * 1024 * 1024)
1780 except IOError, err:
1781 _ThrowError("Error in file creation: %", str(err))
1783 return FileStorage(unique_id, children, size)
1787 constants.LD_LV: LogicalVolume,
1788 constants.LD_DRBD8: DRBD8,
1789 constants.LD_FILE: FileStorage,
1793 def FindDevice(dev_type, unique_id, children, size):
1794 """Search for an existing, assembled device.
1796 This will succeed only if the device exists and is assembled, but it
1797 does not do any actions in order to activate the device.
1800 if dev_type not in DEV_MAP:
1801 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1802 device = DEV_MAP[dev_type](unique_id, children, size)
1803 if not device.attached:
1808 def Assemble(dev_type, unique_id, children, size):
1809 """Try to attach or assemble an existing device.
1811 This will attach to assemble the device, as needed, to bring it
1812 fully up. It must be safe to run on already-assembled devices.
1815 if dev_type not in DEV_MAP:
1816 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1817 device = DEV_MAP[dev_type](unique_id, children, size)
1822 def Create(dev_type, unique_id, children, size):
1826 if dev_type not in DEV_MAP:
1827 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1828 device = DEV_MAP[dev_type].Create(unique_id, children, size)