4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Block device abstraction"""
27 import pyparsing as pyp
31 from ganeti import utils
32 from ganeti import errors
33 from ganeti import constants
34 from ganeti import objects
37 # Size of reads in _CanReadDevice
38 _DEVICE_READ_SIZE = 128 * 1024
41 def _IgnoreError(fn, *args, **kwargs):
42 """Executes the given function, ignoring BlockDeviceErrors.
44 This is used in order to simplify the execution of cleanup or
48 @return: True when fn didn't raise an exception, False otherwise
54 except errors.BlockDeviceError, err:
55 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
59 def _ThrowError(msg, *args):
60 """Log an error to the node daemon and the raise an exception.
63 @param msg: the text of the exception
64 @raise errors.BlockDeviceError
70 raise errors.BlockDeviceError(msg)
73 def _CanReadDevice(path):
74 """Check if we can read from the given device.
76 This tries to read the first 128k of the device.
80 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
82 except EnvironmentError:
83 logging.warning("Can't read from device %s", path, exc_info=True)
87 class BlockDev(object):
88 """Block device abstract class.
90 A block device can be in the following states:
91 - not existing on the system, and by `Create()` it goes into:
92 - existing but not setup/not active, and by `Assemble()` goes into:
93 - active read-write and by `Open()` it goes into
94 - online (=used, or ready for use)
96 A device can also be online but read-only, however we are not using
97 the readonly state (LV has it, if needed in the future) and we are
98 usually looking at this like at a stack, so it's easier to
99 conceptualise the transition from not-existing to online and back
102 The many different states of the device are due to the fact that we
103 need to cover many device types:
104 - logical volumes are created, lvchange -a y $lv, and used
105 - drbd devices are attached to a local disk/remote peer and made primary
107 A block device is identified by three items:
108 - the /dev path of the device (dynamic)
109 - a unique ID of the device (static)
110 - it's major/minor pair (dynamic)
112 Not all devices implement both the first two as distinct items. LVM
113 logical volumes have their unique ID (the pair volume group, logical
114 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
115 the /dev path is again dynamic and the unique id is the pair (host1,
116 dev1), (host2, dev2).
118 You can get to a device in two ways:
119 - creating the (real) device, which returns you
120 an attached instance (lvcreate)
121 - attaching of a python instance to an existing (real) device
123 The second point, the attachement to a device, is different
124 depending on whether the device is assembled or not. At init() time,
125 we search for a device with the same unique_id as us. If found,
126 good. It also means that the device is already assembled. If not,
127 after assembly we'll have our correct major/minor.
130 def __init__(self, unique_id, children, size):
131 self._children = children
133 self.unique_id = unique_id
136 self.attached = False
140 """Assemble the device from its components.
142 Implementations of this method by child classes must ensure that:
143 - after the device has been assembled, it knows its major/minor
144 numbers; this allows other devices (usually parents) to probe
145 correctly for their children
146 - calling this method on an existing, in-use device is safe
147 - if the device is already configured (and in an OK state),
148 this method is idempotent
154 """Find a device which matches our config and attach to it.
157 raise NotImplementedError
160 """Notifies that the device will no longer be used for I/O.
163 raise NotImplementedError
166 def Create(cls, unique_id, children, size):
167 """Create the device.
169 If the device cannot be created, it will return None
170 instead. Error messages go to the logging system.
172 Note that for some devices, the unique_id is used, and for other,
173 the children. The idea is that these two, taken together, are
174 enough for both creation and assembly (later).
177 raise NotImplementedError
180 """Remove this device.
182 This makes sense only for some of the device types: LV and file
183 storage. Also note that if the device can't attach, the removal
187 raise NotImplementedError
189 def Rename(self, new_id):
190 """Rename this device.
192 This may or may not make sense for a given device type.
195 raise NotImplementedError
197 def Open(self, force=False):
198 """Make the device ready for use.
200 This makes the device ready for I/O. For now, just the DRBD
203 The force parameter signifies that if the device has any kind of
204 --force thing, it should be used, we know what we are doing.
207 raise NotImplementedError
210 """Shut down the device, freeing its children.
212 This undoes the `Assemble()` work, except for the child
213 assembling; as such, the children on the device are still
214 assembled after this call.
217 raise NotImplementedError
219 def SetSyncSpeed(self, speed):
220 """Adjust the sync speed of the mirror.
222 In case this is not a mirroring device, this is no-op.
227 for child in self._children:
228 result = result and child.SetSyncSpeed(speed)
231 def GetSyncStatus(self):
232 """Returns the sync status of the device.
234 If this device is a mirroring device, this function returns the
235 status of the mirror.
237 If sync_percent is None, it means the device is not syncing.
239 If estimated_time is None, it means we can't estimate
240 the time needed, otherwise it's the time left in seconds.
242 If is_degraded is True, it means the device is missing
243 redundancy. This is usually a sign that something went wrong in
244 the device setup, if sync_percent is None.
246 The ldisk parameter represents the degradation of the local
247 data. This is only valid for some devices, the rest will always
248 return False (not degraded).
250 @rtype: objects.BlockDevStatus
253 return objects.BlockDevStatus(dev_path=self.dev_path,
259 ldisk_status=constants.LDS_OKAY)
261 def CombinedSyncStatus(self):
262 """Calculate the mirror status recursively for our children.
264 The return value is the same as for `GetSyncStatus()` except the
265 minimum percent and maximum time are calculated across our
268 @rtype: objects.BlockDevStatus
271 status = self.GetSyncStatus()
273 min_percent = status.sync_percent
274 max_time = status.estimated_time
275 is_degraded = status.is_degraded
276 ldisk_status = status.ldisk_status
279 for child in self._children:
280 child_status = child.GetSyncStatus()
282 if min_percent is None:
283 min_percent = child_status.sync_percent
284 elif child_status.sync_percent is not None:
285 min_percent = min(min_percent, child_status.sync_percent)
288 max_time = child_status.estimated_time
289 elif child_status.estimated_time is not None:
290 max_time = max(max_time, child_status.estimated_time)
292 is_degraded = is_degraded or child_status.is_degraded
294 if ldisk_status is None:
295 ldisk_status = child_status.ldisk_status
296 elif child_status.ldisk_status is not None:
297 ldisk_status = max(ldisk_status, child_status.ldisk_status)
299 return objects.BlockDevStatus(dev_path=self.dev_path,
302 sync_percent=min_percent,
303 estimated_time=max_time,
304 is_degraded=is_degraded,
305 ldisk_status=ldisk_status)
308 def SetInfo(self, text):
309 """Update metadata with info text.
311 Only supported for some device types.
314 for child in self._children:
317 def Grow(self, amount):
318 """Grow the block device.
320 @param amount: the amount (in mebibytes) to grow with
323 raise NotImplementedError
325 def GetActualSize(self):
326 """Return the actual disk size.
328 @note: the device needs to be active when this is called
331 assert self.attached, "BlockDevice not attached in GetActualSize()"
332 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
334 _ThrowError("blockdev failed (%s): %s",
335 result.fail_reason, result.output)
337 sz = int(result.output.strip())
338 except (ValueError, TypeError), err:
339 _ThrowError("Failed to parse blockdev output: %s", str(err))
343 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
344 (self.__class__, self.unique_id, self._children,
345 self.major, self.minor, self.dev_path))
348 class LogicalVolume(BlockDev):
349 """Logical Volume block device.
352 def __init__(self, unique_id, children, size):
353 """Attaches to a LV device.
355 The unique_id is a tuple (vg_name, lv_name)
358 super(LogicalVolume, self).__init__(unique_id, children, size)
359 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
360 raise ValueError("Invalid configuration data %s" % str(unique_id))
361 self._vg_name, self._lv_name = unique_id
362 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
363 self._degraded = True
364 self.major = self.minor = self.pe_size = self.stripe_count = None
368 def Create(cls, unique_id, children, size):
369 """Create a new logical volume.
372 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
373 raise errors.ProgrammerError("Invalid configuration data %s" %
375 vg_name, lv_name = unique_id
376 pvs_info = cls.GetPVInfo([vg_name])
378 _ThrowError("Can't compute PV info for vg %s", vg_name)
382 pvlist = [ pv[1] for pv in pvs_info ]
383 if utils.any(pvlist, lambda v: ":" in v):
384 _ThrowError("Some of your PVs have invalid character ':'"
386 free_size = sum([ pv[0] for pv in pvs_info ])
387 current_pvs = len(pvlist)
388 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
390 # The size constraint should have been checked from the master before
391 # calling the create function.
393 _ThrowError("Not enough free space: required %s,"
394 " available %s", size, free_size)
395 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
396 # If the free space is not well distributed, we won't be able to
397 # create an optimally-striped volume; in that case, we want to try
398 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
400 for stripes_arg in range(stripes, 0, -1):
401 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
402 if not result.failed:
405 _ThrowError("LV create failed (%s): %s",
406 result.fail_reason, result.output)
407 return LogicalVolume(unique_id, children, size)
410 def GetPVInfo(vg_names, filter_allocatable=True):
411 """Get the free space info for PVs in a volume group.
413 @param vg_names: list of volume group names, if empty all will be returned
414 @param filter_allocatable: whether to skip over unallocatable PVs
417 @return: list of tuples (free_space, name) with free_space in mebibytes
421 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
422 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
423 "--separator=%s" % sep ]
424 result = utils.RunCmd(command)
426 logging.error("Can't get the PV information: %s - %s",
427 result.fail_reason, result.output)
430 for line in result.stdout.splitlines():
431 fields = line.strip().split(sep)
433 logging.error("Can't parse pvs output: line '%s'", line)
435 # (possibly) skip over pvs which are not allocatable
436 if filter_allocatable and fields[3][0] != 'a':
438 # (possibly) skip over pvs which are not in the right volume group(s)
439 if vg_names and fields[1] not in vg_names:
441 data.append((float(fields[2]), fields[0], fields[1]))
446 """Remove this logical volume.
449 if not self.minor and not self.Attach():
450 # the LV does not exist
452 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
453 (self._vg_name, self._lv_name)])
455 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
457 def Rename(self, new_id):
458 """Rename this logical volume.
461 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
462 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
463 new_vg, new_name = new_id
464 if new_vg != self._vg_name:
465 raise errors.ProgrammerError("Can't move a logical volume across"
466 " volume groups (from %s to to %s)" %
467 (self._vg_name, new_vg))
468 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
470 _ThrowError("Failed to rename the logical volume: %s", result.output)
471 self._lv_name = new_name
472 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
475 """Attach to an existing LV.
477 This method will try to see if an existing and active LV exists
478 which matches our name. If so, its major/minor will be
482 self.attached = False
483 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
484 "--units=m", "--nosuffix",
485 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
486 "vg_extent_size,stripes", self.dev_path])
488 logging.error("Can't find LV %s: %s, %s",
489 self.dev_path, result.fail_reason, result.output)
491 # the output can (and will) have multiple lines for multi-segment
492 # LVs, as the 'stripes' parameter is a segment one, so we take
493 # only the last entry, which is the one we're interested in; note
494 # that with LVM2 anyway the 'stripes' value must be constant
495 # across segments, so this is a no-op actually
496 out = result.stdout.splitlines()
497 if not out: # totally empty result? splitlines() returns at least
498 # one line for any non-empty string
499 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
501 out = out[-1].strip().rstrip(',')
504 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
507 status, major, minor, pe_size, stripes = out
509 logging.error("lvs lv_attr is not 6 characters (%s)", status)
515 except ValueError, err:
516 logging.error("lvs major/minor cannot be parsed: %s", str(err))
519 pe_size = int(float(pe_size))
520 except (TypeError, ValueError), err:
521 logging.error("Can't parse vg extent size: %s", err)
525 stripes = int(stripes)
526 except (TypeError, ValueError), err:
527 logging.error("Can't parse the number of stripes: %s", err)
532 self.pe_size = pe_size
533 self.stripe_count = stripes
534 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
540 """Assemble the device.
542 We always run `lvchange -ay` on the LV to ensure it's active before
543 use, as there were cases when xenvg was not active after boot
544 (also possibly after disk issues).
547 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
549 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
552 """Shutdown the device.
554 This is a no-op for the LV device type, as we don't deactivate the
560 def GetSyncStatus(self):
561 """Returns the sync status of the device.
563 If this device is a mirroring device, this function returns the
564 status of the mirror.
566 For logical volumes, sync_percent and estimated_time are always
567 None (no recovery in progress, as we don't handle the mirrored LV
568 case). The is_degraded parameter is the inverse of the ldisk
571 For the ldisk parameter, we check if the logical volume has the
572 'virtual' type, which means it's not backed by existing storage
573 anymore (read from it return I/O error). This happens after a
574 physical disk failure and subsequent 'vgreduce --removemissing' on
577 The status was already read in Attach, so we just return it.
579 @rtype: objects.BlockDevStatus
583 ldisk_status = constants.LDS_FAULTY
585 ldisk_status = constants.LDS_OKAY
587 return objects.BlockDevStatus(dev_path=self.dev_path,
592 is_degraded=self._degraded,
593 ldisk_status=ldisk_status)
595 def Open(self, force=False):
596 """Make the device ready for I/O.
598 This is a no-op for the LV device type.
604 """Notifies that the device will no longer be used for I/O.
606 This is a no-op for the LV device type.
611 def Snapshot(self, size):
612 """Create a snapshot copy of an lvm block device.
615 snap_name = self._lv_name + ".snap"
617 # remove existing snapshot if found
618 snap = LogicalVolume((self._vg_name, snap_name), None, size)
619 _IgnoreError(snap.Remove)
621 pvs_info = self.GetPVInfo([self._vg_name])
623 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
626 free_size, _, _ = pvs_info[0]
628 _ThrowError("Not enough free space: required %s,"
629 " available %s", size, free_size)
631 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
632 "-n%s" % snap_name, self.dev_path])
634 _ThrowError("command: %s error: %s - %s",
635 result.cmd, result.fail_reason, result.output)
639 def SetInfo(self, text):
640 """Update metadata with info text.
643 BlockDev.SetInfo(self, text)
645 # Replace invalid characters
646 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
647 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
649 # Only up to 128 characters are allowed
652 result = utils.RunCmd(["lvchange", "--addtag", text,
655 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
658 def Grow(self, amount):
659 """Grow the logical volume.
662 if self.pe_size is None or self.stripe_count is None:
663 if not self.Attach():
664 _ThrowError("Can't attach to LV during Grow()")
665 full_stripe_size = self.pe_size * self.stripe_count
666 rest = amount % full_stripe_size
668 amount += full_stripe_size - rest
669 # we try multiple algorithms since the 'best' ones might not have
670 # space available in the right place, but later ones might (since
671 # they have less constraints); also note that only recent LVM
673 for alloc_policy in "contiguous", "cling", "normal":
674 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
675 "-L", "+%dm" % amount, self.dev_path])
676 if not result.failed:
678 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
681 class DRBD8Status(object):
682 """A DRBD status representation class.
684 Note that this doesn't support unconfigured devices (cs:Unconfigured).
687 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
688 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
689 "\s+ds:([^/]+)/(\S+)\s+.*$")
690 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
691 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
693 CS_UNCONFIGURED = "Unconfigured"
694 CS_STANDALONE = "StandAlone"
695 CS_WFCONNECTION = "WFConnection"
696 CS_WFREPORTPARAMS = "WFReportParams"
697 CS_CONNECTED = "Connected"
698 CS_STARTINGSYNCS = "StartingSyncS"
699 CS_STARTINGSYNCT = "StartingSyncT"
700 CS_WFBITMAPS = "WFBitMapS"
701 CS_WFBITMAPT = "WFBitMapT"
702 CS_WFSYNCUUID = "WFSyncUUID"
703 CS_SYNCSOURCE = "SyncSource"
704 CS_SYNCTARGET = "SyncTarget"
705 CS_PAUSEDSYNCS = "PausedSyncS"
706 CS_PAUSEDSYNCT = "PausedSyncT"
707 CSET_SYNC = frozenset([
720 DS_DISKLESS = "Diskless"
721 DS_ATTACHING = "Attaching" # transient state
722 DS_FAILED = "Failed" # transient state, next: diskless
723 DS_NEGOTIATING = "Negotiating" # transient state
724 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
725 DS_OUTDATED = "Outdated"
726 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
727 DS_CONSISTENT = "Consistent"
728 DS_UPTODATE = "UpToDate" # normal state
730 RO_PRIMARY = "Primary"
731 RO_SECONDARY = "Secondary"
732 RO_UNKNOWN = "Unknown"
734 def __init__(self, procline):
735 u = self.UNCONF_RE.match(procline)
737 self.cstatus = self.CS_UNCONFIGURED
738 self.lrole = self.rrole = self.ldisk = self.rdisk = None
740 m = self.LINE_RE.match(procline)
742 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
743 self.cstatus = m.group(1)
744 self.lrole = m.group(2)
745 self.rrole = m.group(3)
746 self.ldisk = m.group(4)
747 self.rdisk = m.group(5)
749 # end reading of data from the LINE_RE or UNCONF_RE
751 self.is_standalone = self.cstatus == self.CS_STANDALONE
752 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
753 self.is_connected = self.cstatus == self.CS_CONNECTED
754 self.is_primary = self.lrole == self.RO_PRIMARY
755 self.is_secondary = self.lrole == self.RO_SECONDARY
756 self.peer_primary = self.rrole == self.RO_PRIMARY
757 self.peer_secondary = self.rrole == self.RO_SECONDARY
758 self.both_primary = self.is_primary and self.peer_primary
759 self.both_secondary = self.is_secondary and self.peer_secondary
761 self.is_diskless = self.ldisk == self.DS_DISKLESS
762 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
764 self.is_in_resync = self.cstatus in self.CSET_SYNC
765 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
767 m = self.SYNC_RE.match(procline)
769 self.sync_percent = float(m.group(1))
770 hours = int(m.group(2))
771 minutes = int(m.group(3))
772 seconds = int(m.group(4))
773 self.est_time = hours * 3600 + minutes * 60 + seconds
775 # we have (in this if branch) no percent information, but if
776 # we're resyncing we need to 'fake' a sync percent information,
777 # as this is how cmdlib determines if it makes sense to wait for
779 if self.is_in_resync:
780 self.sync_percent = 0
782 self.sync_percent = None
786 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
789 This class contains a few bits of common functionality between the
790 0.7 and 8.x versions of DRBD.
793 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
794 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
797 _ST_UNCONFIGURED = "Unconfigured"
798 _ST_WFCONNECTION = "WFConnection"
799 _ST_CONNECTED = "Connected"
801 _STATUS_FILE = "/proc/drbd"
804 def _GetProcData(filename=_STATUS_FILE):
805 """Return data from /proc/drbd.
809 data = utils.ReadFile(filename).splitlines()
810 except EnvironmentError, err:
811 if err.errno == errno.ENOENT:
812 _ThrowError("The file %s cannot be opened, check if the module"
813 " is loaded (%s)", filename, str(err))
815 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
817 _ThrowError("Can't read any data from %s", filename)
821 def _MassageProcData(data):
822 """Transform the output of _GetProdData into a nicer form.
824 @return: a dictionary of minor: joined lines from /proc/drbd
828 lmatch = re.compile("^ *([0-9]+):.*$")
830 old_minor = old_line = None
832 if not line: # completely empty lines, as can be returned by drbd8.0+
834 lresult = lmatch.match(line)
835 if lresult is not None:
836 if old_minor is not None:
837 results[old_minor] = old_line
838 old_minor = int(lresult.group(1))
841 if old_minor is not None:
842 old_line += " " + line.strip()
844 if old_minor is not None:
845 results[old_minor] = old_line
849 def _GetVersion(cls):
850 """Return the DRBD version.
852 This will return a dict with keys:
858 - proto2 (only on drbd > 8.2.X)
861 proc_data = cls._GetProcData()
862 first_line = proc_data[0].strip()
863 version = cls._VERSION_RE.match(first_line)
865 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
868 values = version.groups()
869 retval = {'k_major': int(values[0]),
870 'k_minor': int(values[1]),
871 'k_point': int(values[2]),
872 'api': int(values[3]),
873 'proto': int(values[4]),
875 if values[5] is not None:
876 retval['proto2'] = values[5]
882 """Return the path to a drbd device for a given minor.
885 return "/dev/drbd%d" % minor
888 def GetUsedDevs(cls):
889 """Compute the list of used DRBD devices.
892 data = cls._GetProcData()
895 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
897 match = valid_line.match(line)
900 minor = int(match.group(1))
901 state = match.group(2)
902 if state == cls._ST_UNCONFIGURED:
904 used_devs[minor] = state, line
908 def _SetFromMinor(self, minor):
909 """Set our parameters based on the given minor.
911 This sets our minor variable and our dev_path.
915 self.minor = self.dev_path = None
916 self.attached = False
919 self.dev_path = self._DevPath(minor)
923 def _CheckMetaSize(meta_device):
924 """Check if the given meta device looks like a valid one.
926 This currently only check the size, which must be around
930 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
932 _ThrowError("Failed to get device size: %s - %s",
933 result.fail_reason, result.output)
935 sectors = int(result.stdout)
937 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
938 bytes = sectors * 512
939 if bytes < 128 * 1024 * 1024: # less than 128MiB
940 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
941 # the maximum *valid* size of the meta device when living on top
942 # of LVM is hard to compute: it depends on the number of stripes
943 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
944 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
945 # size meta device; as such, we restrict it to 1GB (a little bit
946 # too generous, but making assumptions about PE size is hard)
947 if bytes > 1024 * 1024 * 1024:
948 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
950 def Rename(self, new_id):
953 This is not supported for drbd devices.
956 raise errors.ProgrammerError("Can't rename a drbd device")
959 class DRBD8(BaseDRBD):
960 """DRBD v8.x block device.
962 This implements the local host part of the DRBD device, i.e. it
963 doesn't do anything to the supposed peer. If you need a fully
964 connected DRBD pair, you need to use this class on both hosts.
966 The unique_id for the drbd device is the (local_ip, local_port,
967 remote_ip, remote_port) tuple, and it must have two children: the
968 data device and the meta_device. The meta device is checked for
969 valid size and is zeroed on create.
976 _NET_RECONFIG_TIMEOUT = 60
978 def __init__(self, unique_id, children, size):
979 if children and children.count(None) > 0:
981 if len(children) not in (0, 2):
982 raise ValueError("Invalid configuration data %s" % str(children))
983 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
984 raise ValueError("Invalid configuration data %s" % str(unique_id))
985 (self._lhost, self._lport,
986 self._rhost, self._rport,
987 self._aminor, self._secret) = unique_id
989 if not _CanReadDevice(children[1].dev_path):
990 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
992 super(DRBD8, self).__init__(unique_id, children, size)
993 self.major = self._DRBD_MAJOR
994 version = self._GetVersion()
995 if version['k_major'] != 8 :
996 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
997 " usage: kernel is %s.%s, ganeti wants 8.x",
998 version['k_major'], version['k_minor'])
1000 if (self._lhost is not None and self._lhost == self._rhost and
1001 self._lport == self._rport):
1002 raise ValueError("Invalid configuration data, same local/remote %s" %
1007 def _InitMeta(cls, minor, dev_path):
1008 """Initialize a meta device.
1010 This will not work if the given minor is in use.
1013 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1014 "v08", dev_path, "0", "create-md"])
1016 _ThrowError("Can't initialize meta device: %s", result.output)
1019 def _FindUnusedMinor(cls):
1020 """Find an unused DRBD device.
1022 This is specific to 8.x as the minors are allocated dynamically,
1023 so non-existing numbers up to a max minor count are actually free.
1026 data = cls._GetProcData()
1028 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1029 used_line = re.compile("^ *([0-9]+): cs:")
1032 match = unused_line.match(line)
1034 return int(match.group(1))
1035 match = used_line.match(line)
1037 minor = int(match.group(1))
1038 highest = max(highest, minor)
1039 if highest is None: # there are no minors in use at all
1041 if highest >= cls._MAX_MINORS:
1042 logging.error("Error: no free drbd minors!")
1043 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1047 def _GetShowParser(cls):
1048 """Return a parser for `drbd show` output.
1050 This will either create or return an already-create parser for the
1051 output of the command `drbd show`.
1054 if cls._PARSE_SHOW is not None:
1055 return cls._PARSE_SHOW
1058 lbrace = pyp.Literal("{").suppress()
1059 rbrace = pyp.Literal("}").suppress()
1060 semi = pyp.Literal(";").suppress()
1061 # this also converts the value to an int
1062 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1064 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1065 defa = pyp.Literal("_is_default").suppress()
1066 dbl_quote = pyp.Literal('"').suppress()
1068 keyword = pyp.Word(pyp.alphanums + '-')
1071 value = pyp.Word(pyp.alphanums + '_-/.:')
1072 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1073 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1074 pyp.Optional(pyp.Literal("ipv6")).suppress())
1075 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1076 pyp.Literal(':').suppress() + number)
1077 # meta device, extended syntax
1078 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1079 number + pyp.Word(']').suppress())
1080 # device name, extended syntax
1081 device_value = pyp.Literal("minor").suppress() + number
1084 stmt = (~rbrace + keyword + ~lbrace +
1085 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1087 pyp.Optional(defa) + semi +
1088 pyp.Optional(pyp.restOfLine).suppress())
1091 section_name = pyp.Word(pyp.alphas + '_')
1092 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1094 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1097 cls._PARSE_SHOW = bnf
1102 def _GetShowData(cls, minor):
1103 """Return the `drbdsetup show` data for a minor.
1106 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1108 logging.error("Can't display the drbd config: %s - %s",
1109 result.fail_reason, result.output)
1111 return result.stdout
1114 def _GetDevInfo(cls, out):
1115 """Parse details about a given DRBD minor.
1117 This return, if available, the local backing device (as a path)
1118 and the local and remote (ip, port) information from a string
1119 containing the output of the `drbdsetup show` command as returned
1127 bnf = cls._GetShowParser()
1131 results = bnf.parseString(out)
1132 except pyp.ParseException, err:
1133 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1135 # and massage the results into our desired format
1136 for section in results:
1138 if sname == "_this_host":
1139 for lst in section[1:]:
1140 if lst[0] == "disk":
1141 data["local_dev"] = lst[1]
1142 elif lst[0] == "meta-disk":
1143 data["meta_dev"] = lst[1]
1144 data["meta_index"] = lst[2]
1145 elif lst[0] == "address":
1146 data["local_addr"] = tuple(lst[1:])
1147 elif sname == "_remote_host":
1148 for lst in section[1:]:
1149 if lst[0] == "address":
1150 data["remote_addr"] = tuple(lst[1:])
1153 def _MatchesLocal(self, info):
1154 """Test if our local config matches with an existing device.
1156 The parameter should be as returned from `_GetDevInfo()`. This
1157 method tests if our local backing device is the same as the one in
1158 the info parameter, in effect testing if we look like the given
1163 backend, meta = self._children
1165 backend = meta = None
1167 if backend is not None:
1168 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1170 retval = ("local_dev" not in info)
1172 if meta is not None:
1173 retval = retval and ("meta_dev" in info and
1174 info["meta_dev"] == meta.dev_path)
1175 retval = retval and ("meta_index" in info and
1176 info["meta_index"] == 0)
1178 retval = retval and ("meta_dev" not in info and
1179 "meta_index" not in info)
1182 def _MatchesNet(self, info):
1183 """Test if our network config matches with an existing device.
1185 The parameter should be as returned from `_GetDevInfo()`. This
1186 method tests if our network configuration is the same as the one
1187 in the info parameter, in effect testing if we look like the given
1191 if (((self._lhost is None and not ("local_addr" in info)) and
1192 (self._rhost is None and not ("remote_addr" in info)))):
1195 if self._lhost is None:
1198 if not ("local_addr" in info and
1199 "remote_addr" in info):
1202 retval = (info["local_addr"] == (self._lhost, self._lport))
1203 retval = (retval and
1204 info["remote_addr"] == (self._rhost, self._rport))
1208 def _AssembleLocal(cls, minor, backend, meta, size):
1209 """Configure the local part of a DRBD device.
1212 args = ["drbdsetup", cls._DevPath(minor), "disk",
1217 args.extend(["-d", "%sm" % size])
1218 result = utils.RunCmd(args)
1220 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1223 def _AssembleNet(cls, minor, net_info, protocol,
1224 dual_pri=False, hmac=None, secret=None):
1225 """Configure the network part of the device.
1228 lhost, lport, rhost, rport = net_info
1229 if None in net_info:
1230 # we don't want network connection and actually want to make
1232 cls._ShutdownNet(minor)
1235 # Workaround for a race condition. When DRBD is doing its dance to
1236 # establish a connection with its peer, it also sends the
1237 # synchronization speed over the wire. In some cases setting the
1238 # sync speed only after setting up both sides can race with DRBD
1239 # connecting, hence we set it here before telling DRBD anything
1241 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1243 args = ["drbdsetup", cls._DevPath(minor), "net",
1244 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1245 "-A", "discard-zero-changes",
1252 args.extend(["-a", hmac, "-x", secret])
1253 result = utils.RunCmd(args)
1255 _ThrowError("drbd%d: can't setup network: %s - %s",
1256 minor, result.fail_reason, result.output)
1258 def _CheckNetworkConfig():
1259 info = cls._GetDevInfo(cls._GetShowData(minor))
1260 if not "local_addr" in info or not "remote_addr" in info:
1261 raise utils.RetryAgain()
1263 if (info["local_addr"] != (lhost, lport) or
1264 info["remote_addr"] != (rhost, rport)):
1265 raise utils.RetryAgain()
1268 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1269 except utils.RetryTimeout:
1270 _ThrowError("drbd%d: timeout while configuring network", minor)
1272 def AddChildren(self, devices):
1273 """Add a disk to the DRBD device.
1276 if self.minor is None:
1277 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1279 if len(devices) != 2:
1280 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1281 info = self._GetDevInfo(self._GetShowData(self.minor))
1282 if "local_dev" in info:
1283 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1284 backend, meta = devices
1285 if backend.dev_path is None or meta.dev_path is None:
1286 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1289 self._CheckMetaSize(meta.dev_path)
1290 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1292 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1293 self._children = devices
1295 def RemoveChildren(self, devices):
1296 """Detach the drbd device from local storage.
1299 if self.minor is None:
1300 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1302 # early return if we don't actually have backing storage
1303 info = self._GetDevInfo(self._GetShowData(self.minor))
1304 if "local_dev" not in info:
1306 if len(self._children) != 2:
1307 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1309 if self._children.count(None) == 2: # we don't actually have children :)
1310 logging.warning("drbd%d: requested detach while detached", self.minor)
1312 if len(devices) != 2:
1313 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1314 for child, dev in zip(self._children, devices):
1315 if dev != child.dev_path:
1316 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1317 " RemoveChildren", self.minor, dev, child.dev_path)
1319 self._ShutdownLocal(self.minor)
1323 def _SetMinorSyncSpeed(cls, minor, kbytes):
1324 """Set the speed of the DRBD syncer.
1326 This is the low-level implementation.
1329 @param minor: the drbd minor whose settings we change
1331 @param kbytes: the speed in kbytes/second
1333 @return: the success of the operation
1336 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1337 "-r", "%d" % kbytes, "--create-device"])
1339 logging.error("Can't change syncer rate: %s - %s",
1340 result.fail_reason, result.output)
1341 return not result.failed
1343 def SetSyncSpeed(self, kbytes):
1344 """Set the speed of the DRBD syncer.
1347 @param kbytes: the speed in kbytes/second
1349 @return: the success of the operation
1352 if self.minor is None:
1353 logging.info("Not attached during SetSyncSpeed")
1355 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1356 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1358 def GetProcStatus(self):
1359 """Return device data from /proc.
1362 if self.minor is None:
1363 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1364 proc_info = self._MassageProcData(self._GetProcData())
1365 if self.minor not in proc_info:
1366 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1367 return DRBD8Status(proc_info[self.minor])
1369 def GetSyncStatus(self):
1370 """Returns the sync status of the device.
1373 If sync_percent is None, it means all is ok
1374 If estimated_time is None, it means we can't estimate
1375 the time needed, otherwise it's the time left in seconds.
1378 We set the is_degraded parameter to True on two conditions:
1379 network not connected or local disk missing.
1381 We compute the ldisk parameter based on whether we have a local
1384 @rtype: objects.BlockDevStatus
1387 if self.minor is None and not self.Attach():
1388 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1390 stats = self.GetProcStatus()
1391 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1393 if stats.is_disk_uptodate:
1394 ldisk_status = constants.LDS_OKAY
1395 elif stats.is_diskless:
1396 ldisk_status = constants.LDS_FAULTY
1398 ldisk_status = constants.LDS_UNKNOWN
1400 return objects.BlockDevStatus(dev_path=self.dev_path,
1403 sync_percent=stats.sync_percent,
1404 estimated_time=stats.est_time,
1405 is_degraded=is_degraded,
1406 ldisk_status=ldisk_status)
1408 def Open(self, force=False):
1409 """Make the local state primary.
1411 If the 'force' parameter is given, the '-o' option is passed to
1412 drbdsetup. Since this is a potentially dangerous operation, the
1413 force flag should be only given after creation, when it actually
1417 if self.minor is None and not self.Attach():
1418 logging.error("DRBD cannot attach to a device during open")
1420 cmd = ["drbdsetup", self.dev_path, "primary"]
1423 result = utils.RunCmd(cmd)
1425 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1429 """Make the local state secondary.
1431 This will, of course, fail if the device is in use.
1434 if self.minor is None and not self.Attach():
1435 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1436 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1438 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1439 self.minor, result.output)
1441 def DisconnectNet(self):
1442 """Removes network configuration.
1444 This method shutdowns the network side of the device.
1446 The method will wait up to a hardcoded timeout for the device to
1447 go into standalone after the 'disconnect' command before
1448 re-configuring it, as sometimes it takes a while for the
1449 disconnect to actually propagate and thus we might issue a 'net'
1450 command while the device is still connected. If the device will
1451 still be attached to the network and we time out, we raise an
1455 if self.minor is None:
1456 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1458 if None in (self._lhost, self._lport, self._rhost, self._rport):
1459 _ThrowError("drbd%d: DRBD disk missing network info in"
1460 " DisconnectNet()", self.minor)
1462 class _DisconnectStatus:
1463 def __init__(self, ever_disconnected):
1464 self.ever_disconnected = ever_disconnected
1466 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1468 def _WaitForDisconnect():
1469 if self.GetProcStatus().is_standalone:
1472 # retry the disconnect, it seems possible that due to a well-time
1473 # disconnect on the peer, my disconnect command might be ignored and
1475 dstatus.ever_disconnected = \
1476 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1478 raise utils.RetryAgain()
1481 start_time = time.time()
1484 # Start delay at 100 milliseconds and grow up to 2 seconds
1485 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1486 self._NET_RECONFIG_TIMEOUT)
1487 except utils.RetryTimeout:
1488 if dstatus.ever_disconnected:
1489 msg = ("drbd%d: device did not react to the"
1490 " 'disconnect' command in a timely manner")
1492 msg = "drbd%d: can't shutdown network, even after multiple retries"
1494 _ThrowError(msg, self.minor)
1496 reconfig_time = time.time() - start_time
1497 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1498 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1499 self.minor, reconfig_time)
1501 def AttachNet(self, multimaster):
1502 """Reconnects the network.
1504 This method connects the network side of the device with a
1505 specified multi-master flag. The device needs to be 'Standalone'
1506 but have valid network configuration data.
1509 - multimaster: init the network in dual-primary mode
1512 if self.minor is None:
1513 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1515 if None in (self._lhost, self._lport, self._rhost, self._rport):
1516 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1518 status = self.GetProcStatus()
1520 if not status.is_standalone:
1521 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1523 self._AssembleNet(self.minor,
1524 (self._lhost, self._lport, self._rhost, self._rport),
1525 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1526 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1529 """Check if our minor is configured.
1531 This doesn't do any device configurations - it only checks if the
1532 minor is in a state different from Unconfigured.
1534 Note that this function will not change the state of the system in
1535 any way (except in case of side-effects caused by reading from
1539 used_devs = self.GetUsedDevs()
1540 if self._aminor in used_devs:
1541 minor = self._aminor
1545 self._SetFromMinor(minor)
1546 return minor is not None
1549 """Assemble the drbd.
1552 - if we have a configured device, we try to ensure that it matches
1554 - if not, we create it from zero
1557 super(DRBD8, self).Assemble()
1560 if self.minor is None:
1561 # local device completely unconfigured
1562 self._FastAssemble()
1564 # we have to recheck the local and network status and try to fix
1566 self._SlowAssemble()
1568 def _SlowAssemble(self):
1569 """Assembles the DRBD device from a (partially) configured device.
1571 In case of partially attached (local device matches but no network
1572 setup), we perform the network attach. If successful, we re-test
1573 the attach if can return success.
1576 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1577 for minor in (self._aminor,):
1578 info = self._GetDevInfo(self._GetShowData(minor))
1579 match_l = self._MatchesLocal(info)
1580 match_r = self._MatchesNet(info)
1582 if match_l and match_r:
1583 # everything matches
1586 if match_l and not match_r and "local_addr" not in info:
1587 # disk matches, but not attached to network, attach and recheck
1588 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1589 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1590 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1593 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1594 " show' disagrees", minor)
1596 if match_r and "local_dev" not in info:
1597 # no local disk, but network attached and it matches
1598 self._AssembleLocal(minor, self._children[0].dev_path,
1599 self._children[1].dev_path, self.size)
1600 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1603 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1604 " show' disagrees", minor)
1606 # this case must be considered only if we actually have local
1607 # storage, i.e. not in diskless mode, because all diskless
1608 # devices are equal from the point of view of local
1610 if (match_l and "local_dev" in info and
1611 not match_r and "local_addr" in info):
1612 # strange case - the device network part points to somewhere
1613 # else, even though its local storage is ours; as we own the
1614 # drbd space, we try to disconnect from the remote peer and
1615 # reconnect to our correct one
1617 self._ShutdownNet(minor)
1618 except errors.BlockDeviceError, err:
1619 _ThrowError("drbd%d: device has correct local storage, wrong"
1620 " remote peer and is unable to disconnect in order"
1621 " to attach to the correct peer: %s", minor, str(err))
1622 # note: _AssembleNet also handles the case when we don't want
1623 # local storage (i.e. one or more of the _[lr](host|port) is
1625 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1626 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1627 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1630 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1631 " show' disagrees", minor)
1636 self._SetFromMinor(minor)
1638 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1641 def _FastAssemble(self):
1642 """Assemble the drbd device from zero.
1644 This is run when in Assemble we detect our minor is unused.
1647 minor = self._aminor
1648 if self._children and self._children[0] and self._children[1]:
1649 self._AssembleLocal(minor, self._children[0].dev_path,
1650 self._children[1].dev_path, self.size)
1651 if self._lhost and self._lport and self._rhost and self._rport:
1652 self._AssembleNet(minor,
1653 (self._lhost, self._lport, self._rhost, self._rport),
1654 constants.DRBD_NET_PROTOCOL,
1655 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1656 self._SetFromMinor(minor)
1659 def _ShutdownLocal(cls, minor):
1660 """Detach from the local device.
1662 I/Os will continue to be served from the remote device. If we
1663 don't have a remote device, this operation will fail.
1666 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1668 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1671 def _ShutdownNet(cls, minor):
1672 """Disconnect from the remote peer.
1674 This fails if we don't have a local device.
1677 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1679 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1682 def _ShutdownAll(cls, minor):
1683 """Deactivate the device.
1685 This will, of course, fail if the device is in use.
1688 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1690 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1691 minor, result.output)
1694 """Shutdown the DRBD device.
1697 if self.minor is None and not self.Attach():
1698 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1702 self.dev_path = None
1703 self._ShutdownAll(minor)
1706 """Stub remove for DRBD devices.
1712 def Create(cls, unique_id, children, size):
1713 """Create a new DRBD8 device.
1715 Since DRBD devices are not created per se, just assembled, this
1716 function only initializes the metadata.
1719 if len(children) != 2:
1720 raise errors.ProgrammerError("Invalid setup for the drbd device")
1721 # check that the minor is unused
1722 aminor = unique_id[4]
1723 proc_info = cls._MassageProcData(cls._GetProcData())
1724 if aminor in proc_info:
1725 status = DRBD8Status(proc_info[aminor])
1726 in_use = status.is_in_use
1730 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1733 if not meta.Attach():
1734 _ThrowError("drbd%d: can't attach to meta device '%s'",
1736 cls._CheckMetaSize(meta.dev_path)
1737 cls._InitMeta(aminor, meta.dev_path)
1738 return cls(unique_id, children, size)
1740 def Grow(self, amount):
1741 """Resize the DRBD device and its backing storage.
1744 if self.minor is None:
1745 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1746 if len(self._children) != 2 or None in self._children:
1747 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1748 self._children[0].Grow(amount)
1749 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1750 "%dm" % (self.size + amount)])
1752 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1755 class FileStorage(BlockDev):
1758 This class represents the a file storage backend device.
1760 The unique_id for the file device is a (file_driver, file_path) tuple.
1763 def __init__(self, unique_id, children, size):
1764 """Initalizes a file device backend.
1768 raise errors.BlockDeviceError("Invalid setup for file device")
1769 super(FileStorage, self).__init__(unique_id, children, size)
1770 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1771 raise ValueError("Invalid configuration data %s" % str(unique_id))
1772 self.driver = unique_id[0]
1773 self.dev_path = unique_id[1]
1777 """Assemble the device.
1779 Checks whether the file device exists, raises BlockDeviceError otherwise.
1782 if not os.path.exists(self.dev_path):
1783 _ThrowError("File device '%s' does not exist" % self.dev_path)
1786 """Shutdown the device.
1788 This is a no-op for the file type, as we don't deactivate
1789 the file on shutdown.
1794 def Open(self, force=False):
1795 """Make the device ready for I/O.
1797 This is a no-op for the file type.
1803 """Notifies that the device will no longer be used for I/O.
1805 This is a no-op for the file type.
1811 """Remove the file backing the block device.
1814 @return: True if the removal was successful
1818 os.remove(self.dev_path)
1819 except OSError, err:
1820 if err.errno != errno.ENOENT:
1821 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1823 def Rename(self, new_id):
1824 """Renames the file.
1827 # TODO: implement rename for file-based storage
1828 _ThrowError("Rename is not supported for file-based storage")
1830 def Grow(self, amount):
1833 @param amount: the amount (in mebibytes) to grow with
1836 # TODO: implement grow for file-based storage
1837 _ThrowError("Grow not supported for file-based storage")
1840 """Attach to an existing file.
1842 Check if this file already exists.
1845 @return: True if file exists
1848 self.attached = os.path.exists(self.dev_path)
1849 return self.attached
1851 def GetActualSize(self):
1852 """Return the actual disk size.
1854 @note: the device needs to be active when this is called
1857 assert self.attached, "BlockDevice not attached in GetActualSize()"
1859 st = os.stat(self.dev_path)
1861 except OSError, err:
1862 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1865 def Create(cls, unique_id, children, size):
1866 """Create a new file.
1868 @param size: the size of file in MiB
1870 @rtype: L{bdev.FileStorage}
1871 @return: an instance of FileStorage
1874 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1875 raise ValueError("Invalid configuration data %s" % str(unique_id))
1876 dev_path = unique_id[1]
1877 if os.path.exists(dev_path):
1878 _ThrowError("File already existing: %s", dev_path)
1880 f = open(dev_path, 'w')
1881 f.truncate(size * 1024 * 1024)
1883 except IOError, err:
1884 _ThrowError("Error in file creation: %", str(err))
1886 return FileStorage(unique_id, children, size)
1890 constants.LD_LV: LogicalVolume,
1891 constants.LD_DRBD8: DRBD8,
1892 constants.LD_FILE: FileStorage,
1896 def FindDevice(dev_type, unique_id, children, size):
1897 """Search for an existing, assembled device.
1899 This will succeed only if the device exists and is assembled, but it
1900 does not do any actions in order to activate the device.
1903 if dev_type not in DEV_MAP:
1904 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1905 device = DEV_MAP[dev_type](unique_id, children, size)
1906 if not device.attached:
1911 def Assemble(dev_type, unique_id, children, size):
1912 """Try to attach or assemble an existing device.
1914 This will attach to assemble the device, as needed, to bring it
1915 fully up. It must be safe to run on already-assembled devices.
1918 if dev_type not in DEV_MAP:
1919 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1920 device = DEV_MAP[dev_type](unique_id, children, size)
1925 def Create(dev_type, unique_id, children, size):
1929 if dev_type not in DEV_MAP:
1930 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1931 device = DEV_MAP[dev_type].Create(unique_id, children, size)