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 (TypeError, 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+))?\)")
795 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
796 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
799 _ST_UNCONFIGURED = "Unconfigured"
800 _ST_WFCONNECTION = "WFConnection"
801 _ST_CONNECTED = "Connected"
803 _STATUS_FILE = "/proc/drbd"
806 def _GetProcData(filename=_STATUS_FILE):
807 """Return data from /proc/drbd.
811 data = utils.ReadFile(filename).splitlines()
812 except EnvironmentError, err:
813 if err.errno == errno.ENOENT:
814 _ThrowError("The file %s cannot be opened, check if the module"
815 " is loaded (%s)", filename, str(err))
817 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
819 _ThrowError("Can't read any data from %s", filename)
823 def _MassageProcData(cls, data):
824 """Transform the output of _GetProdData into a nicer form.
826 @return: a dictionary of minor: joined lines from /proc/drbd
831 old_minor = old_line = None
833 if not line: # completely empty lines, as can be returned by drbd8.0+
835 lresult = cls._VALID_LINE_RE.match(line)
836 if lresult is not None:
837 if old_minor is not None:
838 results[old_minor] = old_line
839 old_minor = int(lresult.group(1))
842 if old_minor is not None:
843 old_line += " " + line.strip()
845 if old_minor is not None:
846 results[old_minor] = old_line
850 def _GetVersion(cls):
851 """Return the DRBD version.
853 This will return a dict with keys:
859 - proto2 (only on drbd > 8.2.X)
862 proc_data = cls._GetProcData()
863 first_line = proc_data[0].strip()
864 version = cls._VERSION_RE.match(first_line)
866 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
869 values = version.groups()
870 retval = {'k_major': int(values[0]),
871 'k_minor': int(values[1]),
872 'k_point': int(values[2]),
873 'api': int(values[3]),
874 'proto': int(values[4]),
876 if values[5] is not None:
877 retval['proto2'] = values[5]
883 """Return the path to a drbd device for a given minor.
886 return "/dev/drbd%d" % minor
889 def GetUsedDevs(cls):
890 """Compute the list of used DRBD devices.
893 data = cls._GetProcData()
897 match = cls._VALID_LINE_RE.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)
936 except (TypeError, ValueError):
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()
1030 match = cls._UNUSED_LINE_RE.match(line)
1032 return int(match.group(1))
1033 match = cls._VALID_LINE_RE.match(line)
1035 minor = int(match.group(1))
1036 highest = max(highest, minor)
1037 if highest is None: # there are no minors in use at all
1039 if highest >= cls._MAX_MINORS:
1040 logging.error("Error: no free drbd minors!")
1041 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1045 def _GetShowParser(cls):
1046 """Return a parser for `drbd show` output.
1048 This will either create or return an already-create parser for the
1049 output of the command `drbd show`.
1052 if cls._PARSE_SHOW is not None:
1053 return cls._PARSE_SHOW
1056 lbrace = pyp.Literal("{").suppress()
1057 rbrace = pyp.Literal("}").suppress()
1058 semi = pyp.Literal(";").suppress()
1059 # this also converts the value to an int
1060 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1062 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1063 defa = pyp.Literal("_is_default").suppress()
1064 dbl_quote = pyp.Literal('"').suppress()
1066 keyword = pyp.Word(pyp.alphanums + '-')
1069 value = pyp.Word(pyp.alphanums + '_-/.:')
1070 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1071 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1072 pyp.Optional(pyp.Literal("ipv6")).suppress())
1073 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1074 pyp.Literal(':').suppress() + number)
1075 # meta device, extended syntax
1076 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1077 number + pyp.Word(']').suppress())
1078 # device name, extended syntax
1079 device_value = pyp.Literal("minor").suppress() + number
1082 stmt = (~rbrace + keyword + ~lbrace +
1083 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1085 pyp.Optional(defa) + semi +
1086 pyp.Optional(pyp.restOfLine).suppress())
1089 section_name = pyp.Word(pyp.alphas + '_')
1090 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1092 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1095 cls._PARSE_SHOW = bnf
1100 def _GetShowData(cls, minor):
1101 """Return the `drbdsetup show` data for a minor.
1104 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1106 logging.error("Can't display the drbd config: %s - %s",
1107 result.fail_reason, result.output)
1109 return result.stdout
1112 def _GetDevInfo(cls, out):
1113 """Parse details about a given DRBD minor.
1115 This return, if available, the local backing device (as a path)
1116 and the local and remote (ip, port) information from a string
1117 containing the output of the `drbdsetup show` command as returned
1125 bnf = cls._GetShowParser()
1129 results = bnf.parseString(out)
1130 except pyp.ParseException, err:
1131 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1133 # and massage the results into our desired format
1134 for section in results:
1136 if sname == "_this_host":
1137 for lst in section[1:]:
1138 if lst[0] == "disk":
1139 data["local_dev"] = lst[1]
1140 elif lst[0] == "meta-disk":
1141 data["meta_dev"] = lst[1]
1142 data["meta_index"] = lst[2]
1143 elif lst[0] == "address":
1144 data["local_addr"] = tuple(lst[1:])
1145 elif sname == "_remote_host":
1146 for lst in section[1:]:
1147 if lst[0] == "address":
1148 data["remote_addr"] = tuple(lst[1:])
1151 def _MatchesLocal(self, info):
1152 """Test if our local config matches with an existing device.
1154 The parameter should be as returned from `_GetDevInfo()`. This
1155 method tests if our local backing device is the same as the one in
1156 the info parameter, in effect testing if we look like the given
1161 backend, meta = self._children
1163 backend = meta = None
1165 if backend is not None:
1166 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1168 retval = ("local_dev" not in info)
1170 if meta is not None:
1171 retval = retval and ("meta_dev" in info and
1172 info["meta_dev"] == meta.dev_path)
1173 retval = retval and ("meta_index" in info and
1174 info["meta_index"] == 0)
1176 retval = retval and ("meta_dev" not in info and
1177 "meta_index" not in info)
1180 def _MatchesNet(self, info):
1181 """Test if our network config matches with an existing device.
1183 The parameter should be as returned from `_GetDevInfo()`. This
1184 method tests if our network configuration is the same as the one
1185 in the info parameter, in effect testing if we look like the given
1189 if (((self._lhost is None and not ("local_addr" in info)) and
1190 (self._rhost is None and not ("remote_addr" in info)))):
1193 if self._lhost is None:
1196 if not ("local_addr" in info and
1197 "remote_addr" in info):
1200 retval = (info["local_addr"] == (self._lhost, self._lport))
1201 retval = (retval and
1202 info["remote_addr"] == (self._rhost, self._rport))
1206 def _AssembleLocal(cls, minor, backend, meta, size):
1207 """Configure the local part of a DRBD device.
1210 args = ["drbdsetup", cls._DevPath(minor), "disk",
1215 args.extend(["-d", "%sm" % size])
1216 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1217 version = cls._GetVersion()
1218 # various DRBD versions support different disk barrier options;
1219 # what we aim here is to revert back to the 'drain' method of
1220 # disk flushes and to disable metadata barriers, in effect going
1221 # back to pre-8.0.7 behaviour
1222 vmaj = version['k_major']
1223 vmin = version['k_minor']
1224 vrel = version['k_point']
1226 if vmin == 0: # 8.0.x
1228 args.extend(['-i', '-m'])
1229 elif vmin == 2: # 8.2.x
1231 args.extend(['-i', '-m'])
1232 elif vmaj >= 3: # 8.3.x or newer
1233 args.extend(['-i', '-a', 'm'])
1234 result = utils.RunCmd(args)
1236 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1239 def _AssembleNet(cls, minor, net_info, protocol,
1240 dual_pri=False, hmac=None, secret=None):
1241 """Configure the network part of the device.
1244 lhost, lport, rhost, rport = net_info
1245 if None in net_info:
1246 # we don't want network connection and actually want to make
1248 cls._ShutdownNet(minor)
1251 # Workaround for a race condition. When DRBD is doing its dance to
1252 # establish a connection with its peer, it also sends the
1253 # synchronization speed over the wire. In some cases setting the
1254 # sync speed only after setting up both sides can race with DRBD
1255 # connecting, hence we set it here before telling DRBD anything
1257 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1259 args = ["drbdsetup", cls._DevPath(minor), "net",
1260 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1261 "-A", "discard-zero-changes",
1268 args.extend(["-a", hmac, "-x", secret])
1269 result = utils.RunCmd(args)
1271 _ThrowError("drbd%d: can't setup network: %s - %s",
1272 minor, result.fail_reason, result.output)
1274 def _CheckNetworkConfig():
1275 info = cls._GetDevInfo(cls._GetShowData(minor))
1276 if not "local_addr" in info or not "remote_addr" in info:
1277 raise utils.RetryAgain()
1279 if (info["local_addr"] != (lhost, lport) or
1280 info["remote_addr"] != (rhost, rport)):
1281 raise utils.RetryAgain()
1284 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1285 except utils.RetryTimeout:
1286 _ThrowError("drbd%d: timeout while configuring network", minor)
1288 def AddChildren(self, devices):
1289 """Add a disk to the DRBD device.
1292 if self.minor is None:
1293 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1295 if len(devices) != 2:
1296 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1297 info = self._GetDevInfo(self._GetShowData(self.minor))
1298 if "local_dev" in info:
1299 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1300 backend, meta = devices
1301 if backend.dev_path is None or meta.dev_path is None:
1302 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1305 self._CheckMetaSize(meta.dev_path)
1306 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1308 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1309 self._children = devices
1311 def RemoveChildren(self, devices):
1312 """Detach the drbd device from local storage.
1315 if self.minor is None:
1316 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1318 # early return if we don't actually have backing storage
1319 info = self._GetDevInfo(self._GetShowData(self.minor))
1320 if "local_dev" not in info:
1322 if len(self._children) != 2:
1323 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1325 if self._children.count(None) == 2: # we don't actually have children :)
1326 logging.warning("drbd%d: requested detach while detached", self.minor)
1328 if len(devices) != 2:
1329 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1330 for child, dev in zip(self._children, devices):
1331 if dev != child.dev_path:
1332 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1333 " RemoveChildren", self.minor, dev, child.dev_path)
1335 self._ShutdownLocal(self.minor)
1339 def _SetMinorSyncSpeed(cls, minor, kbytes):
1340 """Set the speed of the DRBD syncer.
1342 This is the low-level implementation.
1345 @param minor: the drbd minor whose settings we change
1347 @param kbytes: the speed in kbytes/second
1349 @return: the success of the operation
1352 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1353 "-r", "%d" % kbytes, "--create-device"])
1355 logging.error("Can't change syncer rate: %s - %s",
1356 result.fail_reason, result.output)
1357 return not result.failed
1359 def SetSyncSpeed(self, kbytes):
1360 """Set the speed of the DRBD syncer.
1363 @param kbytes: the speed in kbytes/second
1365 @return: the success of the operation
1368 if self.minor is None:
1369 logging.info("Not attached during SetSyncSpeed")
1371 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1372 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1374 def GetProcStatus(self):
1375 """Return device data from /proc.
1378 if self.minor is None:
1379 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1380 proc_info = self._MassageProcData(self._GetProcData())
1381 if self.minor not in proc_info:
1382 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1383 return DRBD8Status(proc_info[self.minor])
1385 def GetSyncStatus(self):
1386 """Returns the sync status of the device.
1389 If sync_percent is None, it means all is ok
1390 If estimated_time is None, it means we can't estimate
1391 the time needed, otherwise it's the time left in seconds.
1394 We set the is_degraded parameter to True on two conditions:
1395 network not connected or local disk missing.
1397 We compute the ldisk parameter based on whether we have a local
1400 @rtype: objects.BlockDevStatus
1403 if self.minor is None and not self.Attach():
1404 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1406 stats = self.GetProcStatus()
1407 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1409 if stats.is_disk_uptodate:
1410 ldisk_status = constants.LDS_OKAY
1411 elif stats.is_diskless:
1412 ldisk_status = constants.LDS_FAULTY
1414 ldisk_status = constants.LDS_UNKNOWN
1416 return objects.BlockDevStatus(dev_path=self.dev_path,
1419 sync_percent=stats.sync_percent,
1420 estimated_time=stats.est_time,
1421 is_degraded=is_degraded,
1422 ldisk_status=ldisk_status)
1424 def Open(self, force=False):
1425 """Make the local state primary.
1427 If the 'force' parameter is given, the '-o' option is passed to
1428 drbdsetup. Since this is a potentially dangerous operation, the
1429 force flag should be only given after creation, when it actually
1433 if self.minor is None and not self.Attach():
1434 logging.error("DRBD cannot attach to a device during open")
1436 cmd = ["drbdsetup", self.dev_path, "primary"]
1439 result = utils.RunCmd(cmd)
1441 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1445 """Make the local state secondary.
1447 This will, of course, fail if the device is in use.
1450 if self.minor is None and not self.Attach():
1451 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1452 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1454 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1455 self.minor, result.output)
1457 def DisconnectNet(self):
1458 """Removes network configuration.
1460 This method shutdowns the network side of the device.
1462 The method will wait up to a hardcoded timeout for the device to
1463 go into standalone after the 'disconnect' command before
1464 re-configuring it, as sometimes it takes a while for the
1465 disconnect to actually propagate and thus we might issue a 'net'
1466 command while the device is still connected. If the device will
1467 still be attached to the network and we time out, we raise an
1471 if self.minor is None:
1472 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1474 if None in (self._lhost, self._lport, self._rhost, self._rport):
1475 _ThrowError("drbd%d: DRBD disk missing network info in"
1476 " DisconnectNet()", self.minor)
1478 class _DisconnectStatus:
1479 def __init__(self, ever_disconnected):
1480 self.ever_disconnected = ever_disconnected
1482 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1484 def _WaitForDisconnect():
1485 if self.GetProcStatus().is_standalone:
1488 # retry the disconnect, it seems possible that due to a well-time
1489 # disconnect on the peer, my disconnect command might be ignored and
1491 dstatus.ever_disconnected = \
1492 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1494 raise utils.RetryAgain()
1497 start_time = time.time()
1500 # Start delay at 100 milliseconds and grow up to 2 seconds
1501 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1502 self._NET_RECONFIG_TIMEOUT)
1503 except utils.RetryTimeout:
1504 if dstatus.ever_disconnected:
1505 msg = ("drbd%d: device did not react to the"
1506 " 'disconnect' command in a timely manner")
1508 msg = "drbd%d: can't shutdown network, even after multiple retries"
1510 _ThrowError(msg, self.minor)
1512 reconfig_time = time.time() - start_time
1513 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1514 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1515 self.minor, reconfig_time)
1517 def AttachNet(self, multimaster):
1518 """Reconnects the network.
1520 This method connects the network side of the device with a
1521 specified multi-master flag. The device needs to be 'Standalone'
1522 but have valid network configuration data.
1525 - multimaster: init the network in dual-primary mode
1528 if self.minor is None:
1529 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1531 if None in (self._lhost, self._lport, self._rhost, self._rport):
1532 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1534 status = self.GetProcStatus()
1536 if not status.is_standalone:
1537 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1539 self._AssembleNet(self.minor,
1540 (self._lhost, self._lport, self._rhost, self._rport),
1541 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1542 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1545 """Check if our minor is configured.
1547 This doesn't do any device configurations - it only checks if the
1548 minor is in a state different from Unconfigured.
1550 Note that this function will not change the state of the system in
1551 any way (except in case of side-effects caused by reading from
1555 used_devs = self.GetUsedDevs()
1556 if self._aminor in used_devs:
1557 minor = self._aminor
1561 self._SetFromMinor(minor)
1562 return minor is not None
1565 """Assemble the drbd.
1568 - if we have a configured device, we try to ensure that it matches
1570 - if not, we create it from zero
1573 super(DRBD8, self).Assemble()
1576 if self.minor is None:
1577 # local device completely unconfigured
1578 self._FastAssemble()
1580 # we have to recheck the local and network status and try to fix
1582 self._SlowAssemble()
1584 def _SlowAssemble(self):
1585 """Assembles the DRBD device from a (partially) configured device.
1587 In case of partially attached (local device matches but no network
1588 setup), we perform the network attach. If successful, we re-test
1589 the attach if can return success.
1592 # TODO: Rewrite to not use a for loop just because there is 'break'
1593 # pylint: disable-msg=W0631
1594 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1595 for minor in (self._aminor,):
1596 info = self._GetDevInfo(self._GetShowData(minor))
1597 match_l = self._MatchesLocal(info)
1598 match_r = self._MatchesNet(info)
1600 if match_l and match_r:
1601 # everything matches
1604 if match_l and not match_r and "local_addr" not in info:
1605 # disk matches, but not attached to network, attach and recheck
1606 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1607 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1608 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1611 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1612 " show' disagrees", minor)
1614 if match_r and "local_dev" not in info:
1615 # no local disk, but network attached and it matches
1616 self._AssembleLocal(minor, self._children[0].dev_path,
1617 self._children[1].dev_path, self.size)
1618 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1621 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1622 " show' disagrees", minor)
1624 # this case must be considered only if we actually have local
1625 # storage, i.e. not in diskless mode, because all diskless
1626 # devices are equal from the point of view of local
1628 if (match_l and "local_dev" in info and
1629 not match_r and "local_addr" in info):
1630 # strange case - the device network part points to somewhere
1631 # else, even though its local storage is ours; as we own the
1632 # drbd space, we try to disconnect from the remote peer and
1633 # reconnect to our correct one
1635 self._ShutdownNet(minor)
1636 except errors.BlockDeviceError, err:
1637 _ThrowError("drbd%d: device has correct local storage, wrong"
1638 " remote peer and is unable to disconnect in order"
1639 " to attach to the correct peer: %s", minor, str(err))
1640 # note: _AssembleNet also handles the case when we don't want
1641 # local storage (i.e. one or more of the _[lr](host|port) is
1643 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1644 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1645 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1648 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1649 " show' disagrees", minor)
1654 self._SetFromMinor(minor)
1656 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1659 def _FastAssemble(self):
1660 """Assemble the drbd device from zero.
1662 This is run when in Assemble we detect our minor is unused.
1665 minor = self._aminor
1666 if self._children and self._children[0] and self._children[1]:
1667 self._AssembleLocal(minor, self._children[0].dev_path,
1668 self._children[1].dev_path, self.size)
1669 if self._lhost and self._lport and self._rhost and self._rport:
1670 self._AssembleNet(minor,
1671 (self._lhost, self._lport, self._rhost, self._rport),
1672 constants.DRBD_NET_PROTOCOL,
1673 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1674 self._SetFromMinor(minor)
1677 def _ShutdownLocal(cls, minor):
1678 """Detach from the local device.
1680 I/Os will continue to be served from the remote device. If we
1681 don't have a remote device, this operation will fail.
1684 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1686 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1689 def _ShutdownNet(cls, minor):
1690 """Disconnect from the remote peer.
1692 This fails if we don't have a local device.
1695 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1697 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1700 def _ShutdownAll(cls, minor):
1701 """Deactivate the device.
1703 This will, of course, fail if the device is in use.
1706 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1708 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1709 minor, result.output)
1712 """Shutdown the DRBD device.
1715 if self.minor is None and not self.Attach():
1716 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1720 self.dev_path = None
1721 self._ShutdownAll(minor)
1724 """Stub remove for DRBD devices.
1730 def Create(cls, unique_id, children, size):
1731 """Create a new DRBD8 device.
1733 Since DRBD devices are not created per se, just assembled, this
1734 function only initializes the metadata.
1737 if len(children) != 2:
1738 raise errors.ProgrammerError("Invalid setup for the drbd device")
1739 # check that the minor is unused
1740 aminor = unique_id[4]
1741 proc_info = cls._MassageProcData(cls._GetProcData())
1742 if aminor in proc_info:
1743 status = DRBD8Status(proc_info[aminor])
1744 in_use = status.is_in_use
1748 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1751 if not meta.Attach():
1752 _ThrowError("drbd%d: can't attach to meta device '%s'",
1754 cls._CheckMetaSize(meta.dev_path)
1755 cls._InitMeta(aminor, meta.dev_path)
1756 return cls(unique_id, children, size)
1758 def Grow(self, amount):
1759 """Resize the DRBD device and its backing storage.
1762 if self.minor is None:
1763 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1764 if len(self._children) != 2 or None in self._children:
1765 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1766 self._children[0].Grow(amount)
1767 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1768 "%dm" % (self.size + amount)])
1770 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1773 class FileStorage(BlockDev):
1776 This class represents the a file storage backend device.
1778 The unique_id for the file device is a (file_driver, file_path) tuple.
1781 def __init__(self, unique_id, children, size):
1782 """Initalizes a file device backend.
1786 raise errors.BlockDeviceError("Invalid setup for file device")
1787 super(FileStorage, self).__init__(unique_id, children, size)
1788 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1789 raise ValueError("Invalid configuration data %s" % str(unique_id))
1790 self.driver = unique_id[0]
1791 self.dev_path = unique_id[1]
1795 """Assemble the device.
1797 Checks whether the file device exists, raises BlockDeviceError otherwise.
1800 if not os.path.exists(self.dev_path):
1801 _ThrowError("File device '%s' does not exist" % self.dev_path)
1804 """Shutdown the device.
1806 This is a no-op for the file type, as we don't deactivate
1807 the file on shutdown.
1812 def Open(self, force=False):
1813 """Make the device ready for I/O.
1815 This is a no-op for the file type.
1821 """Notifies that the device will no longer be used for I/O.
1823 This is a no-op for the file type.
1829 """Remove the file backing the block device.
1832 @return: True if the removal was successful
1836 os.remove(self.dev_path)
1837 except OSError, err:
1838 if err.errno != errno.ENOENT:
1839 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1841 def Rename(self, new_id):
1842 """Renames the file.
1845 # TODO: implement rename for file-based storage
1846 _ThrowError("Rename is not supported for file-based storage")
1848 def Grow(self, amount):
1851 @param amount: the amount (in mebibytes) to grow with
1854 # TODO: implement grow for file-based storage
1855 _ThrowError("Grow not supported for file-based storage")
1858 """Attach to an existing file.
1860 Check if this file already exists.
1863 @return: True if file exists
1866 self.attached = os.path.exists(self.dev_path)
1867 return self.attached
1869 def GetActualSize(self):
1870 """Return the actual disk size.
1872 @note: the device needs to be active when this is called
1875 assert self.attached, "BlockDevice not attached in GetActualSize()"
1877 st = os.stat(self.dev_path)
1879 except OSError, err:
1880 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1883 def Create(cls, unique_id, children, size):
1884 """Create a new file.
1886 @param size: the size of file in MiB
1888 @rtype: L{bdev.FileStorage}
1889 @return: an instance of FileStorage
1892 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1893 raise ValueError("Invalid configuration data %s" % str(unique_id))
1894 dev_path = unique_id[1]
1895 if os.path.exists(dev_path):
1896 _ThrowError("File already existing: %s", dev_path)
1898 f = open(dev_path, 'w')
1899 f.truncate(size * 1024 * 1024)
1901 except IOError, err:
1902 _ThrowError("Error in file creation: %", str(err))
1904 return FileStorage(unique_id, children, size)
1908 constants.LD_LV: LogicalVolume,
1909 constants.LD_DRBD8: DRBD8,
1910 constants.LD_FILE: FileStorage,
1914 def FindDevice(dev_type, unique_id, children, size):
1915 """Search for an existing, assembled device.
1917 This will succeed only if the device exists and is assembled, but it
1918 does not do any actions in order to activate the device.
1921 if dev_type not in DEV_MAP:
1922 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1923 device = DEV_MAP[dev_type](unique_id, children, size)
1924 if not device.attached:
1929 def Assemble(dev_type, unique_id, children, size):
1930 """Try to attach or assemble an existing device.
1932 This will attach to assemble the device, as needed, to bring it
1933 fully up. It must be safe to run on already-assembled devices.
1936 if dev_type not in DEV_MAP:
1937 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1938 device = DEV_MAP[dev_type](unique_id, children, size)
1943 def Create(dev_type, unique_id, children, size):
1947 if dev_type not in DEV_MAP:
1948 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1949 device = DEV_MAP[dev_type].Create(unique_id, children, size)