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 storage. 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
281 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
282 (self.__class__, self.unique_id, self._children,
283 self.major, self.minor, self.dev_path))
286 class LogicalVolume(BlockDev):
287 """Logical Volume block device.
290 def __init__(self, unique_id, children, size):
291 """Attaches to a LV device.
293 The unique_id is a tuple (vg_name, lv_name)
296 super(LogicalVolume, self).__init__(unique_id, children, size)
297 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
298 raise ValueError("Invalid configuration data %s" % str(unique_id))
299 self._vg_name, self._lv_name = unique_id
300 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
301 self._degraded = True
302 self.major = self.minor = self.pe_size = self.stripe_count = None
306 def Create(cls, unique_id, children, size):
307 """Create a new logical volume.
310 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
311 raise errors.ProgrammerError("Invalid configuration data %s" %
313 vg_name, lv_name = unique_id
314 pvs_info = cls.GetPVInfo(vg_name)
316 _ThrowError("Can't compute PV info for vg %s", vg_name)
320 pvlist = [ pv[1] for pv in pvs_info ]
321 free_size = sum([ pv[0] for pv in pvs_info ])
322 current_pvs = len(pvlist)
323 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
325 # The size constraint should have been checked from the master before
326 # calling the create function.
328 _ThrowError("Not enough free space: required %s,"
329 " available %s", size, free_size)
330 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
331 # If the free space is not well distributed, we won't be able to
332 # create an optimally-striped volume; in that case, we want to try
333 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
335 for stripes_arg in range(stripes, 0, -1):
336 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
337 if not result.failed:
340 _ThrowError("LV create failed (%s): %s",
341 result.fail_reason, result.output)
342 return LogicalVolume(unique_id, children, size)
345 def GetPVInfo(vg_name):
346 """Get the free space info for PVs in a volume group.
348 @param vg_name: the volume group name
351 @return: list of tuples (free_space, name) with free_space in mebibytes
354 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
355 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
357 result = utils.RunCmd(command)
359 logging.error("Can't get the PV information: %s - %s",
360 result.fail_reason, result.output)
363 for line in result.stdout.splitlines():
364 fields = line.strip().split(':')
366 logging.error("Can't parse pvs output: line '%s'", line)
368 # skip over pvs from another vg or ones which are not allocatable
369 if fields[1] != vg_name or fields[3][0] != 'a':
371 data.append((float(fields[2]), fields[0]))
376 """Remove this logical volume.
379 if not self.minor and not self.Attach():
380 # the LV does not exist
382 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
383 (self._vg_name, self._lv_name)])
385 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
387 def Rename(self, new_id):
388 """Rename this logical volume.
391 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
392 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
393 new_vg, new_name = new_id
394 if new_vg != self._vg_name:
395 raise errors.ProgrammerError("Can't move a logical volume across"
396 " volume groups (from %s to to %s)" %
397 (self._vg_name, new_vg))
398 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
400 _ThrowError("Failed to rename the logical volume: %s", result.output)
401 self._lv_name = new_name
402 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
405 """Attach to an existing LV.
407 This method will try to see if an existing and active LV exists
408 which matches our name. If so, its major/minor will be
412 self.attached = False
413 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
414 "--units=m", "--nosuffix",
415 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
416 "vg_extent_size,stripes", self.dev_path])
418 logging.error("Can't find LV %s: %s, %s",
419 self.dev_path, result.fail_reason, result.output)
421 # the output can (and will) have multiple lines for multi-segment
422 # LVs, as the 'stripes' parameter is a segment one, so we take
423 # only the last entry, which is the one we're interested in; note
424 # that with LVM2 anyway the 'stripes' value must be constant
425 # across segments, so this is a no-op actually
426 out = result.stdout.splitlines()
427 if not out: # totally empty result? splitlines() returns at least
428 # one line for any non-empty string
429 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
431 out = out[-1].strip().rstrip(',')
434 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
437 status, major, minor, pe_size, stripes = out
439 logging.error("lvs lv_attr is not 6 characters (%s)", status)
445 except ValueError, err:
446 logging.error("lvs major/minor cannot be parsed: %s", str(err))
449 pe_size = int(float(pe_size))
450 except (TypeError, ValueError), err:
451 logging.error("Can't parse vg extent size: %s", err)
455 stripes = int(stripes)
456 except (TypeError, ValueError), err:
457 logging.error("Can't parse the number of stripes: %s", err)
462 self.pe_size = pe_size
463 self.stripe_count = stripes
464 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
470 """Assemble the device.
472 We always run `lvchange -ay` on the LV to ensure it's active before
473 use, as there were cases when xenvg was not active after boot
474 (also possibly after disk issues).
477 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
479 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
482 """Shutdown the device.
484 This is a no-op for the LV device type, as we don't deactivate the
490 def GetSyncStatus(self):
491 """Returns the sync status of the device.
493 If this device is a mirroring device, this function returns the
494 status of the mirror.
496 For logical volumes, sync_percent and estimated_time are always
497 None (no recovery in progress, as we don't handle the mirrored LV
498 case). The is_degraded parameter is the inverse of the ldisk
501 For the ldisk parameter, we check if the logical volume has the
502 'virtual' type, which means it's not backed by existing storage
503 anymore (read from it return I/O error). This happens after a
504 physical disk failure and subsequent 'vgreduce --removemissing' on
507 The status was already read in Attach, so we just return it.
510 @return: (sync_percent, estimated_time, is_degraded, ldisk)
513 return None, None, self._degraded, self._degraded
515 def Open(self, force=False):
516 """Make the device ready for I/O.
518 This is a no-op for the LV device type.
524 """Notifies that the device will no longer be used for I/O.
526 This is a no-op for the LV device type.
531 def Snapshot(self, size):
532 """Create a snapshot copy of an lvm block device.
535 snap_name = self._lv_name + ".snap"
537 # remove existing snapshot if found
538 snap = LogicalVolume((self._vg_name, snap_name), None, size)
539 _IgnoreError(snap.Remove)
541 pvs_info = self.GetPVInfo(self._vg_name)
543 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
546 free_size, pv_name = pvs_info[0]
548 _ThrowError("Not enough free space: required %s,"
549 " available %s", size, free_size)
551 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
552 "-n%s" % snap_name, self.dev_path])
554 _ThrowError("command: %s error: %s - %s",
555 result.cmd, result.fail_reason, result.output)
559 def SetInfo(self, text):
560 """Update metadata with info text.
563 BlockDev.SetInfo(self, text)
565 # Replace invalid characters
566 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
567 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
569 # Only up to 128 characters are allowed
572 result = utils.RunCmd(["lvchange", "--addtag", text,
575 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
578 def Grow(self, amount):
579 """Grow the logical volume.
582 if self.pe_size is None or self.stripe_count is None:
583 if not self.Attach():
584 _ThrowError("Can't attach to LV during Grow()")
585 full_stripe_size = self.pe_size * self.stripe_count
586 rest = amount % full_stripe_size
588 amount += full_stripe_size - rest
589 # we try multiple algorithms since the 'best' ones might not have
590 # space available in the right place, but later ones might (since
591 # they have less constraints); also note that only recent LVM
593 for alloc_policy in "contiguous", "cling", "normal":
594 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
595 "-L", "+%dm" % amount, self.dev_path])
596 if not result.failed:
598 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
601 class DRBD8Status(object):
602 """A DRBD status representation class.
604 Note that this doesn't support unconfigured devices (cs:Unconfigured).
607 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
608 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
609 "\s+ds:([^/]+)/(\S+)\s+.*$")
610 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
611 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
613 CS_UNCONFIGURED = "Unconfigured"
614 CS_STANDALONE = "StandAlone"
615 CS_WFCONNECTION = "WFConnection"
616 CS_WFREPORTPARAMS = "WFReportParams"
617 CS_CONNECTED = "Connected"
618 CS_STARTINGSYNCS = "StartingSyncS"
619 CS_STARTINGSYNCT = "StartingSyncT"
620 CS_WFBITMAPS = "WFBitMapS"
621 CS_WFBITMAPT = "WFBitMapT"
622 CS_WFSYNCUUID = "WFSyncUUID"
623 CS_SYNCSOURCE = "SyncSource"
624 CS_SYNCTARGET = "SyncTarget"
625 CS_PAUSEDSYNCS = "PausedSyncS"
626 CS_PAUSEDSYNCT = "PausedSyncT"
627 CSET_SYNC = frozenset([
640 DS_DISKLESS = "Diskless"
641 DS_ATTACHING = "Attaching" # transient state
642 DS_FAILED = "Failed" # transient state, next: diskless
643 DS_NEGOTIATING = "Negotiating" # transient state
644 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
645 DS_OUTDATED = "Outdated"
646 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
647 DS_CONSISTENT = "Consistent"
648 DS_UPTODATE = "UpToDate" # normal state
650 RO_PRIMARY = "Primary"
651 RO_SECONDARY = "Secondary"
652 RO_UNKNOWN = "Unknown"
654 def __init__(self, procline):
655 u = self.UNCONF_RE.match(procline)
657 self.cstatus = self.CS_UNCONFIGURED
658 self.lrole = self.rrole = self.ldisk = self.rdisk = None
660 m = self.LINE_RE.match(procline)
662 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
663 self.cstatus = m.group(1)
664 self.lrole = m.group(2)
665 self.rrole = m.group(3)
666 self.ldisk = m.group(4)
667 self.rdisk = m.group(5)
669 # end reading of data from the LINE_RE or UNCONF_RE
671 self.is_standalone = self.cstatus == self.CS_STANDALONE
672 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
673 self.is_connected = self.cstatus == self.CS_CONNECTED
674 self.is_primary = self.lrole == self.RO_PRIMARY
675 self.is_secondary = self.lrole == self.RO_SECONDARY
676 self.peer_primary = self.rrole == self.RO_PRIMARY
677 self.peer_secondary = self.rrole == self.RO_SECONDARY
678 self.both_primary = self.is_primary and self.peer_primary
679 self.both_secondary = self.is_secondary and self.peer_secondary
681 self.is_diskless = self.ldisk == self.DS_DISKLESS
682 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
684 self.is_in_resync = self.cstatus in self.CSET_SYNC
685 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
687 m = self.SYNC_RE.match(procline)
689 self.sync_percent = float(m.group(1))
690 hours = int(m.group(2))
691 minutes = int(m.group(3))
692 seconds = int(m.group(4))
693 self.est_time = hours * 3600 + minutes * 60 + seconds
695 # we have (in this if branch) no percent information, but if
696 # we're resyncing we need to 'fake' a sync percent information,
697 # as this is how cmdlib determines if it makes sense to wait for
699 if self.is_in_resync:
700 self.sync_percent = 0
702 self.sync_percent = None
706 class BaseDRBD(BlockDev):
709 This class contains a few bits of common functionality between the
710 0.7 and 8.x versions of DRBD.
713 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
714 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
717 _ST_UNCONFIGURED = "Unconfigured"
718 _ST_WFCONNECTION = "WFConnection"
719 _ST_CONNECTED = "Connected"
721 _STATUS_FILE = "/proc/drbd"
724 def _GetProcData(filename=_STATUS_FILE):
725 """Return data from /proc/drbd.
729 stat = open(filename, "r")
731 data = stat.read().splitlines()
734 except EnvironmentError, err:
735 if err.errno == errno.ENOENT:
736 _ThrowError("The file %s cannot be opened, check if the module"
737 " is loaded (%s)", filename, str(err))
739 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
741 _ThrowError("Can't read any data from %s", filename)
745 def _MassageProcData(data):
746 """Transform the output of _GetProdData into a nicer form.
748 @return: a dictionary of minor: joined lines from /proc/drbd
752 lmatch = re.compile("^ *([0-9]+):.*$")
754 old_minor = old_line = None
756 lresult = lmatch.match(line)
757 if lresult is not None:
758 if old_minor is not None:
759 results[old_minor] = old_line
760 old_minor = int(lresult.group(1))
763 if old_minor is not None:
764 old_line += " " + line.strip()
766 if old_minor is not None:
767 results[old_minor] = old_line
771 def _GetVersion(cls):
772 """Return the DRBD version.
774 This will return a dict with keys:
780 - proto2 (only on drbd > 8.2.X)
783 proc_data = cls._GetProcData()
784 first_line = proc_data[0].strip()
785 version = cls._VERSION_RE.match(first_line)
787 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
790 values = version.groups()
791 retval = {'k_major': int(values[0]),
792 'k_minor': int(values[1]),
793 'k_point': int(values[2]),
794 'api': int(values[3]),
795 'proto': int(values[4]),
797 if values[5] is not None:
798 retval['proto2'] = values[5]
804 """Return the path to a drbd device for a given minor.
807 return "/dev/drbd%d" % minor
810 def GetUsedDevs(cls):
811 """Compute the list of used DRBD devices.
814 data = cls._GetProcData()
817 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
819 match = valid_line.match(line)
822 minor = int(match.group(1))
823 state = match.group(2)
824 if state == cls._ST_UNCONFIGURED:
826 used_devs[minor] = state, line
830 def _SetFromMinor(self, minor):
831 """Set our parameters based on the given minor.
833 This sets our minor variable and our dev_path.
837 self.minor = self.dev_path = None
838 self.attached = False
841 self.dev_path = self._DevPath(minor)
845 def _CheckMetaSize(meta_device):
846 """Check if the given meta device looks like a valid one.
848 This currently only check the size, which must be around
852 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
854 _ThrowError("Failed to get device size: %s - %s",
855 result.fail_reason, result.output)
857 sectors = int(result.stdout)
859 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
860 bytes = sectors * 512
861 if bytes < 128 * 1024 * 1024: # less than 128MiB
862 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
863 # the maximum *valid* size of the meta device when living on top
864 # of LVM is hard to compute: it depends on the number of stripes
865 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
866 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
867 # size meta device; as such, we restrict it to 1GB (a little bit
868 # too generous, but making assumptions about PE size is hard)
869 if bytes > 1024 * 1024 * 1024:
870 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
872 def Rename(self, new_id):
875 This is not supported for drbd devices.
878 raise errors.ProgrammerError("Can't rename a drbd device")
881 class DRBD8(BaseDRBD):
882 """DRBD v8.x block device.
884 This implements the local host part of the DRBD device, i.e. it
885 doesn't do anything to the supposed peer. If you need a fully
886 connected DRBD pair, you need to use this class on both hosts.
888 The unique_id for the drbd device is the (local_ip, local_port,
889 remote_ip, remote_port) tuple, and it must have two children: the
890 data device and the meta_device. The meta device is checked for
891 valid size and is zeroed on create.
898 _NET_RECONFIG_TIMEOUT = 60
900 def __init__(self, unique_id, children, size):
901 if children and children.count(None) > 0:
903 super(DRBD8, self).__init__(unique_id, children, size)
904 self.major = self._DRBD_MAJOR
905 version = self._GetVersion()
906 if version['k_major'] != 8 :
907 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
908 " usage: kernel is %s.%s, ganeti wants 8.x",
909 version['k_major'], version['k_minor'])
911 if len(children) not in (0, 2):
912 raise ValueError("Invalid configuration data %s" % str(children))
913 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
914 raise ValueError("Invalid configuration data %s" % str(unique_id))
915 (self._lhost, self._lport,
916 self._rhost, self._rport,
917 self._aminor, self._secret) = unique_id
918 if (self._lhost is not None and self._lhost == self._rhost and
919 self._lport == self._rport):
920 raise ValueError("Invalid configuration data, same local/remote %s" %
925 def _InitMeta(cls, minor, dev_path):
926 """Initialize a meta device.
928 This will not work if the given minor is in use.
931 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
932 "v08", dev_path, "0", "create-md"])
934 _ThrowError("Can't initialize meta device: %s", result.output)
937 def _FindUnusedMinor(cls):
938 """Find an unused DRBD device.
940 This is specific to 8.x as the minors are allocated dynamically,
941 so non-existing numbers up to a max minor count are actually free.
944 data = cls._GetProcData()
946 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
947 used_line = re.compile("^ *([0-9]+): cs:")
950 match = unused_line.match(line)
952 return int(match.group(1))
953 match = used_line.match(line)
955 minor = int(match.group(1))
956 highest = max(highest, minor)
957 if highest is None: # there are no minors in use at all
959 if highest >= cls._MAX_MINORS:
960 logging.error("Error: no free drbd minors!")
961 raise errors.BlockDeviceError("Can't find a free DRBD minor")
965 def _GetShowParser(cls):
966 """Return a parser for `drbd show` output.
968 This will either create or return an already-create parser for the
969 output of the command `drbd show`.
972 if cls._PARSE_SHOW is not None:
973 return cls._PARSE_SHOW
976 lbrace = pyp.Literal("{").suppress()
977 rbrace = pyp.Literal("}").suppress()
978 semi = pyp.Literal(";").suppress()
979 # this also converts the value to an int
980 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
982 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
983 defa = pyp.Literal("_is_default").suppress()
984 dbl_quote = pyp.Literal('"').suppress()
986 keyword = pyp.Word(pyp.alphanums + '-')
989 value = pyp.Word(pyp.alphanums + '_-/.:')
990 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
991 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
992 pyp.Optional(pyp.Literal("ipv6")).suppress())
993 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
994 pyp.Literal(':').suppress() + number)
995 # meta device, extended syntax
996 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
997 number + pyp.Word(']').suppress())
998 # device name, extended syntax
999 device_value = pyp.Literal("minor").suppress() + number
1002 stmt = (~rbrace + keyword + ~lbrace +
1003 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1005 pyp.Optional(defa) + semi +
1006 pyp.Optional(pyp.restOfLine).suppress())
1009 section_name = pyp.Word(pyp.alphas + '_')
1010 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1012 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1015 cls._PARSE_SHOW = bnf
1020 def _GetShowData(cls, minor):
1021 """Return the `drbdsetup show` data for a minor.
1024 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1026 logging.error("Can't display the drbd config: %s - %s",
1027 result.fail_reason, result.output)
1029 return result.stdout
1032 def _GetDevInfo(cls, out):
1033 """Parse details about a given DRBD minor.
1035 This return, if available, the local backing device (as a path)
1036 and the local and remote (ip, port) information from a string
1037 containing the output of the `drbdsetup show` command as returned
1045 bnf = cls._GetShowParser()
1049 results = bnf.parseString(out)
1050 except pyp.ParseException, err:
1051 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1053 # and massage the results into our desired format
1054 for section in results:
1056 if sname == "_this_host":
1057 for lst in section[1:]:
1058 if lst[0] == "disk":
1059 data["local_dev"] = lst[1]
1060 elif lst[0] == "meta-disk":
1061 data["meta_dev"] = lst[1]
1062 data["meta_index"] = lst[2]
1063 elif lst[0] == "address":
1064 data["local_addr"] = tuple(lst[1:])
1065 elif sname == "_remote_host":
1066 for lst in section[1:]:
1067 if lst[0] == "address":
1068 data["remote_addr"] = tuple(lst[1:])
1071 def _MatchesLocal(self, info):
1072 """Test if our local config matches with an existing device.
1074 The parameter should be as returned from `_GetDevInfo()`. This
1075 method tests if our local backing device is the same as the one in
1076 the info parameter, in effect testing if we look like the given
1081 backend, meta = self._children
1083 backend = meta = None
1085 if backend is not None:
1086 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1088 retval = ("local_dev" not in info)
1090 if meta is not None:
1091 retval = retval and ("meta_dev" in info and
1092 info["meta_dev"] == meta.dev_path)
1093 retval = retval and ("meta_index" in info and
1094 info["meta_index"] == 0)
1096 retval = retval and ("meta_dev" not in info and
1097 "meta_index" not in info)
1100 def _MatchesNet(self, info):
1101 """Test if our network config matches with an existing device.
1103 The parameter should be as returned from `_GetDevInfo()`. This
1104 method tests if our network configuration is the same as the one
1105 in the info parameter, in effect testing if we look like the given
1109 if (((self._lhost is None and not ("local_addr" in info)) and
1110 (self._rhost is None and not ("remote_addr" in info)))):
1113 if self._lhost is None:
1116 if not ("local_addr" in info and
1117 "remote_addr" in info):
1120 retval = (info["local_addr"] == (self._lhost, self._lport))
1121 retval = (retval and
1122 info["remote_addr"] == (self._rhost, self._rport))
1126 def _AssembleLocal(cls, minor, backend, meta, size):
1127 """Configure the local part of a DRBD device.
1130 args = ["drbdsetup", cls._DevPath(minor), "disk",
1135 result = utils.RunCmd(args)
1137 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1140 def _AssembleNet(cls, minor, net_info, protocol,
1141 dual_pri=False, hmac=None, secret=None):
1142 """Configure the network part of the device.
1145 lhost, lport, rhost, rport = net_info
1146 if None in net_info:
1147 # we don't want network connection and actually want to make
1149 cls._ShutdownNet(minor)
1152 # Workaround for a race condition. When DRBD is doing its dance to
1153 # establish a connection with its peer, it also sends the
1154 # synchronization speed over the wire. In some cases setting the
1155 # sync speed only after setting up both sides can race with DRBD
1156 # connecting, hence we set it here before telling DRBD anything
1158 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1160 args = ["drbdsetup", cls._DevPath(minor), "net",
1161 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1162 "-A", "discard-zero-changes",
1169 args.extend(["-a", hmac, "-x", secret])
1170 result = utils.RunCmd(args)
1172 _ThrowError("drbd%d: can't setup network: %s - %s",
1173 minor, result.fail_reason, result.output)
1175 timeout = time.time() + 10
1177 while time.time() < timeout:
1178 info = cls._GetDevInfo(cls._GetShowData(minor))
1179 if not "local_addr" in info or not "remote_addr" in info:
1182 if (info["local_addr"] != (lhost, lport) or
1183 info["remote_addr"] != (rhost, rport)):
1189 _ThrowError("drbd%d: timeout while configuring network", minor)
1191 def AddChildren(self, devices):
1192 """Add a disk to the DRBD device.
1195 if self.minor is None:
1196 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1198 if len(devices) != 2:
1199 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1200 info = self._GetDevInfo(self._GetShowData(self.minor))
1201 if "local_dev" in info:
1202 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1203 backend, meta = devices
1204 if backend.dev_path is None or meta.dev_path is None:
1205 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1208 self._CheckMetaSize(meta.dev_path)
1209 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1211 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1212 self._children = devices
1214 def RemoveChildren(self, devices):
1215 """Detach the drbd device from local storage.
1218 if self.minor is None:
1219 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1221 # early return if we don't actually have backing storage
1222 info = self._GetDevInfo(self._GetShowData(self.minor))
1223 if "local_dev" not in info:
1225 if len(self._children) != 2:
1226 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1228 if self._children.count(None) == 2: # we don't actually have children :)
1229 logging.warning("drbd%d: requested detach while detached", self.minor)
1231 if len(devices) != 2:
1232 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1233 for child, dev in zip(self._children, devices):
1234 if dev != child.dev_path:
1235 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1236 " RemoveChildren", self.minor, dev, child.dev_path)
1238 self._ShutdownLocal(self.minor)
1242 def _SetMinorSyncSpeed(cls, minor, kbytes):
1243 """Set the speed of the DRBD syncer.
1245 This is the low-level implementation.
1248 @param minor: the drbd minor whose settings we change
1250 @param kbytes: the speed in kbytes/second
1252 @return: the success of the operation
1255 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1256 "-r", "%d" % kbytes, "--create-device"])
1258 logging.error("Can't change syncer rate: %s - %s",
1259 result.fail_reason, result.output)
1260 return not result.failed
1262 def SetSyncSpeed(self, kbytes):
1263 """Set the speed of the DRBD syncer.
1266 @param kbytes: the speed in kbytes/second
1268 @return: the success of the operation
1271 if self.minor is None:
1272 logging.info("Not attached during SetSyncSpeed")
1274 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1275 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1277 def GetProcStatus(self):
1278 """Return device data from /proc.
1281 if self.minor is None:
1282 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1283 proc_info = self._MassageProcData(self._GetProcData())
1284 if self.minor not in proc_info:
1285 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1286 return DRBD8Status(proc_info[self.minor])
1288 def GetSyncStatus(self):
1289 """Returns the sync status of the device.
1292 If sync_percent is None, it means all is ok
1293 If estimated_time is None, it means we can't estimate
1294 the time needed, otherwise it's the time left in seconds.
1297 We set the is_degraded parameter to True on two conditions:
1298 network not connected or local disk missing.
1300 We compute the ldisk parameter based on whether we have a local
1304 @return: (sync_percent, estimated_time, is_degraded, ldisk)
1307 if self.minor is None and not self.Attach():
1308 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1309 stats = self.GetProcStatus()
1310 ldisk = not stats.is_disk_uptodate
1311 is_degraded = not stats.is_connected
1312 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1314 def Open(self, force=False):
1315 """Make the local state primary.
1317 If the 'force' parameter is given, the '-o' option is passed to
1318 drbdsetup. Since this is a potentially dangerous operation, the
1319 force flag should be only given after creation, when it actually
1323 if self.minor is None and not self.Attach():
1324 logging.error("DRBD cannot attach to a device during open")
1326 cmd = ["drbdsetup", self.dev_path, "primary"]
1329 result = utils.RunCmd(cmd)
1331 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1335 """Make the local state secondary.
1337 This will, of course, fail if the device is in use.
1340 if self.minor is None and not self.Attach():
1341 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1342 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1344 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1345 self.minor, result.output)
1347 def DisconnectNet(self):
1348 """Removes network configuration.
1350 This method shutdowns the network side of the device.
1352 The method will wait up to a hardcoded timeout for the device to
1353 go into standalone after the 'disconnect' command before
1354 re-configuring it, as sometimes it takes a while for the
1355 disconnect to actually propagate and thus we might issue a 'net'
1356 command while the device is still connected. If the device will
1357 still be attached to the network and we time out, we raise an
1361 if self.minor is None:
1362 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1364 if None in (self._lhost, self._lport, self._rhost, self._rport):
1365 _ThrowError("drbd%d: DRBD disk missing network info in"
1366 " DisconnectNet()", self.minor)
1368 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1369 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1370 sleep_time = 0.100 # we start the retry time at 100 milliseconds
1371 while time.time() < timeout_limit:
1372 status = self.GetProcStatus()
1373 if status.is_standalone:
1375 # retry the disconnect, it seems possible that due to a
1376 # well-time disconnect on the peer, my disconnect command might
1377 # be ignored and forgotten
1378 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1380 time.sleep(sleep_time)
1381 sleep_time = min(2, sleep_time * 1.5)
1383 if not status.is_standalone:
1384 if ever_disconnected:
1385 msg = ("drbd%d: device did not react to the"
1386 " 'disconnect' command in a timely manner")
1388 msg = "drbd%d: can't shutdown network, even after multiple retries"
1389 _ThrowError(msg, self.minor)
1391 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1392 if reconfig_time > 15: # hardcoded alert limit
1393 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1394 self.minor, reconfig_time)
1396 def AttachNet(self, multimaster):
1397 """Reconnects the network.
1399 This method connects the network side of the device with a
1400 specified multi-master flag. The device needs to be 'Standalone'
1401 but have valid network configuration data.
1404 - multimaster: init the network in dual-primary mode
1407 if self.minor is None:
1408 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1410 if None in (self._lhost, self._lport, self._rhost, self._rport):
1411 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1413 status = self.GetProcStatus()
1415 if not status.is_standalone:
1416 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1418 self._AssembleNet(self.minor,
1419 (self._lhost, self._lport, self._rhost, self._rport),
1420 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1421 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1424 """Check if our minor is configured.
1426 This doesn't do any device configurations - it only checks if the
1427 minor is in a state different from Unconfigured.
1429 Note that this function will not change the state of the system in
1430 any way (except in case of side-effects caused by reading from
1434 used_devs = self.GetUsedDevs()
1435 if self._aminor in used_devs:
1436 minor = self._aminor
1440 self._SetFromMinor(minor)
1441 return minor is not None
1444 """Assemble the drbd.
1447 - if we have a configured device, we try to ensure that it matches
1449 - if not, we create it from zero
1452 super(DRBD8, self).Assemble()
1455 if self.minor is None:
1456 # local device completely unconfigured
1457 self._FastAssemble()
1459 # we have to recheck the local and network status and try to fix
1461 self._SlowAssemble()
1463 def _SlowAssemble(self):
1464 """Assembles the DRBD device from a (partially) configured device.
1466 In case of partially attached (local device matches but no network
1467 setup), we perform the network attach. If successful, we re-test
1468 the attach if can return success.
1471 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1472 for minor in (self._aminor,):
1473 info = self._GetDevInfo(self._GetShowData(minor))
1474 match_l = self._MatchesLocal(info)
1475 match_r = self._MatchesNet(info)
1477 if match_l and match_r:
1478 # everything matches
1481 if match_l and not match_r and "local_addr" not in info:
1482 # disk matches, but not attached to network, attach and recheck
1483 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1484 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1485 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1488 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1489 " show' disagrees", minor)
1491 if match_r and "local_dev" not in info:
1492 # no local disk, but network attached and it matches
1493 self._AssembleLocal(minor, self._children[0].dev_path,
1494 self._children[1].dev_path, self.size)
1495 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1498 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1499 " show' disagrees", minor)
1501 # this case must be considered only if we actually have local
1502 # storage, i.e. not in diskless mode, because all diskless
1503 # devices are equal from the point of view of local
1505 if (match_l and "local_dev" in info and
1506 not match_r and "local_addr" in info):
1507 # strange case - the device network part points to somewhere
1508 # else, even though its local storage is ours; as we own the
1509 # drbd space, we try to disconnect from the remote peer and
1510 # reconnect to our correct one
1512 self._ShutdownNet(minor)
1513 except errors.BlockDeviceError, err:
1514 _ThrowError("drbd%d: device has correct local storage, wrong"
1515 " remote peer and is unable to disconnect in order"
1516 " to attach to the correct peer: %s", minor, str(err))
1517 # note: _AssembleNet also handles the case when we don't want
1518 # local storage (i.e. one or more of the _[lr](host|port) is
1520 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1521 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1522 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1525 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1526 " show' disagrees", minor)
1531 self._SetFromMinor(minor)
1533 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1536 def _FastAssemble(self):
1537 """Assemble the drbd device from zero.
1539 This is run when in Assemble we detect our minor is unused.
1542 minor = self._aminor
1543 if self._children and self._children[0] and self._children[1]:
1544 self._AssembleLocal(minor, self._children[0].dev_path,
1545 self._children[1].dev_path, self.size)
1546 if self._lhost and self._lport and self._rhost and self._rport:
1547 self._AssembleNet(minor,
1548 (self._lhost, self._lport, self._rhost, self._rport),
1549 constants.DRBD_NET_PROTOCOL,
1550 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1551 self._SetFromMinor(minor)
1554 def _ShutdownLocal(cls, minor):
1555 """Detach from the local device.
1557 I/Os will continue to be served from the remote device. If we
1558 don't have a remote device, this operation will fail.
1561 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1563 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1566 def _ShutdownNet(cls, minor):
1567 """Disconnect from the remote peer.
1569 This fails if we don't have a local device.
1572 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1574 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1577 def _ShutdownAll(cls, minor):
1578 """Deactivate the device.
1580 This will, of course, fail if the device is in use.
1583 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1585 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1586 minor, result.output)
1589 """Shutdown the DRBD device.
1592 if self.minor is None and not self.Attach():
1593 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1597 self.dev_path = None
1598 self._ShutdownAll(minor)
1601 """Stub remove for DRBD devices.
1607 def Create(cls, unique_id, children, size):
1608 """Create a new DRBD8 device.
1610 Since DRBD devices are not created per se, just assembled, this
1611 function only initializes the metadata.
1614 if len(children) != 2:
1615 raise errors.ProgrammerError("Invalid setup for the drbd device")
1616 # check that the minor is unused
1617 aminor = unique_id[4]
1618 proc_info = cls._MassageProcData(cls._GetProcData())
1619 if aminor in proc_info:
1620 status = DRBD8Status(proc_info[aminor])
1621 in_use = status.is_in_use
1625 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1628 if not meta.Attach():
1629 _ThrowError("drbd%d: can't attach to meta device '%s'",
1631 cls._CheckMetaSize(meta.dev_path)
1632 cls._InitMeta(aminor, meta.dev_path)
1633 return cls(unique_id, children, size)
1635 def Grow(self, amount):
1636 """Resize the DRBD device and its backing storage.
1639 if self.minor is None:
1640 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1641 if len(self._children) != 2 or None in self._children:
1642 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1643 self._children[0].Grow(amount)
1644 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1645 "%dm" % (self.size + amount)])
1647 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1650 class FileStorage(BlockDev):
1653 This class represents the a file storage backend device.
1655 The unique_id for the file device is a (file_driver, file_path) tuple.
1658 def __init__(self, unique_id, children, size):
1659 """Initalizes a file device backend.
1663 raise errors.BlockDeviceError("Invalid setup for file device")
1664 super(FileStorage, self).__init__(unique_id, children, size)
1665 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1666 raise ValueError("Invalid configuration data %s" % str(unique_id))
1667 self.driver = unique_id[0]
1668 self.dev_path = unique_id[1]
1672 """Assemble the device.
1674 Checks whether the file device exists, raises BlockDeviceError otherwise.
1677 if not os.path.exists(self.dev_path):
1678 _ThrowError("File device '%s' does not exist" % self.dev_path)
1681 """Shutdown the device.
1683 This is a no-op for the file type, as we don't deactivate
1684 the file on shutdown.
1689 def Open(self, force=False):
1690 """Make the device ready for I/O.
1692 This is a no-op for the file type.
1698 """Notifies that the device will no longer be used for I/O.
1700 This is a no-op for the file type.
1706 """Remove the file backing the block device.
1709 @return: True if the removal was successful
1713 os.remove(self.dev_path)
1714 except OSError, err:
1715 if err.errno != errno.ENOENT:
1716 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1719 """Attach to an existing file.
1721 Check if this file already exists.
1724 @return: True if file exists
1727 self.attached = os.path.exists(self.dev_path)
1728 return self.attached
1731 def Create(cls, unique_id, children, size):
1732 """Create a new file.
1734 @param size: the size of file in MiB
1736 @rtype: L{bdev.FileStorage}
1737 @return: an instance of FileStorage
1740 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1741 raise ValueError("Invalid configuration data %s" % str(unique_id))
1742 dev_path = unique_id[1]
1743 if os.path.exists(dev_path):
1744 _ThrowError("File already existing: %s", dev_path)
1746 f = open(dev_path, 'w')
1747 f.truncate(size * 1024 * 1024)
1749 except IOError, err:
1750 _ThrowError("Error in file creation: %", str(err))
1752 return FileStorage(unique_id, children, size)
1756 constants.LD_LV: LogicalVolume,
1757 constants.LD_DRBD8: DRBD8,
1758 constants.LD_FILE: FileStorage,
1762 def FindDevice(dev_type, unique_id, children, size):
1763 """Search for an existing, assembled device.
1765 This will succeed only if the device exists and is assembled, but it
1766 does not do any actions in order to activate the device.
1769 if dev_type not in DEV_MAP:
1770 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1771 device = DEV_MAP[dev_type](unique_id, children, size)
1772 if not device.attached:
1777 def Assemble(dev_type, unique_id, children, size):
1778 """Try to attach or assemble an existing device.
1780 This will attach to assemble the device, as needed, to bring it
1781 fully up. It must be safe to run on already-assembled devices.
1784 if dev_type not in DEV_MAP:
1785 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1786 device = DEV_MAP[dev_type](unique_id, children, size)
1791 def Create(dev_type, unique_id, children, size):
1795 if dev_type not in DEV_MAP:
1796 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1797 device = DEV_MAP[dev_type].Create(unique_id, children, size)