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
29 from ganeti import utils
30 from ganeti import logger
31 from ganeti import errors
32 from ganeti import constants
35 class BlockDev(object):
36 """Block device abstract class.
38 A block device can be in the following states:
39 - not existing on the system, and by `Create()` it goes into:
40 - existing but not setup/not active, and by `Assemble()` goes into:
41 - active read-write and by `Open()` it goes into
42 - online (=used, or ready for use)
44 A device can also be online but read-only, however we are not using
45 the readonly state (MD and LV have it, if needed in the future)
46 and we are usually looking at this like at a stack, so it's easier
47 to conceptualise the transition from not-existing to online and back
50 The many different states of the device are due to the fact that we
51 need to cover many device types:
52 - logical volumes are created, lvchange -a y $lv, and used
53 - md arrays are created or assembled and used
54 - drbd devices are attached to a local disk/remote peer and made primary
56 The status of the device can be examined by `GetStatus()`, which
57 returns a numerical value, depending on the position in the
58 transition stack of the device.
60 A block device is identified by three items:
61 - the /dev path of the device (dynamic)
62 - a unique ID of the device (static)
63 - it's major/minor pair (dynamic)
65 Not all devices implement both the first two as distinct items. LVM
66 logical volumes have their unique ID (the pair volume group, logical
67 volume name) in a 1-to-1 relation to the dev path. For MD devices,
68 the /dev path is dynamic and the unique ID is the UUID generated at
69 array creation plus the slave list. For DRBD devices, the /dev path
70 is again dynamic and the unique id is the pair (host1, dev1),
73 You can get to a device in two ways:
74 - creating the (real) device, which returns you
75 an attached instance (lvcreate, mdadm --create)
76 - attaching of a python instance to an existing (real) device
78 The second point, the attachement to a device, is different
79 depending on whether the device is assembled or not. At init() time,
80 we search for a device with the same unique_id as us. If found,
81 good. It also means that the device is already assembled. If not,
82 after assembly we'll have our correct major/minor.
91 STATUS_UNKNOWN: "unknown",
92 STATUS_EXISTING: "existing",
93 STATUS_STANDBY: "ready for use",
94 STATUS_ONLINE: "online",
97 def __init__(self, unique_id, children):
98 self._children = children
100 self.unique_id = unique_id
105 """Assemble the device from its components.
107 If this is a plain block device (e.g. LVM) than assemble does
108 nothing, as the LVM has no children and we don't put logical
111 One guarantee is that after the device has been assembled, it
112 knows its major/minor numbers. This allows other devices (usually
113 parents) to probe correctly for their children.
117 for child in self._children:
118 if not isinstance(child, BlockDev):
119 raise TypeError("Invalid child passed of type '%s'" % type(child))
122 status = status and child.Assemble()
128 except errors.BlockDeviceError:
129 for child in self._children:
134 for child in self._children:
139 """Find a device which matches our config and attach to it.
142 raise NotImplementedError
145 """Notifies that the device will no longer be used for I/O.
148 raise NotImplementedError
151 def Create(cls, unique_id, children, size):
152 """Create the device.
154 If the device cannot be created, it will return None
155 instead. Error messages go to the logging system.
157 Note that for some devices, the unique_id is used, and for other,
158 the children. The idea is that these two, taken together, are
159 enough for both creation and assembly (later).
162 raise NotImplementedError
165 """Remove this device.
167 This makes sense only for some of the device types: LV and to a
168 lesser degree, md devices. Also note that if the device can't
169 attach, the removal can't be completed.
172 raise NotImplementedError
174 def Rename(self, new_id):
175 """Rename this device.
177 This may or may not make sense for a given device type.
180 raise NotImplementedError
183 """Return the status of the device.
186 raise NotImplementedError
188 def Open(self, force=False):
189 """Make the device ready for use.
191 This makes the device ready for I/O. For now, just the DRBD
194 The force parameter signifies that if the device has any kind of
195 --force thing, it should be used, we know what we are doing.
198 raise NotImplementedError
201 """Shut down the device, freeing its children.
203 This undoes the `Assemble()` work, except for the child
204 assembling; as such, the children on the device are still
205 assembled after this call.
208 raise NotImplementedError
210 def SetSyncSpeed(self, speed):
211 """Adjust the sync speed of the mirror.
213 In case this is not a mirroring device, this is no-op.
218 for child in self._children:
219 result = result and child.SetSyncSpeed(speed)
222 def GetSyncStatus(self):
223 """Returns the sync status of the device.
225 If this device is a mirroring device, this function returns the
226 status of the mirror.
229 (sync_percent, estimated_time, is_degraded, ldisk)
231 If sync_percent is None, it means the device is not syncing.
233 If estimated_time is None, it means we can't estimate
234 the time needed, otherwise it's the time left in seconds.
236 If is_degraded is True, it means the device is missing
237 redundancy. This is usually a sign that something went wrong in
238 the device setup, if sync_percent is None.
240 The ldisk parameter represents the degradation of the local
241 data. This is only valid for some devices, the rest will always
242 return False (not degraded).
245 return None, None, False, False
248 def CombinedSyncStatus(self):
249 """Calculate the mirror status recursively for our children.
251 The return value is the same as for `GetSyncStatus()` except the
252 minimum percent and maximum time are calculated across our
256 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
258 for child in self._children:
259 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
260 if min_percent is None:
261 min_percent = c_percent
262 elif c_percent is not None:
263 min_percent = min(min_percent, c_percent)
266 elif c_time is not None:
267 max_time = max(max_time, c_time)
268 is_degraded = is_degraded or c_degraded
269 ldisk = ldisk or c_ldisk
270 return min_percent, max_time, is_degraded, ldisk
273 def SetInfo(self, text):
274 """Update metadata with info text.
276 Only supported for some device types.
279 for child in self._children:
284 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
285 (self.__class__, self.unique_id, self._children,
286 self.major, self.minor, self.dev_path))
289 class LogicalVolume(BlockDev):
290 """Logical Volume block device.
293 def __init__(self, unique_id, children):
294 """Attaches to a LV device.
296 The unique_id is a tuple (vg_name, lv_name)
299 super(LogicalVolume, self).__init__(unique_id, children)
300 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
301 raise ValueError("Invalid configuration data %s" % str(unique_id))
302 self._vg_name, self._lv_name = unique_id
303 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
307 def Create(cls, unique_id, children, size):
308 """Create a new logical volume.
311 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
312 raise ValueError("Invalid configuration data %s" % str(unique_id))
313 vg_name, lv_name = unique_id
314 pvs_info = cls.GetPVInfo(vg_name)
316 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
321 pvlist = [ pv[1] for pv in pvs_info ]
322 free_size = sum([ pv[0] for pv in pvs_info ])
324 # The size constraint should have been checked from the master before
325 # calling the create function.
327 raise errors.BlockDeviceError("Not enough free space: required %s,"
328 " available %s" % (size, free_size))
329 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
332 raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
334 return LogicalVolume(unique_id, children)
337 def GetPVInfo(vg_name):
338 """Get the free space info for PVs in a volume group.
341 vg_name: the volume group name
344 list of (free_space, name) with free_space in mebibytes
347 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
348 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
350 result = utils.RunCmd(command)
352 logger.Error("Can't get the PV information: %s - %s" %
353 (result.fail_reason, result.output))
356 for line in result.stdout.splitlines():
357 fields = line.strip().split(':')
359 logger.Error("Can't parse pvs output: line '%s'" % line)
361 # skip over pvs from another vg or ones which are not allocatable
362 if fields[1] != vg_name or fields[3][0] != 'a':
364 data.append((float(fields[2]), fields[0]))
369 """Remove this logical volume.
372 if not self.minor and not self.Attach():
373 # the LV does not exist
375 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
376 (self._vg_name, self._lv_name)])
378 logger.Error("Can't lvremove: %s - %s" %
379 (result.fail_reason, result.output))
381 return not result.failed
383 def Rename(self, new_id):
384 """Rename this logical volume.
387 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
388 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
389 new_vg, new_name = new_id
390 if new_vg != self._vg_name:
391 raise errors.ProgrammerError("Can't move a logical volume across"
392 " volume groups (from %s to to %s)" %
393 (self._vg_name, new_vg))
394 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
396 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
398 self._lv_name = new_name
399 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
402 """Attach to an existing LV.
404 This method will try to see if an existing and active LV exists
405 which matches our name. If so, its major/minor will be
409 result = utils.RunCmd(["lvdisplay", self.dev_path])
411 logger.Error("Can't find LV %s: %s, %s" %
412 (self.dev_path, result.fail_reason, result.output))
414 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
415 for line in result.stdout.splitlines():
416 match_result = match.match(line)
418 self.major = int(match_result.group(1))
419 self.minor = int(match_result.group(2))
424 """Assemble the device.
426 We alway run `lvchange -ay` on the LV to ensure it's active before
427 use, as there were cases when xenvg was not active after boot
428 (also possibly after disk issues).
431 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
433 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
434 return not result.failed
437 """Shutdown the device.
439 This is a no-op for the LV device type, as we don't deactivate the
446 """Return the status of the device.
448 Logical volumes will can be in all four states, although we don't
449 deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
450 should not be seen for our devices.
453 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
455 logger.Error("Can't display lv: %s - %s" %
456 (result.fail_reason, result.output))
457 return self.STATUS_UNKNOWN
458 out = result.stdout.strip()
459 # format: type/permissions/alloc/fixed_minor/state/open
461 return self.STATUS_UNKNOWN
462 #writable = (out[1] == "w")
463 active = (out[4] == "a")
464 online = (out[5] == "o")
466 retval = self.STATUS_ONLINE
468 retval = self.STATUS_STANDBY
470 retval = self.STATUS_EXISTING
474 def GetSyncStatus(self):
475 """Returns the sync status of the device.
477 If this device is a mirroring device, this function returns the
478 status of the mirror.
481 (sync_percent, estimated_time, is_degraded, ldisk)
483 For logical volumes, sync_percent and estimated_time are always
484 None (no recovery in progress, as we don't handle the mirrored LV
485 case). The is_degraded parameter is the inverse of the ldisk
488 For the ldisk parameter, we check if the logical volume has the
489 'virtual' type, which means it's not backed by existing storage
490 anymore (read from it return I/O error). This happens after a
491 physical disk failure and subsequent 'vgreduce --removemissing' on
495 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
497 logger.Error("Can't display lv: %s - %s" %
498 (result.fail_reason, result.output))
499 return None, None, True, True
500 out = result.stdout.strip()
501 # format: type/permissions/alloc/fixed_minor/state/open
503 logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
504 return None, None, True, True
505 ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
507 return None, None, ldisk, ldisk
509 def Open(self, force=False):
510 """Make the device ready for I/O.
512 This is a no-op for the LV device type.
518 """Notifies that the device will no longer be used for I/O.
520 This is a no-op for the LV device type.
525 def Snapshot(self, size):
526 """Create a snapshot copy of an lvm block device.
529 snap_name = self._lv_name + ".snap"
531 # remove existing snapshot if found
532 snap = LogicalVolume((self._vg_name, snap_name), None)
535 pvs_info = self.GetPVInfo(self._vg_name)
537 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
541 free_size, pv_name = pvs_info[0]
543 raise errors.BlockDeviceError("Not enough free space: required %s,"
544 " available %s" % (size, free_size))
546 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
547 "-n%s" % snap_name, self.dev_path])
549 raise errors.BlockDeviceError("command: %s error: %s - %s" %
550 (result.cmd, result.fail_reason,
555 def SetInfo(self, text):
556 """Update metadata with info text.
559 BlockDev.SetInfo(self, text)
561 # Replace invalid characters
562 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
563 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
565 # Only up to 128 characters are allowed
568 result = utils.RunCmd(["lvchange", "--addtag", text,
571 raise errors.BlockDeviceError("Command: %s error: %s - %s" %
572 (result.cmd, result.fail_reason,
576 class MDRaid1(BlockDev):
577 """raid1 device implemented via md.
580 def __init__(self, unique_id, children):
581 super(MDRaid1, self).__init__(unique_id, children)
586 """Find an array which matches our config and attach to it.
588 This tries to find a MD array which has the same UUID as our own.
591 minor = self._FindMDByUUID(self.unique_id)
592 if minor is not None:
593 self._SetFromMinor(minor)
598 return (minor is not None)
602 """Compute the list of in-use MD devices.
604 It doesn't matter if the used device have other raid level, just
605 that they are in use.
608 mdstat = open("/proc/mdstat", "r")
609 data = mdstat.readlines()
613 valid_line = re.compile("^md([0-9]+) : .*$")
615 match = valid_line.match(line)
617 md_no = int(match.group(1))
618 used_md[md_no] = line
623 def _GetDevInfo(minor):
624 """Get info about a MD device.
626 Currently only uuid is returned.
629 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
631 logger.Error("Can't display md: %s - %s" %
632 (result.fail_reason, result.output))
635 for line in result.stdout.splitlines():
637 kv = line.split(" : ", 1)
640 retval["uuid"] = kv[1].split()[0]
641 elif kv[0] == "State":
642 retval["state"] = kv[1].split(", ")
646 def _FindUnusedMinor():
647 """Compute an unused MD minor.
649 This code assumes that there are 256 minors only.
652 used_md = MDRaid1._GetUsedDevs()
659 logger.Error("Critical: Out of md minor numbers.")
660 raise errors.BlockDeviceError("Can't find a free MD minor")
664 def _FindMDByUUID(cls, uuid):
665 """Find the minor of an MD array with a given UUID.
668 md_list = cls._GetUsedDevs()
669 for minor in md_list:
670 info = cls._GetDevInfo(minor)
671 if info and info["uuid"] == uuid:
676 def _ZeroSuperblock(dev_path):
677 """Zero the possible locations for an MD superblock.
679 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
680 fails in versions 2.x with the same error code as non-writable
683 The superblocks are located at (negative values are relative to
684 the end of the block device):
685 - -128k to end for version 0.90 superblock
686 - -8k to -12k for version 1.0 superblock (included in the above)
687 - 0k to 4k for version 1.1 superblock
688 - 4k to 8k for version 1.2 superblock
690 To cover all situations, the zero-ing will be:
694 As such, the minimum device size must be 128k, otherwise we'll get
697 Note that this function depends on the fact that one can open,
698 read and write block devices normally.
701 overwrite_size = 128 * 1024
702 empty_buf = '\0' * overwrite_size
703 fd = open(dev_path, "r+")
709 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
710 fd.seek(-overwrite_size, 2)
714 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
719 def Create(cls, unique_id, children, size):
720 """Create a new MD raid1 array.
723 if not isinstance(children, (tuple, list)):
724 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
727 if not isinstance(i, BlockDev):
728 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
731 cls._ZeroSuperblock(i.dev_path)
732 except EnvironmentError, err:
733 logger.Error("Can't zero superblock for %s: %s" %
734 (i.dev_path, str(err)))
736 minor = cls._FindUnusedMinor()
737 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
738 "--auto=yes", "--force", "-l1",
739 "-n%d" % len(children)] +
740 [dev.dev_path for dev in children])
743 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
746 info = cls._GetDevInfo(minor)
747 if not info or not "uuid" in info:
748 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
750 return MDRaid1(info["uuid"], children)
753 """Stub remove function for MD RAID 1 arrays.
755 We don't remove the superblock right now. Mark a to do.
758 #TODO: maybe zero superblock on child devices?
759 return self.Shutdown()
761 def Rename(self, new_id):
764 This is not supported for md raid1 devices.
767 raise errors.ProgrammerError("Can't rename a md raid1 device")
769 def AddChildren(self, devices):
770 """Add new member(s) to the md raid1.
773 if self.minor is None and not self.Attach():
774 raise errors.BlockDeviceError("Can't attach to device")
776 args = ["mdadm", "-a", self.dev_path]
778 if dev.dev_path is None:
779 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
781 args.append(dev.dev_path)
782 result = utils.RunCmd(args)
784 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
786 new_len = len(self._children) + len(devices)
787 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
789 raise errors.BlockDeviceError("Can't grow md array: %s" %
791 self._children.extend(devices)
793 def RemoveChildren(self, devices):
794 """Remove member(s) from the md raid1.
797 if self.minor is None and not self.Attach():
798 raise errors.BlockDeviceError("Can't attach to device")
799 new_len = len(self._children) - len(devices)
801 raise errors.BlockDeviceError("Can't reduce to less than one child")
802 args = ["mdadm", "-f", self.dev_path]
806 for c in self._children:
807 if c.dev_path == dev:
811 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
813 result = utils.RunCmd(args)
815 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
818 # it seems here we need a short delay for MD to update its
822 result = utils.RunCmd(args)
824 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
825 " %s" % result.output)
826 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
829 raise errors.BlockDeviceError("Can't shrink md array: %s" %
831 for dev in orig_devs:
832 self._children.remove(dev)
835 """Return the status of the device.
839 if self.minor is None:
840 retval = self.STATUS_UNKNOWN
842 retval = self.STATUS_ONLINE
845 def _SetFromMinor(self, minor):
846 """Set our parameters based on the given minor.
848 This sets our minor variable and our dev_path.
852 self.dev_path = "/dev/md%d" % minor
855 """Assemble the MD device.
857 At this point we should have:
858 - list of children devices
862 result = super(MDRaid1, self).Assemble()
865 md_list = self._GetUsedDevs()
866 for minor in md_list:
867 info = self._GetDevInfo(minor)
868 if info and info["uuid"] == self.unique_id:
869 self._SetFromMinor(minor)
870 logger.Info("MD array %s already started" % str(self))
872 free_minor = self._FindUnusedMinor()
873 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
874 self.unique_id, "/dev/md%d" % free_minor] +
875 [bdev.dev_path for bdev in self._children])
877 logger.Error("Can't assemble MD array: %s: %s" %
878 (result.fail_reason, result.output))
881 self.minor = free_minor
882 return not result.failed
885 """Tear down the MD array.
887 This does a 'mdadm --stop' so after this command, the array is no
891 if self.minor is None and not self.Attach():
892 logger.Info("MD object not attached to a device")
895 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
897 logger.Error("Can't stop MD array: %s - %s" %
898 (result.fail_reason, result.output))
904 def SetSyncSpeed(self, kbytes):
905 """Set the maximum sync speed for the MD array.
908 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
909 if self.minor is None:
910 logger.Error("MD array not attached to a device")
912 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
914 f.write("%d" % kbytes)
917 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
919 f.write("%d" % (kbytes/2))
924 def GetSyncStatus(self):
925 """Returns the sync status of the device.
928 (sync_percent, estimated_time, is_degraded, ldisk)
930 If sync_percent is None, it means all is ok
931 If estimated_time is None, it means we can't esimate
932 the time needed, otherwise it's the time left in seconds.
934 The ldisk parameter is always true for MD devices.
937 if self.minor is None and not self.Attach():
938 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
939 dev_info = self._GetDevInfo(self.minor)
940 is_clean = ("state" in dev_info and
941 len(dev_info["state"]) == 1 and
942 dev_info["state"][0] in ("clean", "active"))
943 sys_path = "/sys/block/md%s/md/" % self.minor
944 f = file(sys_path + "sync_action")
945 sync_status = f.readline().strip()
947 if sync_status == "idle":
948 return None, None, not is_clean, False
949 f = file(sys_path + "sync_completed")
950 sync_completed = f.readline().strip().split(" / ")
952 if len(sync_completed) != 2:
953 return 0, None, not is_clean, False
954 sync_done, sync_total = [float(i) for i in sync_completed]
955 sync_percent = 100.0*sync_done/sync_total
956 f = file(sys_path + "sync_speed")
957 sync_speed_k = int(f.readline().strip())
958 if sync_speed_k == 0:
961 time_est = (sync_total - sync_done) / 2 / sync_speed_k
962 return sync_percent, time_est, not is_clean, False
964 def Open(self, force=False):
965 """Make the device ready for I/O.
967 This is a no-op for the MDRaid1 device type, although we could use
968 the 2.6.18's new array_state thing.
974 """Notifies that the device will no longer be used for I/O.
976 This is a no-op for the MDRaid1 device type, but see comment for
983 class BaseDRBD(BlockDev):
986 This class contains a few bits of common functionality between the
987 0.7 and 8.x versions of DRBD.
990 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
991 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
994 _ST_UNCONFIGURED = "Unconfigured"
995 _ST_WFCONNECTION = "WFConnection"
996 _ST_CONNECTED = "Connected"
1000 """Return data from /proc/drbd.
1003 stat = open("/proc/drbd", "r")
1005 data = stat.read().splitlines()
1009 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
1013 def _MassageProcData(data):
1014 """Transform the output of _GetProdData into a nicer form.
1017 a dictionary of minor: joined lines from /proc/drbd for that minor
1020 lmatch = re.compile("^ *([0-9]+):.*$")
1022 old_minor = old_line = None
1024 lresult = lmatch.match(line)
1025 if lresult is not None:
1026 if old_minor is not None:
1027 results[old_minor] = old_line
1028 old_minor = int(lresult.group(1))
1031 if old_minor is not None:
1032 old_line += " " + line.strip()
1034 if old_minor is not None:
1035 results[old_minor] = old_line
1039 def _GetVersion(cls):
1040 """Return the DRBD version.
1042 This will return a dict with keys:
1048 proto2 (only on drbd > 8.2.X)
1051 proc_data = cls._GetProcData()
1052 first_line = proc_data[0].strip()
1053 version = cls._VERSION_RE.match(first_line)
1055 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1058 values = version.groups()
1059 retval = {'k_major': int(values[0]),
1060 'k_minor': int(values[1]),
1061 'k_point': int(values[2]),
1062 'api': int(values[3]),
1063 'proto': int(values[4]),
1065 if values[5] is not None:
1066 retval['proto2'] = values[5]
1071 def _DevPath(minor):
1072 """Return the path to a drbd device for a given minor.
1075 return "/dev/drbd%d" % minor
1078 def _GetUsedDevs(cls):
1079 """Compute the list of used DRBD devices.
1082 data = cls._GetProcData()
1085 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1087 match = valid_line.match(line)
1090 minor = int(match.group(1))
1091 state = match.group(2)
1092 if state == cls._ST_UNCONFIGURED:
1094 used_devs[minor] = state, line
1098 def _SetFromMinor(self, minor):
1099 """Set our parameters based on the given minor.
1101 This sets our minor variable and our dev_path.
1105 self.minor = self.dev_path = None
1108 self.dev_path = self._DevPath(minor)
1111 def _CheckMetaSize(meta_device):
1112 """Check if the given meta device looks like a valid one.
1114 This currently only check the size, which must be around
1118 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1120 logger.Error("Failed to get device size: %s - %s" %
1121 (result.fail_reason, result.output))
1124 sectors = int(result.stdout)
1126 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1128 bytes = sectors * 512
1129 if bytes < 128 * 1024 * 1024: # less than 128MiB
1130 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1132 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1133 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1137 def Rename(self, new_id):
1140 This is not supported for drbd devices.
1143 raise errors.ProgrammerError("Can't rename a drbd device")
1146 class DRBDev(BaseDRBD):
1147 """DRBD block device.
1149 This implements the local host part of the DRBD device, i.e. it
1150 doesn't do anything to the supposed peer. If you need a fully
1151 connected DRBD pair, you need to use this class on both hosts.
1153 The unique_id for the drbd device is the (local_ip, local_port,
1154 remote_ip, remote_port) tuple, and it must have two children: the
1155 data device and the meta_device. The meta device is checked for
1156 valid size and is zeroed on create.
1159 def __init__(self, unique_id, children):
1160 super(DRBDev, self).__init__(unique_id, children)
1161 self.major = self._DRBD_MAJOR
1162 version = self._GetVersion()
1163 if version['k_major'] != 0 and version['k_minor'] != 7:
1164 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1165 " requested ganeti usage: kernel is"
1166 " %s.%s, ganeti wants 0.7" %
1167 (version['k_major'], version['k_minor']))
1168 if len(children) != 2:
1169 raise ValueError("Invalid configuration data %s" % str(children))
1170 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1171 raise ValueError("Invalid configuration data %s" % str(unique_id))
1172 self._lhost, self._lport, self._rhost, self._rport = unique_id
1176 def _FindUnusedMinor(cls):
1177 """Find an unused DRBD device.
1180 data = cls._GetProcData()
1182 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1184 match = valid_line.match(line)
1186 return int(match.group(1))
1187 logger.Error("Error: no free drbd minors!")
1188 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1191 def _GetDevInfo(cls, minor):
1192 """Get details about a given DRBD minor.
1194 This return, if available, the local backing device in (major,
1195 minor) formant and the local and remote (ip, port) information.
1199 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1201 logger.Error("Can't display the drbd config: %s - %s" %
1202 (result.fail_reason, result.output))
1205 if out == "Not configured\n":
1207 for line in out.splitlines():
1208 if "local_dev" not in data:
1209 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1211 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1213 if "meta_dev" not in data:
1214 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1216 if match.group(2) is not None and match.group(3) is not None:
1217 # matched on the major/minor
1218 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1220 # matched on the "internal" string
1221 data["meta_dev"] = match.group(1)
1222 # in this case, no meta_index is in the output
1223 data["meta_index"] = -1
1225 if "meta_index" not in data:
1226 match = re.match("^Meta index: ([0-9]+).*$", line)
1228 data["meta_index"] = int(match.group(1))
1230 if "local_addr" not in data:
1231 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1233 data["local_addr"] = (match.group(1), int(match.group(2)))
1235 if "remote_addr" not in data:
1236 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1238 data["remote_addr"] = (match.group(1), int(match.group(2)))
1242 def _MatchesLocal(self, info):
1243 """Test if our local config matches with an existing device.
1245 The parameter should be as returned from `_GetDevInfo()`. This
1246 method tests if our local backing device is the same as the one in
1247 the info parameter, in effect testing if we look like the given
1251 if not ("local_dev" in info and "meta_dev" in info and
1252 "meta_index" in info):
1255 backend = self._children[0]
1256 if backend is not None:
1257 retval = (info["local_dev"] == (backend.major, backend.minor))
1259 retval = (info["local_dev"] == (0, 0))
1260 meta = self._children[1]
1261 if meta is not None:
1262 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1263 retval = retval and (info["meta_index"] == 0)
1265 retval = retval and (info["meta_dev"] == "internal" and
1266 info["meta_index"] == -1)
1269 def _MatchesNet(self, info):
1270 """Test if our network config matches with an existing device.
1272 The parameter should be as returned from `_GetDevInfo()`. This
1273 method tests if our network configuration is the same as the one
1274 in the info parameter, in effect testing if we look like the given
1278 if (((self._lhost is None and not ("local_addr" in info)) and
1279 (self._rhost is None and not ("remote_addr" in info)))):
1282 if self._lhost is None:
1285 if not ("local_addr" in info and
1286 "remote_addr" in info):
1289 retval = (info["local_addr"] == (self._lhost, self._lport))
1290 retval = (retval and
1291 info["remote_addr"] == (self._rhost, self._rport))
1295 def _AssembleLocal(cls, minor, backend, meta):
1296 """Configure the local part of a DRBD device.
1298 This is the first thing that must be done on an unconfigured DRBD
1299 device. And it must be done only once.
1302 if not cls._CheckMetaSize(meta):
1304 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1305 backend, meta, "0", "-e", "detach"])
1307 logger.Error("Can't attach local disk: %s" % result.output)
1308 return not result.failed
1311 def _ShutdownLocal(cls, minor):
1312 """Detach from the local device.
1314 I/Os will continue to be served from the remote device. If we
1315 don't have a remote device, this operation will fail.
1318 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1320 logger.Error("Can't detach local device: %s" % result.output)
1321 return not result.failed
1324 def _ShutdownAll(minor):
1325 """Deactivate the device.
1327 This will, of course, fail if the device is in use.
1330 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1332 logger.Error("Can't shutdown drbd device: %s" % result.output)
1333 return not result.failed
1336 def _AssembleNet(cls, minor, net_info, protocol):
1337 """Configure the network part of the device.
1339 This operation can be, in theory, done multiple times, but there
1340 have been cases (in lab testing) in which the network part of the
1341 device had become stuck and couldn't be shut down because activity
1342 from the new peer (also stuck) triggered a timer re-init and
1343 needed remote peer interface shutdown in order to clear. So please
1344 don't change online the net config.
1347 lhost, lport, rhost, rport = net_info
1348 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1349 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1352 logger.Error("Can't setup network for dbrd device: %s - %s" %
1353 (result.fail_reason, result.output))
1356 timeout = time.time() + 10
1358 while time.time() < timeout:
1359 info = cls._GetDevInfo(minor)
1360 if not "local_addr" in info or not "remote_addr" in info:
1363 if (info["local_addr"] != (lhost, lport) or
1364 info["remote_addr"] != (rhost, rport)):
1370 logger.Error("Timeout while configuring network")
1375 def _ShutdownNet(cls, minor):
1376 """Disconnect from the remote peer.
1378 This fails if we don't have a local device.
1381 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1383 logger.Error("Can't shutdown network: %s" % result.output)
1384 return not result.failed
1387 """Assemble the drbd.
1390 - if we have a local backing device, we bind to it by:
1391 - checking the list of used drbd devices
1392 - check if the local minor use of any of them is our own device
1395 - if we have a local/remote net info:
1396 - redo the local backing device step for the remote device
1397 - check if any drbd device is using the local port,
1399 - check if any remote drbd device is using the remote
1400 port, if yes abort (for now)
1402 - bind the remote net port
1406 if self.minor is not None:
1407 logger.Info("Already assembled")
1410 result = super(DRBDev, self).Assemble()
1414 minor = self._FindUnusedMinor()
1415 need_localdev_teardown = False
1416 if self._children[0]:
1417 result = self._AssembleLocal(minor, self._children[0].dev_path,
1418 self._children[1].dev_path)
1421 need_localdev_teardown = True
1422 if self._lhost and self._lport and self._rhost and self._rport:
1423 result = self._AssembleNet(minor,
1424 (self._lhost, self._lport,
1425 self._rhost, self._rport),
1428 if need_localdev_teardown:
1429 # we will ignore failures from this
1430 logger.Error("net setup failed, tearing down local device")
1431 self._ShutdownAll(minor)
1433 self._SetFromMinor(minor)
1437 """Shutdown the DRBD device.
1440 if self.minor is None and not self.Attach():
1441 logger.Info("DRBD device not attached to a device during Shutdown")
1443 if not self._ShutdownAll(self.minor):
1446 self.dev_path = None
1450 """Find a DRBD device which matches our config and attach to it.
1452 In case of partially attached (local device matches but no network
1453 setup), we perform the network attach. If successful, we re-test
1454 the attach if can return success.
1457 for minor in self._GetUsedDevs():
1458 info = self._GetDevInfo(minor)
1459 match_l = self._MatchesLocal(info)
1460 match_r = self._MatchesNet(info)
1461 if match_l and match_r:
1463 if match_l and not match_r and "local_addr" not in info:
1464 res_r = self._AssembleNet(minor,
1465 (self._lhost, self._lport,
1466 self._rhost, self._rport),
1468 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1473 self._SetFromMinor(minor)
1474 return minor is not None
1476 def Open(self, force=False):
1477 """Make the local state primary.
1479 If the 'force' parameter is given, the '--do-what-I-say' option is
1480 is passed to drbdsetup. Since this is a potentially dangerous operation,
1481 the force flag should be only given after creation, when it actually
1485 if self.minor is None and not self.Attach():
1486 logger.Error("DRBD cannot attach to a device during open")
1488 cmd = ["drbdsetup", self.dev_path, "primary"]
1490 cmd.append("--do-what-I-say")
1491 result = utils.RunCmd(cmd)
1493 msg = ("Can't make drbd device primary: %s" % result.output)
1495 raise errors.BlockDeviceError(msg)
1498 """Make the local state secondary.
1500 This will, of course, fail if the device is in use.
1503 if self.minor is None and not self.Attach():
1504 logger.Info("Instance not attached to a device")
1505 raise errors.BlockDeviceError("Can't find device")
1506 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1508 msg = ("Can't switch drbd device to"
1509 " secondary: %s" % result.output)
1511 raise errors.BlockDeviceError(msg)
1513 def SetSyncSpeed(self, kbytes):
1514 """Set the speed of the DRBD syncer.
1517 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1518 if self.minor is None:
1519 logger.Info("Instance not attached to a device")
1521 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1524 logger.Error("Can't change syncer rate: %s - %s" %
1525 (result.fail_reason, result.output))
1526 return not result.failed and children_result
1528 def GetSyncStatus(self):
1529 """Returns the sync status of the device.
1532 (sync_percent, estimated_time, is_degraded, ldisk)
1534 If sync_percent is None, it means all is ok
1535 If estimated_time is None, it means we can't esimate
1536 the time needed, otherwise it's the time left in seconds.
1538 The ldisk parameter will be returned as True, since the DRBD7
1539 devices have not been converted.
1542 if self.minor is None and not self.Attach():
1543 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1544 proc_info = self._MassageProcData(self._GetProcData())
1545 if self.minor not in proc_info:
1546 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1548 line = proc_info[self.minor]
1549 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1550 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1552 sync_percent = float(match.group(1))
1553 hours = int(match.group(2))
1554 minutes = int(match.group(3))
1555 seconds = int(match.group(4))
1556 est_time = hours * 3600 + minutes * 60 + seconds
1560 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1562 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1564 client_state = match.group(1)
1565 is_degraded = client_state != "Connected"
1566 return sync_percent, est_time, is_degraded, False
1568 def GetStatus(self):
1569 """Compute the status of the DRBD device
1571 Note that DRBD devices don't have the STATUS_EXISTING state.
1574 if self.minor is None and not self.Attach():
1575 return self.STATUS_UNKNOWN
1577 data = self._GetProcData()
1578 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1581 mresult = match.match(line)
1585 logger.Error("Can't find myself!")
1586 return self.STATUS_UNKNOWN
1588 state = mresult.group(2)
1589 if state == "Primary":
1590 result = self.STATUS_ONLINE
1592 result = self.STATUS_STANDBY
1597 def _ZeroDevice(device):
1600 This writes until we get ENOSPC.
1603 f = open(device, "w")
1604 buf = "\0" * 1048576
1608 except IOError, err:
1609 if err.errno != errno.ENOSPC:
1613 def Create(cls, unique_id, children, size):
1614 """Create a new DRBD device.
1616 Since DRBD devices are not created per se, just assembled, this
1617 function just zeroes the meta device.
1620 if len(children) != 2:
1621 raise errors.ProgrammerError("Invalid setup for the drbd device")
1624 if not meta.Attach():
1625 raise errors.BlockDeviceError("Can't attach to meta device")
1626 if not cls._CheckMetaSize(meta.dev_path):
1627 raise errors.BlockDeviceError("Invalid meta device")
1628 logger.Info("Started zeroing device %s" % meta.dev_path)
1629 cls._ZeroDevice(meta.dev_path)
1630 logger.Info("Done zeroing device %s" % meta.dev_path)
1631 return cls(unique_id, children)
1634 """Stub remove for DRBD devices.
1637 return self.Shutdown()
1640 class DRBD8(BaseDRBD):
1641 """DRBD v8.x block device.
1643 This implements the local host part of the DRBD device, i.e. it
1644 doesn't do anything to the supposed peer. If you need a fully
1645 connected DRBD pair, you need to use this class on both hosts.
1647 The unique_id for the drbd device is the (local_ip, local_port,
1648 remote_ip, remote_port) tuple, and it must have two children: the
1649 data device and the meta_device. The meta device is checked for
1650 valid size and is zeroed on create.
1656 def __init__(self, unique_id, children):
1657 if children and children.count(None) > 0:
1659 super(DRBD8, self).__init__(unique_id, children)
1660 self.major = self._DRBD_MAJOR
1661 version = self._GetVersion()
1662 if version['k_major'] != 8 :
1663 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1664 " requested ganeti usage: kernel is"
1665 " %s.%s, ganeti wants 8.x" %
1666 (version['k_major'], version['k_minor']))
1668 if len(children) not in (0, 2):
1669 raise ValueError("Invalid configuration data %s" % str(children))
1670 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1671 raise ValueError("Invalid configuration data %s" % str(unique_id))
1672 self._lhost, self._lport, self._rhost, self._rport = unique_id
1676 def _InitMeta(cls, minor, dev_path):
1677 """Initialize a meta device.
1679 This will not work if the given minor is in use.
1682 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1683 "v08", dev_path, "0", "create-md"])
1685 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1689 def _FindUnusedMinor(cls):
1690 """Find an unused DRBD device.
1692 This is specific to 8.x as the minors are allocated dynamically,
1693 so non-existing numbers up to a max minor count are actually free.
1696 data = cls._GetProcData()
1698 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1699 used_line = re.compile("^ *([0-9]+): cs:")
1702 match = unused_line.match(line)
1704 return int(match.group(1))
1705 match = used_line.match(line)
1707 minor = int(match.group(1))
1708 highest = max(highest, minor)
1709 if highest is None: # there are no minors in use at all
1711 if highest >= cls._MAX_MINORS:
1712 logger.Error("Error: no free drbd minors!")
1713 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1717 def _IsValidMeta(cls, meta_device):
1718 """Check if the given meta device looks like a valid one.
1721 minor = cls._FindUnusedMinor()
1722 minor_path = cls._DevPath(minor)
1723 result = utils.RunCmd(["drbdmeta", minor_path,
1724 "v08", meta_device, "0",
1727 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1732 def _GetShowParser(cls):
1733 """Return a parser for `drbd show` output.
1735 This will either create or return an already-create parser for the
1736 output of the command `drbd show`.
1739 if cls._PARSE_SHOW is not None:
1740 return cls._PARSE_SHOW
1743 lbrace = pyp.Literal("{").suppress()
1744 rbrace = pyp.Literal("}").suppress()
1745 semi = pyp.Literal(";").suppress()
1746 # this also converts the value to an int
1747 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1749 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1750 defa = pyp.Literal("_is_default").suppress()
1751 dbl_quote = pyp.Literal('"').suppress()
1753 keyword = pyp.Word(pyp.alphanums + '-')
1756 value = pyp.Word(pyp.alphanums + '_-/.:')
1757 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1758 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1760 # meta device, extended syntax
1761 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1762 number + pyp.Word(']').suppress())
1765 stmt = (~rbrace + keyword + ~lbrace +
1766 (addr_port ^ value ^ quoted ^ meta_value) +
1767 pyp.Optional(defa) + semi +
1768 pyp.Optional(pyp.restOfLine).suppress())
1771 section_name = pyp.Word(pyp.alphas + '_')
1772 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1774 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1777 cls._PARSE_SHOW = bnf
1782 def _GetShowData(cls, minor):
1783 """Return the `drbdsetup show` data for a minor.
1786 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1788 logger.Error("Can't display the drbd config: %s - %s" %
1789 (result.fail_reason, result.output))
1791 return result.stdout
1794 def _GetDevInfo(cls, out):
1795 """Parse details about a given DRBD minor.
1797 This return, if available, the local backing device (as a path)
1798 and the local and remote (ip, port) information from a string
1799 containing the output of the `drbdsetup show` command as returned
1807 bnf = cls._GetShowParser()
1811 results = bnf.parseString(out)
1812 except pyp.ParseException, err:
1813 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1816 # and massage the results into our desired format
1817 for section in results:
1819 if sname == "_this_host":
1820 for lst in section[1:]:
1821 if lst[0] == "disk":
1822 data["local_dev"] = lst[1]
1823 elif lst[0] == "meta-disk":
1824 data["meta_dev"] = lst[1]
1825 data["meta_index"] = lst[2]
1826 elif lst[0] == "address":
1827 data["local_addr"] = tuple(lst[1:])
1828 elif sname == "_remote_host":
1829 for lst in section[1:]:
1830 if lst[0] == "address":
1831 data["remote_addr"] = tuple(lst[1:])
1834 def _MatchesLocal(self, info):
1835 """Test if our local config matches with an existing device.
1837 The parameter should be as returned from `_GetDevInfo()`. This
1838 method tests if our local backing device is the same as the one in
1839 the info parameter, in effect testing if we look like the given
1844 backend, meta = self._children
1846 backend = meta = None
1848 if backend is not None:
1849 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1851 retval = ("local_dev" not in info)
1853 if meta is not None:
1854 retval = retval and ("meta_dev" in info and
1855 info["meta_dev"] == meta.dev_path)
1856 retval = retval and ("meta_index" in info and
1857 info["meta_index"] == 0)
1859 retval = retval and ("meta_dev" not in info and
1860 "meta_index" not in info)
1863 def _MatchesNet(self, info):
1864 """Test if our network config matches with an existing device.
1866 The parameter should be as returned from `_GetDevInfo()`. This
1867 method tests if our network configuration is the same as the one
1868 in the info parameter, in effect testing if we look like the given
1872 if (((self._lhost is None and not ("local_addr" in info)) and
1873 (self._rhost is None and not ("remote_addr" in info)))):
1876 if self._lhost is None:
1879 if not ("local_addr" in info and
1880 "remote_addr" in info):
1883 retval = (info["local_addr"] == (self._lhost, self._lport))
1884 retval = (retval and
1885 info["remote_addr"] == (self._rhost, self._rport))
1889 def _AssembleLocal(cls, minor, backend, meta):
1890 """Configure the local part of a DRBD device.
1892 This is the first thing that must be done on an unconfigured DRBD
1893 device. And it must be done only once.
1896 if not cls._IsValidMeta(meta):
1898 args = ["drbdsetup", cls._DevPath(minor), "disk",
1899 backend, meta, "0", "-e", "detach", "--create-device"]
1900 result = utils.RunCmd(args)
1902 logger.Error("Can't attach local disk: %s" % result.output)
1903 return not result.failed
1906 def _AssembleNet(cls, minor, net_info, protocol,
1907 dual_pri=False, hmac=None, secret=None):
1908 """Configure the network part of the device.
1911 lhost, lport, rhost, rport = net_info
1912 if None in net_info:
1913 # we don't want network connection and actually want to make
1915 return cls._ShutdownNet(minor)
1917 args = ["drbdsetup", cls._DevPath(minor), "net",
1918 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1919 "-A", "discard-zero-changes",
1926 args.extend(["-a", hmac, "-x", secret])
1927 result = utils.RunCmd(args)
1929 logger.Error("Can't setup network for dbrd device: %s - %s" %
1930 (result.fail_reason, result.output))
1933 timeout = time.time() + 10
1935 while time.time() < timeout:
1936 info = cls._GetDevInfo(cls._GetShowData(minor))
1937 if not "local_addr" in info or not "remote_addr" in info:
1940 if (info["local_addr"] != (lhost, lport) or
1941 info["remote_addr"] != (rhost, rport)):
1947 logger.Error("Timeout while configuring network")
1951 def AddChildren(self, devices):
1952 """Add a disk to the DRBD device.
1955 if self.minor is None:
1956 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1957 if len(devices) != 2:
1958 raise errors.BlockDeviceError("Need two devices for AddChildren")
1959 info = self._GetDevInfo(self._GetShowData(self.minor))
1960 if "local_dev" in info:
1961 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1962 backend, meta = devices
1963 if backend.dev_path is None or meta.dev_path is None:
1964 raise errors.BlockDeviceError("Children not ready during AddChildren")
1967 if not self._CheckMetaSize(meta.dev_path):
1968 raise errors.BlockDeviceError("Invalid meta device size")
1969 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1970 if not self._IsValidMeta(meta.dev_path):
1971 raise errors.BlockDeviceError("Cannot initalize meta device")
1973 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1974 raise errors.BlockDeviceError("Can't attach to local storage")
1975 self._children = devices
1977 def RemoveChildren(self, devices):
1978 """Detach the drbd device from local storage.
1981 if self.minor is None:
1982 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1984 # early return if we don't actually have backing storage
1985 info = self._GetDevInfo(self._GetShowData(self.minor))
1986 if "local_dev" not in info:
1988 if len(self._children) != 2:
1989 raise errors.BlockDeviceError("We don't have two children: %s" %
1991 if self._children.count(None) == 2: # we don't actually have children :)
1992 logger.Error("Requested detach while detached")
1994 if len(devices) != 2:
1995 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1996 for child, dev in zip(self._children, devices):
1997 if dev != child.dev_path:
1998 raise errors.BlockDeviceError("Mismatch in local storage"
1999 " (%s != %s) in RemoveChildren" %
2000 (dev, child.dev_path))
2002 if not self._ShutdownLocal(self.minor):
2003 raise errors.BlockDeviceError("Can't detach from local storage")
2006 def SetSyncSpeed(self, kbytes):
2007 """Set the speed of the DRBD syncer.
2010 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
2011 if self.minor is None:
2012 logger.Info("Instance not attached to a device")
2014 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
2017 logger.Error("Can't change syncer rate: %s - %s" %
2018 (result.fail_reason, result.output))
2019 return not result.failed and children_result
2021 def GetSyncStatus(self):
2022 """Returns the sync status of the device.
2025 (sync_percent, estimated_time, is_degraded)
2027 If sync_percent is None, it means all is ok
2028 If estimated_time is None, it means we can't esimate
2029 the time needed, otherwise it's the time left in seconds.
2032 We set the is_degraded parameter to True on two conditions:
2033 network not connected or local disk missing.
2035 We compute the ldisk parameter based on wheter we have a local
2039 if self.minor is None and not self.Attach():
2040 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
2041 proc_info = self._MassageProcData(self._GetProcData())
2042 if self.minor not in proc_info:
2043 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
2045 line = proc_info[self.minor]
2046 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
2047 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
2049 sync_percent = float(match.group(1))
2050 hours = int(match.group(2))
2051 minutes = int(match.group(3))
2052 seconds = int(match.group(4))
2053 est_time = hours * 3600 + minutes * 60 + seconds
2057 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
2059 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2061 client_state = match.group(1)
2062 local_disk_state = match.group(2)
2063 ldisk = local_disk_state != "UpToDate"
2064 is_degraded = client_state != "Connected"
2065 return sync_percent, est_time, is_degraded or ldisk, ldisk
2067 def GetStatus(self):
2068 """Compute the status of the DRBD device
2070 Note that DRBD devices don't have the STATUS_EXISTING state.
2073 if self.minor is None and not self.Attach():
2074 return self.STATUS_UNKNOWN
2076 data = self._GetProcData()
2077 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2080 mresult = match.match(line)
2084 logger.Error("Can't find myself!")
2085 return self.STATUS_UNKNOWN
2087 state = mresult.group(2)
2088 if state == "Primary":
2089 result = self.STATUS_ONLINE
2091 result = self.STATUS_STANDBY
2095 def Open(self, force=False):
2096 """Make the local state primary.
2098 If the 'force' parameter is given, the '-o' option is passed to
2099 drbdsetup. Since this is a potentially dangerous operation, the
2100 force flag should be only given after creation, when it actually
2104 if self.minor is None and not self.Attach():
2105 logger.Error("DRBD cannot attach to a device during open")
2107 cmd = ["drbdsetup", self.dev_path, "primary"]
2110 result = utils.RunCmd(cmd)
2112 msg = ("Can't make drbd device primary: %s" % result.output)
2114 raise errors.BlockDeviceError(msg)
2117 """Make the local state secondary.
2119 This will, of course, fail if the device is in use.
2122 if self.minor is None and not self.Attach():
2123 logger.Info("Instance not attached to a device")
2124 raise errors.BlockDeviceError("Can't find device")
2125 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2127 msg = ("Can't switch drbd device to"
2128 " secondary: %s" % result.output)
2130 raise errors.BlockDeviceError(msg)
2133 """Find a DRBD device which matches our config and attach to it.
2135 In case of partially attached (local device matches but no network
2136 setup), we perform the network attach. If successful, we re-test
2137 the attach if can return success.
2140 for minor in self._GetUsedDevs():
2141 info = self._GetDevInfo(self._GetShowData(minor))
2142 match_l = self._MatchesLocal(info)
2143 match_r = self._MatchesNet(info)
2144 if match_l and match_r:
2146 if match_l and not match_r and "local_addr" not in info:
2147 res_r = self._AssembleNet(minor,
2148 (self._lhost, self._lport,
2149 self._rhost, self._rport),
2152 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2154 # the weakest case: we find something that is only net attached
2155 # even though we were passed some children at init time
2156 if match_r and "local_dev" not in info:
2159 # this case must be considered only if we actually have local
2160 # storage, i.e. not in diskless mode, because all diskless
2161 # devices are equal from the point of view of local
2163 if (match_l and "local_dev" in info and
2164 not match_r and "local_addr" in info):
2165 # strange case - the device network part points to somewhere
2166 # else, even though its local storage is ours; as we own the
2167 # drbd space, we try to disconnect from the remote peer and
2168 # reconnect to our correct one
2169 if not self._ShutdownNet(minor):
2170 raise errors.BlockDeviceError("Device has correct local storage,"
2171 " wrong remote peer and is unable to"
2172 " disconnect in order to attach to"
2173 " the correct peer")
2174 # note: _AssembleNet also handles the case when we don't want
2175 # local storage (i.e. one or more of the _[lr](host|port) is
2177 if (self._AssembleNet(minor, (self._lhost, self._lport,
2178 self._rhost, self._rport), "C") and
2179 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2185 self._SetFromMinor(minor)
2186 return minor is not None
2189 """Assemble the drbd.
2192 - if we have a local backing device, we bind to it by:
2193 - checking the list of used drbd devices
2194 - check if the local minor use of any of them is our own device
2197 - if we have a local/remote net info:
2198 - redo the local backing device step for the remote device
2199 - check if any drbd device is using the local port,
2201 - check if any remote drbd device is using the remote
2202 port, if yes abort (for now)
2204 - bind the remote net port
2208 if self.minor is not None:
2209 logger.Info("Already assembled")
2212 result = super(DRBD8, self).Assemble()
2216 minor = self._FindUnusedMinor()
2217 need_localdev_teardown = False
2218 if self._children and self._children[0] and self._children[1]:
2219 result = self._AssembleLocal(minor, self._children[0].dev_path,
2220 self._children[1].dev_path)
2223 need_localdev_teardown = True
2224 if self._lhost and self._lport and self._rhost and self._rport:
2225 result = self._AssembleNet(minor,
2226 (self._lhost, self._lport,
2227 self._rhost, self._rport),
2230 if need_localdev_teardown:
2231 # we will ignore failures from this
2232 logger.Error("net setup failed, tearing down local device")
2233 self._ShutdownAll(minor)
2235 self._SetFromMinor(minor)
2239 def _ShutdownLocal(cls, minor):
2240 """Detach from the local device.
2242 I/Os will continue to be served from the remote device. If we
2243 don't have a remote device, this operation will fail.
2246 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2248 logger.Error("Can't detach local device: %s" % result.output)
2249 return not result.failed
2252 def _ShutdownNet(cls, minor):
2253 """Disconnect from the remote peer.
2255 This fails if we don't have a local device.
2258 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2260 logger.Error("Can't shutdown network: %s" % result.output)
2261 return not result.failed
2264 def _ShutdownAll(cls, minor):
2265 """Deactivate the device.
2267 This will, of course, fail if the device is in use.
2270 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2272 logger.Error("Can't shutdown drbd device: %s" % result.output)
2273 return not result.failed
2276 """Shutdown the DRBD device.
2279 if self.minor is None and not self.Attach():
2280 logger.Info("DRBD device not attached to a device during Shutdown")
2282 if not self._ShutdownAll(self.minor):
2285 self.dev_path = None
2289 """Stub remove for DRBD devices.
2292 return self.Shutdown()
2295 def Create(cls, unique_id, children, size):
2296 """Create a new DRBD8 device.
2298 Since DRBD devices are not created per se, just assembled, this
2299 function only initializes the metadata.
2302 if len(children) != 2:
2303 raise errors.ProgrammerError("Invalid setup for the drbd device")
2306 if not meta.Attach():
2307 raise errors.BlockDeviceError("Can't attach to meta device")
2308 if not cls._CheckMetaSize(meta.dev_path):
2309 raise errors.BlockDeviceError("Invalid meta device size")
2310 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2311 if not cls._IsValidMeta(meta.dev_path):
2312 raise errors.BlockDeviceError("Cannot initalize meta device")
2313 return cls(unique_id, children)
2317 constants.LD_LV: LogicalVolume,
2318 constants.LD_MD_R1: MDRaid1,
2319 constants.LD_DRBD7: DRBDev,
2320 constants.LD_DRBD8: DRBD8,
2324 def FindDevice(dev_type, unique_id, children):
2325 """Search for an existing, assembled device.
2327 This will succeed only if the device exists and is assembled, but it
2328 does not do any actions in order to activate the device.
2331 if dev_type not in DEV_MAP:
2332 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2333 device = DEV_MAP[dev_type](unique_id, children)
2334 if not device.Attach():
2339 def AttachOrAssemble(dev_type, unique_id, children):
2340 """Try to attach or assemble an existing device.
2342 This will attach to an existing assembled device or will assemble
2343 the device, as needed, to bring it fully up.
2346 if dev_type not in DEV_MAP:
2347 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2348 device = DEV_MAP[dev_type](unique_id, children)
2349 if not device.Attach():
2351 if not device.Attach():
2352 raise errors.BlockDeviceError("Can't find a valid block device for"
2354 (dev_type, unique_id, children))
2358 def Create(dev_type, unique_id, children, size):
2362 if dev_type not in DEV_MAP:
2363 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2364 device = DEV_MAP[dev_type].Create(unique_id, children, size)