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 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
353 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
354 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
356 def __init__(self, unique_id, children, size):
357 """Attaches to a LV device.
359 The unique_id is a tuple (vg_name, lv_name)
362 super(LogicalVolume, self).__init__(unique_id, children, size)
363 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
364 raise ValueError("Invalid configuration data %s" % str(unique_id))
365 self._vg_name, self._lv_name = unique_id
366 self._ValidateName(self._vg_name)
367 self._ValidateName(self._lv_name)
368 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
369 self._degraded = True
370 self.major = self.minor = self.pe_size = self.stripe_count = None
374 def Create(cls, unique_id, children, size):
375 """Create a new logical volume.
378 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
379 raise errors.ProgrammerError("Invalid configuration data %s" %
381 vg_name, lv_name = unique_id
382 cls._ValidateName(vg_name)
383 cls._ValidateName(lv_name)
384 pvs_info = cls.GetPVInfo([vg_name])
386 _ThrowError("Can't compute PV info for vg %s", vg_name)
390 pvlist = [ pv[1] for pv in pvs_info ]
391 if utils.any(pvlist, lambda v: ":" in v):
392 _ThrowError("Some of your PVs have the invalid character ':' in their"
393 " name, this is not supported - please filter them out"
394 " in lvm.conf using either 'filter' or 'preferred_names'")
395 free_size = sum([ pv[0] for pv in pvs_info ])
396 current_pvs = len(pvlist)
397 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
399 # The size constraint should have been checked from the master before
400 # calling the create function.
402 _ThrowError("Not enough free space: required %s,"
403 " available %s", size, free_size)
404 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
405 # If the free space is not well distributed, we won't be able to
406 # create an optimally-striped volume; in that case, we want to try
407 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
409 for stripes_arg in range(stripes, 0, -1):
410 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
411 if not result.failed:
414 _ThrowError("LV create failed (%s): %s",
415 result.fail_reason, result.output)
416 return LogicalVolume(unique_id, children, size)
419 def GetPVInfo(vg_names, filter_allocatable=True):
420 """Get the free space info for PVs in a volume group.
422 @param vg_names: list of volume group names, if empty all will be returned
423 @param filter_allocatable: whether to skip over unallocatable PVs
426 @return: list of tuples (free_space, name) with free_space in mebibytes
430 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
431 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
432 "--separator=%s" % sep ]
433 result = utils.RunCmd(command)
435 logging.error("Can't get the PV information: %s - %s",
436 result.fail_reason, result.output)
439 for line in result.stdout.splitlines():
440 fields = line.strip().split(sep)
442 logging.error("Can't parse pvs output: line '%s'", line)
444 # (possibly) skip over pvs which are not allocatable
445 if filter_allocatable and fields[3][0] != 'a':
447 # (possibly) skip over pvs which are not in the right volume group(s)
448 if vg_names and fields[1] not in vg_names:
450 data.append((float(fields[2]), fields[0], fields[1]))
455 def _ValidateName(cls, name):
456 """Validates that a given name is valid as VG or LV name.
458 The list of valid characters and restricted names is taken out of
459 the lvm(8) manpage, with the simplification that we enforce both
460 VG and LV restrictions on the names.
463 if (not cls._VALID_NAME_RE.match(name) or
464 name in cls._INVALID_NAMES or
465 utils.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
466 _ThrowError("Invalid LVM name '%s'", name)
469 """Remove this logical volume.
472 if not self.minor and not self.Attach():
473 # the LV does not exist
475 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
476 (self._vg_name, self._lv_name)])
478 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
480 def Rename(self, new_id):
481 """Rename this logical volume.
484 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
485 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
486 new_vg, new_name = new_id
487 if new_vg != self._vg_name:
488 raise errors.ProgrammerError("Can't move a logical volume across"
489 " volume groups (from %s to to %s)" %
490 (self._vg_name, new_vg))
491 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
493 _ThrowError("Failed to rename the logical volume: %s", result.output)
494 self._lv_name = new_name
495 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
498 """Attach to an existing LV.
500 This method will try to see if an existing and active LV exists
501 which matches our name. If so, its major/minor will be
505 self.attached = False
506 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
507 "--units=m", "--nosuffix",
508 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
509 "vg_extent_size,stripes", self.dev_path])
511 logging.error("Can't find LV %s: %s, %s",
512 self.dev_path, result.fail_reason, result.output)
514 # the output can (and will) have multiple lines for multi-segment
515 # LVs, as the 'stripes' parameter is a segment one, so we take
516 # only the last entry, which is the one we're interested in; note
517 # that with LVM2 anyway the 'stripes' value must be constant
518 # across segments, so this is a no-op actually
519 out = result.stdout.splitlines()
520 if not out: # totally empty result? splitlines() returns at least
521 # one line for any non-empty string
522 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
524 out = out[-1].strip().rstrip(',')
527 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
530 status, major, minor, pe_size, stripes = out
532 logging.error("lvs lv_attr is not 6 characters (%s)", status)
538 except (TypeError, ValueError), err:
539 logging.error("lvs major/minor cannot be parsed: %s", str(err))
542 pe_size = int(float(pe_size))
543 except (TypeError, ValueError), err:
544 logging.error("Can't parse vg extent size: %s", err)
548 stripes = int(stripes)
549 except (TypeError, ValueError), err:
550 logging.error("Can't parse the number of stripes: %s", err)
555 self.pe_size = pe_size
556 self.stripe_count = stripes
557 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
563 """Assemble the device.
565 We always run `lvchange -ay` on the LV to ensure it's active before
566 use, as there were cases when xenvg was not active after boot
567 (also possibly after disk issues).
570 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
572 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
575 """Shutdown the device.
577 This is a no-op for the LV device type, as we don't deactivate the
583 def GetSyncStatus(self):
584 """Returns the sync status of the device.
586 If this device is a mirroring device, this function returns the
587 status of the mirror.
589 For logical volumes, sync_percent and estimated_time are always
590 None (no recovery in progress, as we don't handle the mirrored LV
591 case). The is_degraded parameter is the inverse of the ldisk
594 For the ldisk parameter, we check if the logical volume has the
595 'virtual' type, which means it's not backed by existing storage
596 anymore (read from it return I/O error). This happens after a
597 physical disk failure and subsequent 'vgreduce --removemissing' on
600 The status was already read in Attach, so we just return it.
602 @rtype: objects.BlockDevStatus
606 ldisk_status = constants.LDS_FAULTY
608 ldisk_status = constants.LDS_OKAY
610 return objects.BlockDevStatus(dev_path=self.dev_path,
615 is_degraded=self._degraded,
616 ldisk_status=ldisk_status)
618 def Open(self, force=False):
619 """Make the device ready for I/O.
621 This is a no-op for the LV device type.
627 """Notifies that the device will no longer be used for I/O.
629 This is a no-op for the LV device type.
634 def Snapshot(self, size):
635 """Create a snapshot copy of an lvm block device.
638 snap_name = self._lv_name + ".snap"
640 # remove existing snapshot if found
641 snap = LogicalVolume((self._vg_name, snap_name), None, size)
642 _IgnoreError(snap.Remove)
644 pvs_info = self.GetPVInfo([self._vg_name])
646 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
649 free_size, _, _ = pvs_info[0]
651 _ThrowError("Not enough free space: required %s,"
652 " available %s", size, free_size)
654 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
655 "-n%s" % snap_name, self.dev_path])
657 _ThrowError("command: %s error: %s - %s",
658 result.cmd, result.fail_reason, result.output)
662 def SetInfo(self, text):
663 """Update metadata with info text.
666 BlockDev.SetInfo(self, text)
668 # Replace invalid characters
669 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
670 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
672 # Only up to 128 characters are allowed
675 result = utils.RunCmd(["lvchange", "--addtag", text,
678 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
681 def Grow(self, amount):
682 """Grow the logical volume.
685 if self.pe_size is None or self.stripe_count is None:
686 if not self.Attach():
687 _ThrowError("Can't attach to LV during Grow()")
688 full_stripe_size = self.pe_size * self.stripe_count
689 rest = amount % full_stripe_size
691 amount += full_stripe_size - rest
692 # we try multiple algorithms since the 'best' ones might not have
693 # space available in the right place, but later ones might (since
694 # they have less constraints); also note that only recent LVM
696 for alloc_policy in "contiguous", "cling", "normal":
697 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
698 "-L", "+%dm" % amount, self.dev_path])
699 if not result.failed:
701 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
704 class DRBD8Status(object):
705 """A DRBD status representation class.
707 Note that this doesn't support unconfigured devices (cs:Unconfigured).
710 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
711 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
712 "\s+ds:([^/]+)/(\S+)\s+.*$")
713 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
714 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
716 CS_UNCONFIGURED = "Unconfigured"
717 CS_STANDALONE = "StandAlone"
718 CS_WFCONNECTION = "WFConnection"
719 CS_WFREPORTPARAMS = "WFReportParams"
720 CS_CONNECTED = "Connected"
721 CS_STARTINGSYNCS = "StartingSyncS"
722 CS_STARTINGSYNCT = "StartingSyncT"
723 CS_WFBITMAPS = "WFBitMapS"
724 CS_WFBITMAPT = "WFBitMapT"
725 CS_WFSYNCUUID = "WFSyncUUID"
726 CS_SYNCSOURCE = "SyncSource"
727 CS_SYNCTARGET = "SyncTarget"
728 CS_PAUSEDSYNCS = "PausedSyncS"
729 CS_PAUSEDSYNCT = "PausedSyncT"
730 CSET_SYNC = frozenset([
743 DS_DISKLESS = "Diskless"
744 DS_ATTACHING = "Attaching" # transient state
745 DS_FAILED = "Failed" # transient state, next: diskless
746 DS_NEGOTIATING = "Negotiating" # transient state
747 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
748 DS_OUTDATED = "Outdated"
749 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
750 DS_CONSISTENT = "Consistent"
751 DS_UPTODATE = "UpToDate" # normal state
753 RO_PRIMARY = "Primary"
754 RO_SECONDARY = "Secondary"
755 RO_UNKNOWN = "Unknown"
757 def __init__(self, procline):
758 u = self.UNCONF_RE.match(procline)
760 self.cstatus = self.CS_UNCONFIGURED
761 self.lrole = self.rrole = self.ldisk = self.rdisk = None
763 m = self.LINE_RE.match(procline)
765 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
766 self.cstatus = m.group(1)
767 self.lrole = m.group(2)
768 self.rrole = m.group(3)
769 self.ldisk = m.group(4)
770 self.rdisk = m.group(5)
772 # end reading of data from the LINE_RE or UNCONF_RE
774 self.is_standalone = self.cstatus == self.CS_STANDALONE
775 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
776 self.is_connected = self.cstatus == self.CS_CONNECTED
777 self.is_primary = self.lrole == self.RO_PRIMARY
778 self.is_secondary = self.lrole == self.RO_SECONDARY
779 self.peer_primary = self.rrole == self.RO_PRIMARY
780 self.peer_secondary = self.rrole == self.RO_SECONDARY
781 self.both_primary = self.is_primary and self.peer_primary
782 self.both_secondary = self.is_secondary and self.peer_secondary
784 self.is_diskless = self.ldisk == self.DS_DISKLESS
785 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
787 self.is_in_resync = self.cstatus in self.CSET_SYNC
788 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
790 m = self.SYNC_RE.match(procline)
792 self.sync_percent = float(m.group(1))
793 hours = int(m.group(2))
794 minutes = int(m.group(3))
795 seconds = int(m.group(4))
796 self.est_time = hours * 3600 + minutes * 60 + seconds
798 # we have (in this if branch) no percent information, but if
799 # we're resyncing we need to 'fake' a sync percent information,
800 # as this is how cmdlib determines if it makes sense to wait for
802 if self.is_in_resync:
803 self.sync_percent = 0
805 self.sync_percent = None
809 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
812 This class contains a few bits of common functionality between the
813 0.7 and 8.x versions of DRBD.
816 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
817 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
818 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
819 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
822 _ST_UNCONFIGURED = "Unconfigured"
823 _ST_WFCONNECTION = "WFConnection"
824 _ST_CONNECTED = "Connected"
826 _STATUS_FILE = "/proc/drbd"
829 def _GetProcData(filename=_STATUS_FILE):
830 """Return data from /proc/drbd.
834 data = utils.ReadFile(filename).splitlines()
835 except EnvironmentError, err:
836 if err.errno == errno.ENOENT:
837 _ThrowError("The file %s cannot be opened, check if the module"
838 " is loaded (%s)", filename, str(err))
840 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
842 _ThrowError("Can't read any data from %s", filename)
846 def _MassageProcData(cls, data):
847 """Transform the output of _GetProdData into a nicer form.
849 @return: a dictionary of minor: joined lines from /proc/drbd
854 old_minor = old_line = None
856 if not line: # completely empty lines, as can be returned by drbd8.0+
858 lresult = cls._VALID_LINE_RE.match(line)
859 if lresult is not None:
860 if old_minor is not None:
861 results[old_minor] = old_line
862 old_minor = int(lresult.group(1))
865 if old_minor is not None:
866 old_line += " " + line.strip()
868 if old_minor is not None:
869 results[old_minor] = old_line
873 def _GetVersion(cls):
874 """Return the DRBD version.
876 This will return a dict with keys:
882 - proto2 (only on drbd > 8.2.X)
885 proc_data = cls._GetProcData()
886 first_line = proc_data[0].strip()
887 version = cls._VERSION_RE.match(first_line)
889 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
892 values = version.groups()
893 retval = {'k_major': int(values[0]),
894 'k_minor': int(values[1]),
895 'k_point': int(values[2]),
896 'api': int(values[3]),
897 'proto': int(values[4]),
899 if values[5] is not None:
900 retval['proto2'] = values[5]
906 """Return the path to a drbd device for a given minor.
909 return "/dev/drbd%d" % minor
912 def GetUsedDevs(cls):
913 """Compute the list of used DRBD devices.
916 data = cls._GetProcData()
920 match = cls._VALID_LINE_RE.match(line)
923 minor = int(match.group(1))
924 state = match.group(2)
925 if state == cls._ST_UNCONFIGURED:
927 used_devs[minor] = state, line
931 def _SetFromMinor(self, minor):
932 """Set our parameters based on the given minor.
934 This sets our minor variable and our dev_path.
938 self.minor = self.dev_path = None
939 self.attached = False
942 self.dev_path = self._DevPath(minor)
946 def _CheckMetaSize(meta_device):
947 """Check if the given meta device looks like a valid one.
949 This currently only check the size, which must be around
953 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
955 _ThrowError("Failed to get device size: %s - %s",
956 result.fail_reason, result.output)
958 sectors = int(result.stdout)
959 except (TypeError, ValueError):
960 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
961 bytes = sectors * 512
962 if bytes < 128 * 1024 * 1024: # less than 128MiB
963 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
964 # the maximum *valid* size of the meta device when living on top
965 # of LVM is hard to compute: it depends on the number of stripes
966 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
967 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
968 # size meta device; as such, we restrict it to 1GB (a little bit
969 # too generous, but making assumptions about PE size is hard)
970 if bytes > 1024 * 1024 * 1024:
971 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
973 def Rename(self, new_id):
976 This is not supported for drbd devices.
979 raise errors.ProgrammerError("Can't rename a drbd device")
982 class DRBD8(BaseDRBD):
983 """DRBD v8.x block device.
985 This implements the local host part of the DRBD device, i.e. it
986 doesn't do anything to the supposed peer. If you need a fully
987 connected DRBD pair, you need to use this class on both hosts.
989 The unique_id for the drbd device is the (local_ip, local_port,
990 remote_ip, remote_port) tuple, and it must have two children: the
991 data device and the meta_device. The meta device is checked for
992 valid size and is zeroed on create.
999 _NET_RECONFIG_TIMEOUT = 60
1001 def __init__(self, unique_id, children, size):
1002 if children and children.count(None) > 0:
1004 if len(children) not in (0, 2):
1005 raise ValueError("Invalid configuration data %s" % str(children))
1006 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1007 raise ValueError("Invalid configuration data %s" % str(unique_id))
1008 (self._lhost, self._lport,
1009 self._rhost, self._rport,
1010 self._aminor, self._secret) = unique_id
1012 if not _CanReadDevice(children[1].dev_path):
1013 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1015 super(DRBD8, self).__init__(unique_id, children, size)
1016 self.major = self._DRBD_MAJOR
1017 version = self._GetVersion()
1018 if version['k_major'] != 8 :
1019 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1020 " usage: kernel is %s.%s, ganeti wants 8.x",
1021 version['k_major'], version['k_minor'])
1023 if (self._lhost is not None and self._lhost == self._rhost and
1024 self._lport == self._rport):
1025 raise ValueError("Invalid configuration data, same local/remote %s" %
1030 def _InitMeta(cls, minor, dev_path):
1031 """Initialize a meta device.
1033 This will not work if the given minor is in use.
1036 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1037 "v08", dev_path, "0", "create-md"])
1039 _ThrowError("Can't initialize meta device: %s", result.output)
1042 def _FindUnusedMinor(cls):
1043 """Find an unused DRBD device.
1045 This is specific to 8.x as the minors are allocated dynamically,
1046 so non-existing numbers up to a max minor count are actually free.
1049 data = cls._GetProcData()
1053 match = cls._UNUSED_LINE_RE.match(line)
1055 return int(match.group(1))
1056 match = cls._VALID_LINE_RE.match(line)
1058 minor = int(match.group(1))
1059 highest = max(highest, minor)
1060 if highest is None: # there are no minors in use at all
1062 if highest >= cls._MAX_MINORS:
1063 logging.error("Error: no free drbd minors!")
1064 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1068 def _GetShowParser(cls):
1069 """Return a parser for `drbd show` output.
1071 This will either create or return an already-create parser for the
1072 output of the command `drbd show`.
1075 if cls._PARSE_SHOW is not None:
1076 return cls._PARSE_SHOW
1079 lbrace = pyp.Literal("{").suppress()
1080 rbrace = pyp.Literal("}").suppress()
1081 semi = pyp.Literal(";").suppress()
1082 # this also converts the value to an int
1083 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1085 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1086 defa = pyp.Literal("_is_default").suppress()
1087 dbl_quote = pyp.Literal('"').suppress()
1089 keyword = pyp.Word(pyp.alphanums + '-')
1092 value = pyp.Word(pyp.alphanums + '_-/.:')
1093 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1094 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1095 pyp.Optional(pyp.Literal("ipv6")).suppress())
1096 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1097 pyp.Literal(':').suppress() + number)
1098 # meta device, extended syntax
1099 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1100 number + pyp.Word(']').suppress())
1101 # device name, extended syntax
1102 device_value = pyp.Literal("minor").suppress() + number
1105 stmt = (~rbrace + keyword + ~lbrace +
1106 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1108 pyp.Optional(defa) + semi +
1109 pyp.Optional(pyp.restOfLine).suppress())
1112 section_name = pyp.Word(pyp.alphas + '_')
1113 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1115 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1118 cls._PARSE_SHOW = bnf
1123 def _GetShowData(cls, minor):
1124 """Return the `drbdsetup show` data for a minor.
1127 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1129 logging.error("Can't display the drbd config: %s - %s",
1130 result.fail_reason, result.output)
1132 return result.stdout
1135 def _GetDevInfo(cls, out):
1136 """Parse details about a given DRBD minor.
1138 This return, if available, the local backing device (as a path)
1139 and the local and remote (ip, port) information from a string
1140 containing the output of the `drbdsetup show` command as returned
1148 bnf = cls._GetShowParser()
1152 results = bnf.parseString(out)
1153 except pyp.ParseException, err:
1154 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1156 # and massage the results into our desired format
1157 for section in results:
1159 if sname == "_this_host":
1160 for lst in section[1:]:
1161 if lst[0] == "disk":
1162 data["local_dev"] = lst[1]
1163 elif lst[0] == "meta-disk":
1164 data["meta_dev"] = lst[1]
1165 data["meta_index"] = lst[2]
1166 elif lst[0] == "address":
1167 data["local_addr"] = tuple(lst[1:])
1168 elif sname == "_remote_host":
1169 for lst in section[1:]:
1170 if lst[0] == "address":
1171 data["remote_addr"] = tuple(lst[1:])
1174 def _MatchesLocal(self, info):
1175 """Test if our local config matches with an existing device.
1177 The parameter should be as returned from `_GetDevInfo()`. This
1178 method tests if our local backing device is the same as the one in
1179 the info parameter, in effect testing if we look like the given
1184 backend, meta = self._children
1186 backend = meta = None
1188 if backend is not None:
1189 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1191 retval = ("local_dev" not in info)
1193 if meta is not None:
1194 retval = retval and ("meta_dev" in info and
1195 info["meta_dev"] == meta.dev_path)
1196 retval = retval and ("meta_index" in info and
1197 info["meta_index"] == 0)
1199 retval = retval and ("meta_dev" not in info and
1200 "meta_index" not in info)
1203 def _MatchesNet(self, info):
1204 """Test if our network config matches with an existing device.
1206 The parameter should be as returned from `_GetDevInfo()`. This
1207 method tests if our network configuration is the same as the one
1208 in the info parameter, in effect testing if we look like the given
1212 if (((self._lhost is None and not ("local_addr" in info)) and
1213 (self._rhost is None and not ("remote_addr" in info)))):
1216 if self._lhost is None:
1219 if not ("local_addr" in info and
1220 "remote_addr" in info):
1223 retval = (info["local_addr"] == (self._lhost, self._lport))
1224 retval = (retval and
1225 info["remote_addr"] == (self._rhost, self._rport))
1229 def _AssembleLocal(cls, minor, backend, meta, size):
1230 """Configure the local part of a DRBD device.
1233 args = ["drbdsetup", cls._DevPath(minor), "disk",
1238 args.extend(["-d", "%sm" % size])
1239 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1240 version = cls._GetVersion()
1241 # various DRBD versions support different disk barrier options;
1242 # what we aim here is to revert back to the 'drain' method of
1243 # disk flushes and to disable metadata barriers, in effect going
1244 # back to pre-8.0.7 behaviour
1245 vmaj = version['k_major']
1246 vmin = version['k_minor']
1247 vrel = version['k_point']
1249 if vmin == 0: # 8.0.x
1251 args.extend(['-i', '-m'])
1252 elif vmin == 2: # 8.2.x
1254 args.extend(['-i', '-m'])
1255 elif vmaj >= 3: # 8.3.x or newer
1256 args.extend(['-i', '-a', 'm'])
1257 result = utils.RunCmd(args)
1259 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1262 def _AssembleNet(cls, minor, net_info, protocol,
1263 dual_pri=False, hmac=None, secret=None):
1264 """Configure the network part of the device.
1267 lhost, lport, rhost, rport = net_info
1268 if None in net_info:
1269 # we don't want network connection and actually want to make
1271 cls._ShutdownNet(minor)
1274 # Workaround for a race condition. When DRBD is doing its dance to
1275 # establish a connection with its peer, it also sends the
1276 # synchronization speed over the wire. In some cases setting the
1277 # sync speed only after setting up both sides can race with DRBD
1278 # connecting, hence we set it here before telling DRBD anything
1280 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1282 args = ["drbdsetup", cls._DevPath(minor), "net",
1283 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1284 "-A", "discard-zero-changes",
1291 args.extend(["-a", hmac, "-x", secret])
1292 result = utils.RunCmd(args)
1294 _ThrowError("drbd%d: can't setup network: %s - %s",
1295 minor, result.fail_reason, result.output)
1297 def _CheckNetworkConfig():
1298 info = cls._GetDevInfo(cls._GetShowData(minor))
1299 if not "local_addr" in info or not "remote_addr" in info:
1300 raise utils.RetryAgain()
1302 if (info["local_addr"] != (lhost, lport) or
1303 info["remote_addr"] != (rhost, rport)):
1304 raise utils.RetryAgain()
1307 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1308 except utils.RetryTimeout:
1309 _ThrowError("drbd%d: timeout while configuring network", minor)
1311 def AddChildren(self, devices):
1312 """Add a disk to the DRBD device.
1315 if self.minor is None:
1316 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1318 if len(devices) != 2:
1319 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1320 info = self._GetDevInfo(self._GetShowData(self.minor))
1321 if "local_dev" in info:
1322 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1323 backend, meta = devices
1324 if backend.dev_path is None or meta.dev_path is None:
1325 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1328 self._CheckMetaSize(meta.dev_path)
1329 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1331 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1332 self._children = devices
1334 def RemoveChildren(self, devices):
1335 """Detach the drbd device from local storage.
1338 if self.minor is None:
1339 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1341 # early return if we don't actually have backing storage
1342 info = self._GetDevInfo(self._GetShowData(self.minor))
1343 if "local_dev" not in info:
1345 if len(self._children) != 2:
1346 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1348 if self._children.count(None) == 2: # we don't actually have children :)
1349 logging.warning("drbd%d: requested detach while detached", self.minor)
1351 if len(devices) != 2:
1352 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1353 for child, dev in zip(self._children, devices):
1354 if dev != child.dev_path:
1355 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1356 " RemoveChildren", self.minor, dev, child.dev_path)
1358 self._ShutdownLocal(self.minor)
1362 def _SetMinorSyncSpeed(cls, minor, kbytes):
1363 """Set the speed of the DRBD syncer.
1365 This is the low-level implementation.
1368 @param minor: the drbd minor whose settings we change
1370 @param kbytes: the speed in kbytes/second
1372 @return: the success of the operation
1375 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1376 "-r", "%d" % kbytes, "--create-device"])
1378 logging.error("Can't change syncer rate: %s - %s",
1379 result.fail_reason, result.output)
1380 return not result.failed
1382 def SetSyncSpeed(self, kbytes):
1383 """Set the speed of the DRBD syncer.
1386 @param kbytes: the speed in kbytes/second
1388 @return: the success of the operation
1391 if self.minor is None:
1392 logging.info("Not attached during SetSyncSpeed")
1394 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1395 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1397 def GetProcStatus(self):
1398 """Return device data from /proc.
1401 if self.minor is None:
1402 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1403 proc_info = self._MassageProcData(self._GetProcData())
1404 if self.minor not in proc_info:
1405 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1406 return DRBD8Status(proc_info[self.minor])
1408 def GetSyncStatus(self):
1409 """Returns the sync status of the device.
1412 If sync_percent is None, it means all is ok
1413 If estimated_time is None, it means we can't estimate
1414 the time needed, otherwise it's the time left in seconds.
1417 We set the is_degraded parameter to True on two conditions:
1418 network not connected or local disk missing.
1420 We compute the ldisk parameter based on whether we have a local
1423 @rtype: objects.BlockDevStatus
1426 if self.minor is None and not self.Attach():
1427 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1429 stats = self.GetProcStatus()
1430 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1432 if stats.is_disk_uptodate:
1433 ldisk_status = constants.LDS_OKAY
1434 elif stats.is_diskless:
1435 ldisk_status = constants.LDS_FAULTY
1437 ldisk_status = constants.LDS_UNKNOWN
1439 return objects.BlockDevStatus(dev_path=self.dev_path,
1442 sync_percent=stats.sync_percent,
1443 estimated_time=stats.est_time,
1444 is_degraded=is_degraded,
1445 ldisk_status=ldisk_status)
1447 def Open(self, force=False):
1448 """Make the local state primary.
1450 If the 'force' parameter is given, the '-o' option is passed to
1451 drbdsetup. Since this is a potentially dangerous operation, the
1452 force flag should be only given after creation, when it actually
1456 if self.minor is None and not self.Attach():
1457 logging.error("DRBD cannot attach to a device during open")
1459 cmd = ["drbdsetup", self.dev_path, "primary"]
1462 result = utils.RunCmd(cmd)
1464 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1468 """Make the local state secondary.
1470 This will, of course, fail if the device is in use.
1473 if self.minor is None and not self.Attach():
1474 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1475 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1477 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1478 self.minor, result.output)
1480 def DisconnectNet(self):
1481 """Removes network configuration.
1483 This method shutdowns the network side of the device.
1485 The method will wait up to a hardcoded timeout for the device to
1486 go into standalone after the 'disconnect' command before
1487 re-configuring it, as sometimes it takes a while for the
1488 disconnect to actually propagate and thus we might issue a 'net'
1489 command while the device is still connected. If the device will
1490 still be attached to the network and we time out, we raise an
1494 if self.minor is None:
1495 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1497 if None in (self._lhost, self._lport, self._rhost, self._rport):
1498 _ThrowError("drbd%d: DRBD disk missing network info in"
1499 " DisconnectNet()", self.minor)
1501 class _DisconnectStatus:
1502 def __init__(self, ever_disconnected):
1503 self.ever_disconnected = ever_disconnected
1505 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1507 def _WaitForDisconnect():
1508 if self.GetProcStatus().is_standalone:
1511 # retry the disconnect, it seems possible that due to a well-time
1512 # disconnect on the peer, my disconnect command might be ignored and
1514 dstatus.ever_disconnected = \
1515 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1517 raise utils.RetryAgain()
1520 start_time = time.time()
1523 # Start delay at 100 milliseconds and grow up to 2 seconds
1524 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1525 self._NET_RECONFIG_TIMEOUT)
1526 except utils.RetryTimeout:
1527 if dstatus.ever_disconnected:
1528 msg = ("drbd%d: device did not react to the"
1529 " 'disconnect' command in a timely manner")
1531 msg = "drbd%d: can't shutdown network, even after multiple retries"
1533 _ThrowError(msg, self.minor)
1535 reconfig_time = time.time() - start_time
1536 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1537 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1538 self.minor, reconfig_time)
1540 def AttachNet(self, multimaster):
1541 """Reconnects the network.
1543 This method connects the network side of the device with a
1544 specified multi-master flag. The device needs to be 'Standalone'
1545 but have valid network configuration data.
1548 - multimaster: init the network in dual-primary mode
1551 if self.minor is None:
1552 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1554 if None in (self._lhost, self._lport, self._rhost, self._rport):
1555 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1557 status = self.GetProcStatus()
1559 if not status.is_standalone:
1560 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1562 self._AssembleNet(self.minor,
1563 (self._lhost, self._lport, self._rhost, self._rport),
1564 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1565 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1568 """Check if our minor is configured.
1570 This doesn't do any device configurations - it only checks if the
1571 minor is in a state different from Unconfigured.
1573 Note that this function will not change the state of the system in
1574 any way (except in case of side-effects caused by reading from
1578 used_devs = self.GetUsedDevs()
1579 if self._aminor in used_devs:
1580 minor = self._aminor
1584 self._SetFromMinor(minor)
1585 return minor is not None
1588 """Assemble the drbd.
1591 - if we have a configured device, we try to ensure that it matches
1593 - if not, we create it from zero
1596 super(DRBD8, self).Assemble()
1599 if self.minor is None:
1600 # local device completely unconfigured
1601 self._FastAssemble()
1603 # we have to recheck the local and network status and try to fix
1605 self._SlowAssemble()
1607 def _SlowAssemble(self):
1608 """Assembles the DRBD device from a (partially) configured device.
1610 In case of partially attached (local device matches but no network
1611 setup), we perform the network attach. If successful, we re-test
1612 the attach if can return success.
1615 # TODO: Rewrite to not use a for loop just because there is 'break'
1616 # pylint: disable-msg=W0631
1617 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1618 for minor in (self._aminor,):
1619 info = self._GetDevInfo(self._GetShowData(minor))
1620 match_l = self._MatchesLocal(info)
1621 match_r = self._MatchesNet(info)
1623 if match_l and match_r:
1624 # everything matches
1627 if match_l and not match_r and "local_addr" not in info:
1628 # disk matches, but not attached to network, attach and recheck
1629 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1630 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1631 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1634 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1635 " show' disagrees", minor)
1637 if match_r and "local_dev" not in info:
1638 # no local disk, but network attached and it matches
1639 self._AssembleLocal(minor, self._children[0].dev_path,
1640 self._children[1].dev_path, self.size)
1641 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1644 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1645 " show' disagrees", minor)
1647 # this case must be considered only if we actually have local
1648 # storage, i.e. not in diskless mode, because all diskless
1649 # devices are equal from the point of view of local
1651 if (match_l and "local_dev" in info and
1652 not match_r and "local_addr" in info):
1653 # strange case - the device network part points to somewhere
1654 # else, even though its local storage is ours; as we own the
1655 # drbd space, we try to disconnect from the remote peer and
1656 # reconnect to our correct one
1658 self._ShutdownNet(minor)
1659 except errors.BlockDeviceError, err:
1660 _ThrowError("drbd%d: device has correct local storage, wrong"
1661 " remote peer and is unable to disconnect in order"
1662 " to attach to the correct peer: %s", minor, str(err))
1663 # note: _AssembleNet also handles the case when we don't want
1664 # local storage (i.e. one or more of the _[lr](host|port) is
1666 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1667 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1668 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1671 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1672 " show' disagrees", minor)
1677 self._SetFromMinor(minor)
1679 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1682 def _FastAssemble(self):
1683 """Assemble the drbd device from zero.
1685 This is run when in Assemble we detect our minor is unused.
1688 minor = self._aminor
1689 if self._children and self._children[0] and self._children[1]:
1690 self._AssembleLocal(minor, self._children[0].dev_path,
1691 self._children[1].dev_path, self.size)
1692 if self._lhost and self._lport and self._rhost and self._rport:
1693 self._AssembleNet(minor,
1694 (self._lhost, self._lport, self._rhost, self._rport),
1695 constants.DRBD_NET_PROTOCOL,
1696 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1697 self._SetFromMinor(minor)
1700 def _ShutdownLocal(cls, minor):
1701 """Detach from the local device.
1703 I/Os will continue to be served from the remote device. If we
1704 don't have a remote device, this operation will fail.
1707 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1709 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1712 def _ShutdownNet(cls, minor):
1713 """Disconnect from the remote peer.
1715 This fails if we don't have a local device.
1718 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1720 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1723 def _ShutdownAll(cls, minor):
1724 """Deactivate the device.
1726 This will, of course, fail if the device is in use.
1729 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1731 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1732 minor, result.output)
1735 """Shutdown the DRBD device.
1738 if self.minor is None and not self.Attach():
1739 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1743 self.dev_path = None
1744 self._ShutdownAll(minor)
1747 """Stub remove for DRBD devices.
1753 def Create(cls, unique_id, children, size):
1754 """Create a new DRBD8 device.
1756 Since DRBD devices are not created per se, just assembled, this
1757 function only initializes the metadata.
1760 if len(children) != 2:
1761 raise errors.ProgrammerError("Invalid setup for the drbd device")
1762 # check that the minor is unused
1763 aminor = unique_id[4]
1764 proc_info = cls._MassageProcData(cls._GetProcData())
1765 if aminor in proc_info:
1766 status = DRBD8Status(proc_info[aminor])
1767 in_use = status.is_in_use
1771 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1774 if not meta.Attach():
1775 _ThrowError("drbd%d: can't attach to meta device '%s'",
1777 cls._CheckMetaSize(meta.dev_path)
1778 cls._InitMeta(aminor, meta.dev_path)
1779 return cls(unique_id, children, size)
1781 def Grow(self, amount):
1782 """Resize the DRBD device and its backing storage.
1785 if self.minor is None:
1786 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1787 if len(self._children) != 2 or None in self._children:
1788 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1789 self._children[0].Grow(amount)
1790 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1791 "%dm" % (self.size + amount)])
1793 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1796 class FileStorage(BlockDev):
1799 This class represents the a file storage backend device.
1801 The unique_id for the file device is a (file_driver, file_path) tuple.
1804 def __init__(self, unique_id, children, size):
1805 """Initalizes a file device backend.
1809 raise errors.BlockDeviceError("Invalid setup for file device")
1810 super(FileStorage, self).__init__(unique_id, children, size)
1811 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1812 raise ValueError("Invalid configuration data %s" % str(unique_id))
1813 self.driver = unique_id[0]
1814 self.dev_path = unique_id[1]
1818 """Assemble the device.
1820 Checks whether the file device exists, raises BlockDeviceError otherwise.
1823 if not os.path.exists(self.dev_path):
1824 _ThrowError("File device '%s' does not exist" % self.dev_path)
1827 """Shutdown the device.
1829 This is a no-op for the file type, as we don't deactivate
1830 the file on shutdown.
1835 def Open(self, force=False):
1836 """Make the device ready for I/O.
1838 This is a no-op for the file type.
1844 """Notifies that the device will no longer be used for I/O.
1846 This is a no-op for the file type.
1852 """Remove the file backing the block device.
1855 @return: True if the removal was successful
1859 os.remove(self.dev_path)
1860 except OSError, err:
1861 if err.errno != errno.ENOENT:
1862 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1864 def Rename(self, new_id):
1865 """Renames the file.
1868 # TODO: implement rename for file-based storage
1869 _ThrowError("Rename is not supported for file-based storage")
1871 def Grow(self, amount):
1874 @param amount: the amount (in mebibytes) to grow with
1877 # TODO: implement grow for file-based storage
1878 _ThrowError("Grow not supported for file-based storage")
1881 """Attach to an existing file.
1883 Check if this file already exists.
1886 @return: True if file exists
1889 self.attached = os.path.exists(self.dev_path)
1890 return self.attached
1892 def GetActualSize(self):
1893 """Return the actual disk size.
1895 @note: the device needs to be active when this is called
1898 assert self.attached, "BlockDevice not attached in GetActualSize()"
1900 st = os.stat(self.dev_path)
1902 except OSError, err:
1903 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1906 def Create(cls, unique_id, children, size):
1907 """Create a new file.
1909 @param size: the size of file in MiB
1911 @rtype: L{bdev.FileStorage}
1912 @return: an instance of FileStorage
1915 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1916 raise ValueError("Invalid configuration data %s" % str(unique_id))
1917 dev_path = unique_id[1]
1918 if os.path.exists(dev_path):
1919 _ThrowError("File already existing: %s", dev_path)
1921 f = open(dev_path, 'w')
1922 f.truncate(size * 1024 * 1024)
1924 except IOError, err:
1925 _ThrowError("Error in file creation: %", str(err))
1927 return FileStorage(unique_id, children, size)
1931 constants.LD_LV: LogicalVolume,
1932 constants.LD_DRBD8: DRBD8,
1935 if constants.ENABLE_FILE_STORAGE:
1936 DEV_MAP[constants.LD_FILE] = FileStorage
1939 def FindDevice(dev_type, unique_id, children, size):
1940 """Search for an existing, assembled device.
1942 This will succeed only if the device exists and is assembled, but it
1943 does not do any actions in order to activate the device.
1946 if dev_type not in DEV_MAP:
1947 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1948 device = DEV_MAP[dev_type](unique_id, children, size)
1949 if not device.attached:
1954 def Assemble(dev_type, unique_id, children, size):
1955 """Try to attach or assemble an existing device.
1957 This will attach to assemble the device, as needed, to bring it
1958 fully up. It must be safe to run on already-assembled devices.
1961 if dev_type not in DEV_MAP:
1962 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1963 device = DEV_MAP[dev_type](unique_id, children, size)
1968 def Create(dev_type, unique_id, children, size):
1972 if dev_type not in DEV_MAP:
1973 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1974 device = DEV_MAP[dev_type].Create(unique_id, children, size)