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 invalid character ':'"
394 free_size = sum([ pv[0] for pv in pvs_info ])
395 current_pvs = len(pvlist)
396 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
398 # The size constraint should have been checked from the master before
399 # calling the create function.
401 _ThrowError("Not enough free space: required %s,"
402 " available %s", size, free_size)
403 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
404 # If the free space is not well distributed, we won't be able to
405 # create an optimally-striped volume; in that case, we want to try
406 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
408 for stripes_arg in range(stripes, 0, -1):
409 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
410 if not result.failed:
413 _ThrowError("LV create failed (%s): %s",
414 result.fail_reason, result.output)
415 return LogicalVolume(unique_id, children, size)
418 def GetPVInfo(vg_names, filter_allocatable=True):
419 """Get the free space info for PVs in a volume group.
421 @param vg_names: list of volume group names, if empty all will be returned
422 @param filter_allocatable: whether to skip over unallocatable PVs
425 @return: list of tuples (free_space, name) with free_space in mebibytes
429 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
430 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
431 "--separator=%s" % sep ]
432 result = utils.RunCmd(command)
434 logging.error("Can't get the PV information: %s - %s",
435 result.fail_reason, result.output)
438 for line in result.stdout.splitlines():
439 fields = line.strip().split(sep)
441 logging.error("Can't parse pvs output: line '%s'", line)
443 # (possibly) skip over pvs which are not allocatable
444 if filter_allocatable and fields[3][0] != 'a':
446 # (possibly) skip over pvs which are not in the right volume group(s)
447 if vg_names and fields[1] not in vg_names:
449 data.append((float(fields[2]), fields[0], fields[1]))
454 def _ValidateName(cls, name):
455 """Validates that a given name is valid as VG or LV name.
457 The list of valid characters and restricted names is taken out of
458 the lvm(8) manpage, with the simplification that we enforce both
459 VG and LV restrictions on the names.
462 if (not cls._VALID_NAME_RE.match(name) or
463 name in cls._INVALID_NAMES or
464 utils.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
465 _ThrowError("Invalid LVM name '%s'", name)
468 """Remove this logical volume.
471 if not self.minor and not self.Attach():
472 # the LV does not exist
474 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
475 (self._vg_name, self._lv_name)])
477 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
479 def Rename(self, new_id):
480 """Rename this logical volume.
483 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
484 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
485 new_vg, new_name = new_id
486 if new_vg != self._vg_name:
487 raise errors.ProgrammerError("Can't move a logical volume across"
488 " volume groups (from %s to to %s)" %
489 (self._vg_name, new_vg))
490 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
492 _ThrowError("Failed to rename the logical volume: %s", result.output)
493 self._lv_name = new_name
494 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
497 """Attach to an existing LV.
499 This method will try to see if an existing and active LV exists
500 which matches our name. If so, its major/minor will be
504 self.attached = False
505 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
506 "--units=m", "--nosuffix",
507 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
508 "vg_extent_size,stripes", self.dev_path])
510 logging.error("Can't find LV %s: %s, %s",
511 self.dev_path, result.fail_reason, result.output)
513 # the output can (and will) have multiple lines for multi-segment
514 # LVs, as the 'stripes' parameter is a segment one, so we take
515 # only the last entry, which is the one we're interested in; note
516 # that with LVM2 anyway the 'stripes' value must be constant
517 # across segments, so this is a no-op actually
518 out = result.stdout.splitlines()
519 if not out: # totally empty result? splitlines() returns at least
520 # one line for any non-empty string
521 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
523 out = out[-1].strip().rstrip(',')
526 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
529 status, major, minor, pe_size, stripes = out
531 logging.error("lvs lv_attr is not 6 characters (%s)", status)
537 except (TypeError, ValueError), err:
538 logging.error("lvs major/minor cannot be parsed: %s", str(err))
541 pe_size = int(float(pe_size))
542 except (TypeError, ValueError), err:
543 logging.error("Can't parse vg extent size: %s", err)
547 stripes = int(stripes)
548 except (TypeError, ValueError), err:
549 logging.error("Can't parse the number of stripes: %s", err)
554 self.pe_size = pe_size
555 self.stripe_count = stripes
556 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
562 """Assemble the device.
564 We always run `lvchange -ay` on the LV to ensure it's active before
565 use, as there were cases when xenvg was not active after boot
566 (also possibly after disk issues).
569 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
571 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
574 """Shutdown the device.
576 This is a no-op for the LV device type, as we don't deactivate the
582 def GetSyncStatus(self):
583 """Returns the sync status of the device.
585 If this device is a mirroring device, this function returns the
586 status of the mirror.
588 For logical volumes, sync_percent and estimated_time are always
589 None (no recovery in progress, as we don't handle the mirrored LV
590 case). The is_degraded parameter is the inverse of the ldisk
593 For the ldisk parameter, we check if the logical volume has the
594 'virtual' type, which means it's not backed by existing storage
595 anymore (read from it return I/O error). This happens after a
596 physical disk failure and subsequent 'vgreduce --removemissing' on
599 The status was already read in Attach, so we just return it.
601 @rtype: objects.BlockDevStatus
605 ldisk_status = constants.LDS_FAULTY
607 ldisk_status = constants.LDS_OKAY
609 return objects.BlockDevStatus(dev_path=self.dev_path,
614 is_degraded=self._degraded,
615 ldisk_status=ldisk_status)
617 def Open(self, force=False):
618 """Make the device ready for I/O.
620 This is a no-op for the LV device type.
626 """Notifies that the device will no longer be used for I/O.
628 This is a no-op for the LV device type.
633 def Snapshot(self, size):
634 """Create a snapshot copy of an lvm block device.
637 snap_name = self._lv_name + ".snap"
639 # remove existing snapshot if found
640 snap = LogicalVolume((self._vg_name, snap_name), None, size)
641 _IgnoreError(snap.Remove)
643 pvs_info = self.GetPVInfo([self._vg_name])
645 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
648 free_size, _, _ = pvs_info[0]
650 _ThrowError("Not enough free space: required %s,"
651 " available %s", size, free_size)
653 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
654 "-n%s" % snap_name, self.dev_path])
656 _ThrowError("command: %s error: %s - %s",
657 result.cmd, result.fail_reason, result.output)
661 def SetInfo(self, text):
662 """Update metadata with info text.
665 BlockDev.SetInfo(self, text)
667 # Replace invalid characters
668 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
669 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
671 # Only up to 128 characters are allowed
674 result = utils.RunCmd(["lvchange", "--addtag", text,
677 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
680 def Grow(self, amount):
681 """Grow the logical volume.
684 if self.pe_size is None or self.stripe_count is None:
685 if not self.Attach():
686 _ThrowError("Can't attach to LV during Grow()")
687 full_stripe_size = self.pe_size * self.stripe_count
688 rest = amount % full_stripe_size
690 amount += full_stripe_size - rest
691 # we try multiple algorithms since the 'best' ones might not have
692 # space available in the right place, but later ones might (since
693 # they have less constraints); also note that only recent LVM
695 for alloc_policy in "contiguous", "cling", "normal":
696 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
697 "-L", "+%dm" % amount, self.dev_path])
698 if not result.failed:
700 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
703 class DRBD8Status(object):
704 """A DRBD status representation class.
706 Note that this doesn't support unconfigured devices (cs:Unconfigured).
709 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
710 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
711 "\s+ds:([^/]+)/(\S+)\s+.*$")
712 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
713 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
715 CS_UNCONFIGURED = "Unconfigured"
716 CS_STANDALONE = "StandAlone"
717 CS_WFCONNECTION = "WFConnection"
718 CS_WFREPORTPARAMS = "WFReportParams"
719 CS_CONNECTED = "Connected"
720 CS_STARTINGSYNCS = "StartingSyncS"
721 CS_STARTINGSYNCT = "StartingSyncT"
722 CS_WFBITMAPS = "WFBitMapS"
723 CS_WFBITMAPT = "WFBitMapT"
724 CS_WFSYNCUUID = "WFSyncUUID"
725 CS_SYNCSOURCE = "SyncSource"
726 CS_SYNCTARGET = "SyncTarget"
727 CS_PAUSEDSYNCS = "PausedSyncS"
728 CS_PAUSEDSYNCT = "PausedSyncT"
729 CSET_SYNC = frozenset([
742 DS_DISKLESS = "Diskless"
743 DS_ATTACHING = "Attaching" # transient state
744 DS_FAILED = "Failed" # transient state, next: diskless
745 DS_NEGOTIATING = "Negotiating" # transient state
746 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
747 DS_OUTDATED = "Outdated"
748 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
749 DS_CONSISTENT = "Consistent"
750 DS_UPTODATE = "UpToDate" # normal state
752 RO_PRIMARY = "Primary"
753 RO_SECONDARY = "Secondary"
754 RO_UNKNOWN = "Unknown"
756 def __init__(self, procline):
757 u = self.UNCONF_RE.match(procline)
759 self.cstatus = self.CS_UNCONFIGURED
760 self.lrole = self.rrole = self.ldisk = self.rdisk = None
762 m = self.LINE_RE.match(procline)
764 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
765 self.cstatus = m.group(1)
766 self.lrole = m.group(2)
767 self.rrole = m.group(3)
768 self.ldisk = m.group(4)
769 self.rdisk = m.group(5)
771 # end reading of data from the LINE_RE or UNCONF_RE
773 self.is_standalone = self.cstatus == self.CS_STANDALONE
774 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
775 self.is_connected = self.cstatus == self.CS_CONNECTED
776 self.is_primary = self.lrole == self.RO_PRIMARY
777 self.is_secondary = self.lrole == self.RO_SECONDARY
778 self.peer_primary = self.rrole == self.RO_PRIMARY
779 self.peer_secondary = self.rrole == self.RO_SECONDARY
780 self.both_primary = self.is_primary and self.peer_primary
781 self.both_secondary = self.is_secondary and self.peer_secondary
783 self.is_diskless = self.ldisk == self.DS_DISKLESS
784 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
786 self.is_in_resync = self.cstatus in self.CSET_SYNC
787 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
789 m = self.SYNC_RE.match(procline)
791 self.sync_percent = float(m.group(1))
792 hours = int(m.group(2))
793 minutes = int(m.group(3))
794 seconds = int(m.group(4))
795 self.est_time = hours * 3600 + minutes * 60 + seconds
797 # we have (in this if branch) no percent information, but if
798 # we're resyncing we need to 'fake' a sync percent information,
799 # as this is how cmdlib determines if it makes sense to wait for
801 if self.is_in_resync:
802 self.sync_percent = 0
804 self.sync_percent = None
808 class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
811 This class contains a few bits of common functionality between the
812 0.7 and 8.x versions of DRBD.
815 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
816 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
817 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
818 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
821 _ST_UNCONFIGURED = "Unconfigured"
822 _ST_WFCONNECTION = "WFConnection"
823 _ST_CONNECTED = "Connected"
825 _STATUS_FILE = "/proc/drbd"
828 def _GetProcData(filename=_STATUS_FILE):
829 """Return data from /proc/drbd.
833 data = utils.ReadFile(filename).splitlines()
834 except EnvironmentError, err:
835 if err.errno == errno.ENOENT:
836 _ThrowError("The file %s cannot be opened, check if the module"
837 " is loaded (%s)", filename, str(err))
839 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
841 _ThrowError("Can't read any data from %s", filename)
845 def _MassageProcData(cls, data):
846 """Transform the output of _GetProdData into a nicer form.
848 @return: a dictionary of minor: joined lines from /proc/drbd
853 old_minor = old_line = None
855 if not line: # completely empty lines, as can be returned by drbd8.0+
857 lresult = cls._VALID_LINE_RE.match(line)
858 if lresult is not None:
859 if old_minor is not None:
860 results[old_minor] = old_line
861 old_minor = int(lresult.group(1))
864 if old_minor is not None:
865 old_line += " " + line.strip()
867 if old_minor is not None:
868 results[old_minor] = old_line
872 def _GetVersion(cls):
873 """Return the DRBD version.
875 This will return a dict with keys:
881 - proto2 (only on drbd > 8.2.X)
884 proc_data = cls._GetProcData()
885 first_line = proc_data[0].strip()
886 version = cls._VERSION_RE.match(first_line)
888 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
891 values = version.groups()
892 retval = {'k_major': int(values[0]),
893 'k_minor': int(values[1]),
894 'k_point': int(values[2]),
895 'api': int(values[3]),
896 'proto': int(values[4]),
898 if values[5] is not None:
899 retval['proto2'] = values[5]
905 """Return the path to a drbd device for a given minor.
908 return "/dev/drbd%d" % minor
911 def GetUsedDevs(cls):
912 """Compute the list of used DRBD devices.
915 data = cls._GetProcData()
919 match = cls._VALID_LINE_RE.match(line)
922 minor = int(match.group(1))
923 state = match.group(2)
924 if state == cls._ST_UNCONFIGURED:
926 used_devs[minor] = state, line
930 def _SetFromMinor(self, minor):
931 """Set our parameters based on the given minor.
933 This sets our minor variable and our dev_path.
937 self.minor = self.dev_path = None
938 self.attached = False
941 self.dev_path = self._DevPath(minor)
945 def _CheckMetaSize(meta_device):
946 """Check if the given meta device looks like a valid one.
948 This currently only check the size, which must be around
952 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
954 _ThrowError("Failed to get device size: %s - %s",
955 result.fail_reason, result.output)
957 sectors = int(result.stdout)
958 except (TypeError, ValueError):
959 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
960 bytes = sectors * 512
961 if bytes < 128 * 1024 * 1024: # less than 128MiB
962 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
963 # the maximum *valid* size of the meta device when living on top
964 # of LVM is hard to compute: it depends on the number of stripes
965 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
966 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
967 # size meta device; as such, we restrict it to 1GB (a little bit
968 # too generous, but making assumptions about PE size is hard)
969 if bytes > 1024 * 1024 * 1024:
970 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
972 def Rename(self, new_id):
975 This is not supported for drbd devices.
978 raise errors.ProgrammerError("Can't rename a drbd device")
981 class DRBD8(BaseDRBD):
982 """DRBD v8.x block device.
984 This implements the local host part of the DRBD device, i.e. it
985 doesn't do anything to the supposed peer. If you need a fully
986 connected DRBD pair, you need to use this class on both hosts.
988 The unique_id for the drbd device is the (local_ip, local_port,
989 remote_ip, remote_port) tuple, and it must have two children: the
990 data device and the meta_device. The meta device is checked for
991 valid size and is zeroed on create.
998 _NET_RECONFIG_TIMEOUT = 60
1000 def __init__(self, unique_id, children, size):
1001 if children and children.count(None) > 0:
1003 if len(children) not in (0, 2):
1004 raise ValueError("Invalid configuration data %s" % str(children))
1005 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1006 raise ValueError("Invalid configuration data %s" % str(unique_id))
1007 (self._lhost, self._lport,
1008 self._rhost, self._rport,
1009 self._aminor, self._secret) = unique_id
1011 if not _CanReadDevice(children[1].dev_path):
1012 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1014 super(DRBD8, self).__init__(unique_id, children, size)
1015 self.major = self._DRBD_MAJOR
1016 version = self._GetVersion()
1017 if version['k_major'] != 8 :
1018 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1019 " usage: kernel is %s.%s, ganeti wants 8.x",
1020 version['k_major'], version['k_minor'])
1022 if (self._lhost is not None and self._lhost == self._rhost and
1023 self._lport == self._rport):
1024 raise ValueError("Invalid configuration data, same local/remote %s" %
1029 def _InitMeta(cls, minor, dev_path):
1030 """Initialize a meta device.
1032 This will not work if the given minor is in use.
1035 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1036 "v08", dev_path, "0", "create-md"])
1038 _ThrowError("Can't initialize meta device: %s", result.output)
1041 def _FindUnusedMinor(cls):
1042 """Find an unused DRBD device.
1044 This is specific to 8.x as the minors are allocated dynamically,
1045 so non-existing numbers up to a max minor count are actually free.
1048 data = cls._GetProcData()
1052 match = cls._UNUSED_LINE_RE.match(line)
1054 return int(match.group(1))
1055 match = cls._VALID_LINE_RE.match(line)
1057 minor = int(match.group(1))
1058 highest = max(highest, minor)
1059 if highest is None: # there are no minors in use at all
1061 if highest >= cls._MAX_MINORS:
1062 logging.error("Error: no free drbd minors!")
1063 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1067 def _GetShowParser(cls):
1068 """Return a parser for `drbd show` output.
1070 This will either create or return an already-create parser for the
1071 output of the command `drbd show`.
1074 if cls._PARSE_SHOW is not None:
1075 return cls._PARSE_SHOW
1078 lbrace = pyp.Literal("{").suppress()
1079 rbrace = pyp.Literal("}").suppress()
1080 semi = pyp.Literal(";").suppress()
1081 # this also converts the value to an int
1082 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1084 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1085 defa = pyp.Literal("_is_default").suppress()
1086 dbl_quote = pyp.Literal('"').suppress()
1088 keyword = pyp.Word(pyp.alphanums + '-')
1091 value = pyp.Word(pyp.alphanums + '_-/.:')
1092 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1093 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1094 pyp.Optional(pyp.Literal("ipv6")).suppress())
1095 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
1096 pyp.Literal(':').suppress() + number)
1097 # meta device, extended syntax
1098 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1099 number + pyp.Word(']').suppress())
1100 # device name, extended syntax
1101 device_value = pyp.Literal("minor").suppress() + number
1104 stmt = (~rbrace + keyword + ~lbrace +
1105 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
1107 pyp.Optional(defa) + semi +
1108 pyp.Optional(pyp.restOfLine).suppress())
1111 section_name = pyp.Word(pyp.alphas + '_')
1112 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1114 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1117 cls._PARSE_SHOW = bnf
1122 def _GetShowData(cls, minor):
1123 """Return the `drbdsetup show` data for a minor.
1126 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1128 logging.error("Can't display the drbd config: %s - %s",
1129 result.fail_reason, result.output)
1131 return result.stdout
1134 def _GetDevInfo(cls, out):
1135 """Parse details about a given DRBD minor.
1137 This return, if available, the local backing device (as a path)
1138 and the local and remote (ip, port) information from a string
1139 containing the output of the `drbdsetup show` command as returned
1147 bnf = cls._GetShowParser()
1151 results = bnf.parseString(out)
1152 except pyp.ParseException, err:
1153 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1155 # and massage the results into our desired format
1156 for section in results:
1158 if sname == "_this_host":
1159 for lst in section[1:]:
1160 if lst[0] == "disk":
1161 data["local_dev"] = lst[1]
1162 elif lst[0] == "meta-disk":
1163 data["meta_dev"] = lst[1]
1164 data["meta_index"] = lst[2]
1165 elif lst[0] == "address":
1166 data["local_addr"] = tuple(lst[1:])
1167 elif sname == "_remote_host":
1168 for lst in section[1:]:
1169 if lst[0] == "address":
1170 data["remote_addr"] = tuple(lst[1:])
1173 def _MatchesLocal(self, info):
1174 """Test if our local config matches with an existing device.
1176 The parameter should be as returned from `_GetDevInfo()`. This
1177 method tests if our local backing device is the same as the one in
1178 the info parameter, in effect testing if we look like the given
1183 backend, meta = self._children
1185 backend = meta = None
1187 if backend is not None:
1188 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1190 retval = ("local_dev" not in info)
1192 if meta is not None:
1193 retval = retval and ("meta_dev" in info and
1194 info["meta_dev"] == meta.dev_path)
1195 retval = retval and ("meta_index" in info and
1196 info["meta_index"] == 0)
1198 retval = retval and ("meta_dev" not in info and
1199 "meta_index" not in info)
1202 def _MatchesNet(self, info):
1203 """Test if our network config matches with an existing device.
1205 The parameter should be as returned from `_GetDevInfo()`. This
1206 method tests if our network configuration is the same as the one
1207 in the info parameter, in effect testing if we look like the given
1211 if (((self._lhost is None and not ("local_addr" in info)) and
1212 (self._rhost is None and not ("remote_addr" in info)))):
1215 if self._lhost is None:
1218 if not ("local_addr" in info and
1219 "remote_addr" in info):
1222 retval = (info["local_addr"] == (self._lhost, self._lport))
1223 retval = (retval and
1224 info["remote_addr"] == (self._rhost, self._rport))
1228 def _AssembleLocal(cls, minor, backend, meta, size):
1229 """Configure the local part of a DRBD device.
1232 args = ["drbdsetup", cls._DevPath(minor), "disk",
1237 args.extend(["-d", "%sm" % size])
1238 if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1239 version = cls._GetVersion()
1240 # various DRBD versions support different disk barrier options;
1241 # what we aim here is to revert back to the 'drain' method of
1242 # disk flushes and to disable metadata barriers, in effect going
1243 # back to pre-8.0.7 behaviour
1244 vmaj = version['k_major']
1245 vmin = version['k_minor']
1246 vrel = version['k_point']
1248 if vmin == 0: # 8.0.x
1250 args.extend(['-i', '-m'])
1251 elif vmin == 2: # 8.2.x
1253 args.extend(['-i', '-m'])
1254 elif vmaj >= 3: # 8.3.x or newer
1255 args.extend(['-i', '-a', 'm'])
1256 result = utils.RunCmd(args)
1258 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1261 def _AssembleNet(cls, minor, net_info, protocol,
1262 dual_pri=False, hmac=None, secret=None):
1263 """Configure the network part of the device.
1266 lhost, lport, rhost, rport = net_info
1267 if None in net_info:
1268 # we don't want network connection and actually want to make
1270 cls._ShutdownNet(minor)
1273 # Workaround for a race condition. When DRBD is doing its dance to
1274 # establish a connection with its peer, it also sends the
1275 # synchronization speed over the wire. In some cases setting the
1276 # sync speed only after setting up both sides can race with DRBD
1277 # connecting, hence we set it here before telling DRBD anything
1279 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1281 args = ["drbdsetup", cls._DevPath(minor), "net",
1282 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1283 "-A", "discard-zero-changes",
1290 args.extend(["-a", hmac, "-x", secret])
1291 result = utils.RunCmd(args)
1293 _ThrowError("drbd%d: can't setup network: %s - %s",
1294 minor, result.fail_reason, result.output)
1296 def _CheckNetworkConfig():
1297 info = cls._GetDevInfo(cls._GetShowData(minor))
1298 if not "local_addr" in info or not "remote_addr" in info:
1299 raise utils.RetryAgain()
1301 if (info["local_addr"] != (lhost, lport) or
1302 info["remote_addr"] != (rhost, rport)):
1303 raise utils.RetryAgain()
1306 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1307 except utils.RetryTimeout:
1308 _ThrowError("drbd%d: timeout while configuring network", minor)
1310 def AddChildren(self, devices):
1311 """Add a disk to the DRBD device.
1314 if self.minor is None:
1315 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1317 if len(devices) != 2:
1318 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1319 info = self._GetDevInfo(self._GetShowData(self.minor))
1320 if "local_dev" in info:
1321 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1322 backend, meta = devices
1323 if backend.dev_path is None or meta.dev_path is None:
1324 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1327 self._CheckMetaSize(meta.dev_path)
1328 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1330 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1331 self._children = devices
1333 def RemoveChildren(self, devices):
1334 """Detach the drbd device from local storage.
1337 if self.minor is None:
1338 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1340 # early return if we don't actually have backing storage
1341 info = self._GetDevInfo(self._GetShowData(self.minor))
1342 if "local_dev" not in info:
1344 if len(self._children) != 2:
1345 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1347 if self._children.count(None) == 2: # we don't actually have children :)
1348 logging.warning("drbd%d: requested detach while detached", self.minor)
1350 if len(devices) != 2:
1351 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1352 for child, dev in zip(self._children, devices):
1353 if dev != child.dev_path:
1354 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1355 " RemoveChildren", self.minor, dev, child.dev_path)
1357 self._ShutdownLocal(self.minor)
1361 def _SetMinorSyncSpeed(cls, minor, kbytes):
1362 """Set the speed of the DRBD syncer.
1364 This is the low-level implementation.
1367 @param minor: the drbd minor whose settings we change
1369 @param kbytes: the speed in kbytes/second
1371 @return: the success of the operation
1374 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1375 "-r", "%d" % kbytes, "--create-device"])
1377 logging.error("Can't change syncer rate: %s - %s",
1378 result.fail_reason, result.output)
1379 return not result.failed
1381 def SetSyncSpeed(self, kbytes):
1382 """Set the speed of the DRBD syncer.
1385 @param kbytes: the speed in kbytes/second
1387 @return: the success of the operation
1390 if self.minor is None:
1391 logging.info("Not attached during SetSyncSpeed")
1393 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1394 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1396 def GetProcStatus(self):
1397 """Return device data from /proc.
1400 if self.minor is None:
1401 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1402 proc_info = self._MassageProcData(self._GetProcData())
1403 if self.minor not in proc_info:
1404 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1405 return DRBD8Status(proc_info[self.minor])
1407 def GetSyncStatus(self):
1408 """Returns the sync status of the device.
1411 If sync_percent is None, it means all is ok
1412 If estimated_time is None, it means we can't estimate
1413 the time needed, otherwise it's the time left in seconds.
1416 We set the is_degraded parameter to True on two conditions:
1417 network not connected or local disk missing.
1419 We compute the ldisk parameter based on whether we have a local
1422 @rtype: objects.BlockDevStatus
1425 if self.minor is None and not self.Attach():
1426 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1428 stats = self.GetProcStatus()
1429 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1431 if stats.is_disk_uptodate:
1432 ldisk_status = constants.LDS_OKAY
1433 elif stats.is_diskless:
1434 ldisk_status = constants.LDS_FAULTY
1436 ldisk_status = constants.LDS_UNKNOWN
1438 return objects.BlockDevStatus(dev_path=self.dev_path,
1441 sync_percent=stats.sync_percent,
1442 estimated_time=stats.est_time,
1443 is_degraded=is_degraded,
1444 ldisk_status=ldisk_status)
1446 def Open(self, force=False):
1447 """Make the local state primary.
1449 If the 'force' parameter is given, the '-o' option is passed to
1450 drbdsetup. Since this is a potentially dangerous operation, the
1451 force flag should be only given after creation, when it actually
1455 if self.minor is None and not self.Attach():
1456 logging.error("DRBD cannot attach to a device during open")
1458 cmd = ["drbdsetup", self.dev_path, "primary"]
1461 result = utils.RunCmd(cmd)
1463 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1467 """Make the local state secondary.
1469 This will, of course, fail if the device is in use.
1472 if self.minor is None and not self.Attach():
1473 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1474 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1476 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1477 self.minor, result.output)
1479 def DisconnectNet(self):
1480 """Removes network configuration.
1482 This method shutdowns the network side of the device.
1484 The method will wait up to a hardcoded timeout for the device to
1485 go into standalone after the 'disconnect' command before
1486 re-configuring it, as sometimes it takes a while for the
1487 disconnect to actually propagate and thus we might issue a 'net'
1488 command while the device is still connected. If the device will
1489 still be attached to the network and we time out, we raise an
1493 if self.minor is None:
1494 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1496 if None in (self._lhost, self._lport, self._rhost, self._rport):
1497 _ThrowError("drbd%d: DRBD disk missing network info in"
1498 " DisconnectNet()", self.minor)
1500 class _DisconnectStatus:
1501 def __init__(self, ever_disconnected):
1502 self.ever_disconnected = ever_disconnected
1504 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1506 def _WaitForDisconnect():
1507 if self.GetProcStatus().is_standalone:
1510 # retry the disconnect, it seems possible that due to a well-time
1511 # disconnect on the peer, my disconnect command might be ignored and
1513 dstatus.ever_disconnected = \
1514 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1516 raise utils.RetryAgain()
1519 start_time = time.time()
1522 # Start delay at 100 milliseconds and grow up to 2 seconds
1523 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1524 self._NET_RECONFIG_TIMEOUT)
1525 except utils.RetryTimeout:
1526 if dstatus.ever_disconnected:
1527 msg = ("drbd%d: device did not react to the"
1528 " 'disconnect' command in a timely manner")
1530 msg = "drbd%d: can't shutdown network, even after multiple retries"
1532 _ThrowError(msg, self.minor)
1534 reconfig_time = time.time() - start_time
1535 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1536 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1537 self.minor, reconfig_time)
1539 def AttachNet(self, multimaster):
1540 """Reconnects the network.
1542 This method connects the network side of the device with a
1543 specified multi-master flag. The device needs to be 'Standalone'
1544 but have valid network configuration data.
1547 - multimaster: init the network in dual-primary mode
1550 if self.minor is None:
1551 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1553 if None in (self._lhost, self._lport, self._rhost, self._rport):
1554 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1556 status = self.GetProcStatus()
1558 if not status.is_standalone:
1559 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1561 self._AssembleNet(self.minor,
1562 (self._lhost, self._lport, self._rhost, self._rport),
1563 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1564 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1567 """Check if our minor is configured.
1569 This doesn't do any device configurations - it only checks if the
1570 minor is in a state different from Unconfigured.
1572 Note that this function will not change the state of the system in
1573 any way (except in case of side-effects caused by reading from
1577 used_devs = self.GetUsedDevs()
1578 if self._aminor in used_devs:
1579 minor = self._aminor
1583 self._SetFromMinor(minor)
1584 return minor is not None
1587 """Assemble the drbd.
1590 - if we have a configured device, we try to ensure that it matches
1592 - if not, we create it from zero
1595 super(DRBD8, self).Assemble()
1598 if self.minor is None:
1599 # local device completely unconfigured
1600 self._FastAssemble()
1602 # we have to recheck the local and network status and try to fix
1604 self._SlowAssemble()
1606 def _SlowAssemble(self):
1607 """Assembles the DRBD device from a (partially) configured device.
1609 In case of partially attached (local device matches but no network
1610 setup), we perform the network attach. If successful, we re-test
1611 the attach if can return success.
1614 # TODO: Rewrite to not use a for loop just because there is 'break'
1615 # pylint: disable-msg=W0631
1616 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1617 for minor in (self._aminor,):
1618 info = self._GetDevInfo(self._GetShowData(minor))
1619 match_l = self._MatchesLocal(info)
1620 match_r = self._MatchesNet(info)
1622 if match_l and match_r:
1623 # everything matches
1626 if match_l and not match_r and "local_addr" not in info:
1627 # disk matches, but not attached to network, attach and recheck
1628 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1629 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1630 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1633 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1634 " show' disagrees", minor)
1636 if match_r and "local_dev" not in info:
1637 # no local disk, but network attached and it matches
1638 self._AssembleLocal(minor, self._children[0].dev_path,
1639 self._children[1].dev_path, self.size)
1640 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1643 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1644 " show' disagrees", minor)
1646 # this case must be considered only if we actually have local
1647 # storage, i.e. not in diskless mode, because all diskless
1648 # devices are equal from the point of view of local
1650 if (match_l and "local_dev" in info and
1651 not match_r and "local_addr" in info):
1652 # strange case - the device network part points to somewhere
1653 # else, even though its local storage is ours; as we own the
1654 # drbd space, we try to disconnect from the remote peer and
1655 # reconnect to our correct one
1657 self._ShutdownNet(minor)
1658 except errors.BlockDeviceError, err:
1659 _ThrowError("drbd%d: device has correct local storage, wrong"
1660 " remote peer and is unable to disconnect in order"
1661 " to attach to the correct peer: %s", minor, str(err))
1662 # note: _AssembleNet also handles the case when we don't want
1663 # local storage (i.e. one or more of the _[lr](host|port) is
1665 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1666 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1667 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1670 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1671 " show' disagrees", minor)
1676 self._SetFromMinor(minor)
1678 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1681 def _FastAssemble(self):
1682 """Assemble the drbd device from zero.
1684 This is run when in Assemble we detect our minor is unused.
1687 minor = self._aminor
1688 if self._children and self._children[0] and self._children[1]:
1689 self._AssembleLocal(minor, self._children[0].dev_path,
1690 self._children[1].dev_path, self.size)
1691 if self._lhost and self._lport and self._rhost and self._rport:
1692 self._AssembleNet(minor,
1693 (self._lhost, self._lport, self._rhost, self._rport),
1694 constants.DRBD_NET_PROTOCOL,
1695 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1696 self._SetFromMinor(minor)
1699 def _ShutdownLocal(cls, minor):
1700 """Detach from the local device.
1702 I/Os will continue to be served from the remote device. If we
1703 don't have a remote device, this operation will fail.
1706 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1708 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1711 def _ShutdownNet(cls, minor):
1712 """Disconnect from the remote peer.
1714 This fails if we don't have a local device.
1717 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1719 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1722 def _ShutdownAll(cls, minor):
1723 """Deactivate the device.
1725 This will, of course, fail if the device is in use.
1728 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1730 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1731 minor, result.output)
1734 """Shutdown the DRBD device.
1737 if self.minor is None and not self.Attach():
1738 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1742 self.dev_path = None
1743 self._ShutdownAll(minor)
1746 """Stub remove for DRBD devices.
1752 def Create(cls, unique_id, children, size):
1753 """Create a new DRBD8 device.
1755 Since DRBD devices are not created per se, just assembled, this
1756 function only initializes the metadata.
1759 if len(children) != 2:
1760 raise errors.ProgrammerError("Invalid setup for the drbd device")
1761 # check that the minor is unused
1762 aminor = unique_id[4]
1763 proc_info = cls._MassageProcData(cls._GetProcData())
1764 if aminor in proc_info:
1765 status = DRBD8Status(proc_info[aminor])
1766 in_use = status.is_in_use
1770 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1773 if not meta.Attach():
1774 _ThrowError("drbd%d: can't attach to meta device '%s'",
1776 cls._CheckMetaSize(meta.dev_path)
1777 cls._InitMeta(aminor, meta.dev_path)
1778 return cls(unique_id, children, size)
1780 def Grow(self, amount):
1781 """Resize the DRBD device and its backing storage.
1784 if self.minor is None:
1785 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1786 if len(self._children) != 2 or None in self._children:
1787 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1788 self._children[0].Grow(amount)
1789 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1790 "%dm" % (self.size + amount)])
1792 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1795 class FileStorage(BlockDev):
1798 This class represents the a file storage backend device.
1800 The unique_id for the file device is a (file_driver, file_path) tuple.
1803 def __init__(self, unique_id, children, size):
1804 """Initalizes a file device backend.
1808 raise errors.BlockDeviceError("Invalid setup for file device")
1809 super(FileStorage, self).__init__(unique_id, children, size)
1810 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1811 raise ValueError("Invalid configuration data %s" % str(unique_id))
1812 self.driver = unique_id[0]
1813 self.dev_path = unique_id[1]
1817 """Assemble the device.
1819 Checks whether the file device exists, raises BlockDeviceError otherwise.
1822 if not os.path.exists(self.dev_path):
1823 _ThrowError("File device '%s' does not exist" % self.dev_path)
1826 """Shutdown the device.
1828 This is a no-op for the file type, as we don't deactivate
1829 the file on shutdown.
1834 def Open(self, force=False):
1835 """Make the device ready for I/O.
1837 This is a no-op for the file type.
1843 """Notifies that the device will no longer be used for I/O.
1845 This is a no-op for the file type.
1851 """Remove the file backing the block device.
1854 @return: True if the removal was successful
1858 os.remove(self.dev_path)
1859 except OSError, err:
1860 if err.errno != errno.ENOENT:
1861 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1863 def Rename(self, new_id):
1864 """Renames the file.
1867 # TODO: implement rename for file-based storage
1868 _ThrowError("Rename is not supported for file-based storage")
1870 def Grow(self, amount):
1873 @param amount: the amount (in mebibytes) to grow with
1876 # TODO: implement grow for file-based storage
1877 _ThrowError("Grow not supported for file-based storage")
1880 """Attach to an existing file.
1882 Check if this file already exists.
1885 @return: True if file exists
1888 self.attached = os.path.exists(self.dev_path)
1889 return self.attached
1891 def GetActualSize(self):
1892 """Return the actual disk size.
1894 @note: the device needs to be active when this is called
1897 assert self.attached, "BlockDevice not attached in GetActualSize()"
1899 st = os.stat(self.dev_path)
1901 except OSError, err:
1902 _ThrowError("Can't stat %s: %s", self.dev_path, err)
1905 def Create(cls, unique_id, children, size):
1906 """Create a new file.
1908 @param size: the size of file in MiB
1910 @rtype: L{bdev.FileStorage}
1911 @return: an instance of FileStorage
1914 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1915 raise ValueError("Invalid configuration data %s" % str(unique_id))
1916 dev_path = unique_id[1]
1917 if os.path.exists(dev_path):
1918 _ThrowError("File already existing: %s", dev_path)
1920 f = open(dev_path, 'w')
1921 f.truncate(size * 1024 * 1024)
1923 except IOError, err:
1924 _ThrowError("Error in file creation: %", str(err))
1926 return FileStorage(unique_id, children, size)
1930 constants.LD_LV: LogicalVolume,
1931 constants.LD_DRBD8: DRBD8,
1934 if constants.ENABLE_FILE_STORAGE:
1935 DEV_MAP[constants.LD_FILE] = FileStorage
1938 def FindDevice(dev_type, unique_id, children, size):
1939 """Search for an existing, assembled device.
1941 This will succeed only if the device exists and is assembled, but it
1942 does not do any actions in order to activate the device.
1945 if dev_type not in DEV_MAP:
1946 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1947 device = DEV_MAP[dev_type](unique_id, children, size)
1948 if not device.attached:
1953 def Assemble(dev_type, unique_id, children, size):
1954 """Try to attach or assemble an existing device.
1956 This will attach to assemble the device, as needed, to bring it
1957 fully up. It must be safe to run on already-assembled devices.
1960 if dev_type not in DEV_MAP:
1961 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1962 device = DEV_MAP[dev_type](unique_id, children, size)
1967 def Create(dev_type, unique_id, children, size):
1971 if dev_type not in DEV_MAP:
1972 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1973 device = DEV_MAP[dev_type].Create(unique_id, children, size)