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(result.fail_reason)
333 return LogicalVolume(unique_id, children)
336 def GetPVInfo(vg_name):
337 """Get the free space info for PVs in a volume group.
340 vg_name: the volume group name
343 list of (free_space, name) with free_space in mebibytes
346 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
347 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
349 result = utils.RunCmd(command)
351 logger.Error("Can't get the PV information: %s" % result.fail_reason)
354 for line in result.stdout.splitlines():
355 fields = line.strip().split(':')
357 logger.Error("Can't parse pvs output: line '%s'" % line)
359 # skip over pvs from another vg or ones which are not allocatable
360 if fields[1] != vg_name or fields[3][0] != 'a':
362 data.append((float(fields[2]), fields[0]))
367 """Remove this logical volume.
370 if not self.minor and not self.Attach():
371 # the LV does not exist
373 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
374 (self._vg_name, self._lv_name)])
376 logger.Error("Can't lvremove: %s" % result.fail_reason)
378 return not result.failed
380 def Rename(self, new_id):
381 """Rename this logical volume.
384 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
385 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
386 new_vg, new_name = new_id
387 if new_vg != self._vg_name:
388 raise errors.ProgrammerError("Can't move a logical volume across"
389 " volume groups (from %s to to %s)" %
390 (self._vg_name, new_vg))
391 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
393 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
395 self._lv_name = new_name
396 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
400 """Attach to an existing LV.
402 This method will try to see if an existing and active LV exists
403 which matches the our name. If so, its major/minor will be
407 result = utils.RunCmd(["lvdisplay", self.dev_path])
409 logger.Error("Can't find LV %s: %s, %s" %
410 (self.dev_path, result.fail_reason, result.output))
412 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
413 for line in result.stdout.splitlines():
414 match_result = match.match(line)
416 self.major = int(match_result.group(1))
417 self.minor = int(match_result.group(2))
422 """Assemble the device.
424 We alway run `lvchange -ay` on the LV to ensure it's active before
425 use, as there were cases when xenvg was not active after boot
426 (also possibly after disk issues).
429 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
431 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
432 return not result.failed
435 """Shutdown the device.
437 This is a no-op for the LV device type, as we don't deactivate the
444 """Return the status of the device.
446 Logical volumes will can be in all four states, although we don't
447 deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
448 should not be seen for our devices.
451 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
453 logger.Error("Can't display lv: %s" % result.fail_reason)
454 return self.STATUS_UNKNOWN
455 out = result.stdout.strip()
456 # format: type/permissions/alloc/fixed_minor/state/open
458 return self.STATUS_UNKNOWN
459 #writable = (out[1] == "w")
460 active = (out[4] == "a")
461 online = (out[5] == "o")
463 retval = self.STATUS_ONLINE
465 retval = self.STATUS_STANDBY
467 retval = self.STATUS_EXISTING
471 def GetSyncStatus(self):
472 """Returns the sync status of the device.
474 If this device is a mirroring device, this function returns the
475 status of the mirror.
478 (sync_percent, estimated_time, is_degraded, ldisk)
480 For logical volumes, sync_percent and estimated_time are always
481 None (no recovery in progress, as we don't handle the mirrored LV
482 case). The is_degraded parameter is the inverse of the ldisk
485 For the ldisk parameter, we check if the logical volume has the
486 'virtual' type, which means it's not backed by existing storage
487 anymore (read from it return I/O error). This happens after a
488 physical disk failure and subsequent 'vgreduce --removemissing' on
492 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
494 logger.Error("Can't display lv: %s" % result.fail_reason)
495 return None, None, True, True
496 out = result.stdout.strip()
497 # format: type/permissions/alloc/fixed_minor/state/open
499 logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
500 return None, None, True, True
501 ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
503 return None, None, ldisk, ldisk
505 def Open(self, force=False):
506 """Make the device ready for I/O.
508 This is a no-op for the LV device type.
514 """Notifies that the device will no longer be used for I/O.
516 This is a no-op for the LV device type.
521 def Snapshot(self, size):
522 """Create a snapshot copy of an lvm block device.
525 snap_name = self._lv_name + ".snap"
527 # remove existing snapshot if found
528 snap = LogicalVolume((self._vg_name, snap_name), None)
531 pvs_info = self.GetPVInfo(self._vg_name)
533 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
537 free_size, pv_name = pvs_info[0]
539 raise errors.BlockDeviceError("Not enough free space: required %s,"
540 " available %s" % (size, free_size))
542 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
543 "-n%s" % snap_name, self.dev_path])
545 raise errors.BlockDeviceError("command: %s error: %s" %
546 (result.cmd, result.fail_reason))
550 def SetInfo(self, text):
551 """Update metadata with info text.
554 BlockDev.SetInfo(self, text)
556 # Replace invalid characters
557 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
558 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
560 # Only up to 128 characters are allowed
563 result = utils.RunCmd(["lvchange", "--addtag", text,
566 raise errors.BlockDeviceError("Command: %s error: %s" %
567 (result.cmd, result.fail_reason))
570 class MDRaid1(BlockDev):
571 """raid1 device implemented via md.
574 def __init__(self, unique_id, children):
575 super(MDRaid1, self).__init__(unique_id, children)
580 """Find an array which matches our config and attach to it.
582 This tries to find a MD array which has the same UUID as our own.
585 minor = self._FindMDByUUID(self.unique_id)
586 if minor is not None:
587 self._SetFromMinor(minor)
592 return (minor is not None)
596 """Compute the list of in-use MD devices.
598 It doesn't matter if the used device have other raid level, just
599 that they are in use.
602 mdstat = open("/proc/mdstat", "r")
603 data = mdstat.readlines()
607 valid_line = re.compile("^md([0-9]+) : .*$")
609 match = valid_line.match(line)
611 md_no = int(match.group(1))
612 used_md[md_no] = line
617 def _GetDevInfo(minor):
618 """Get info about a MD device.
620 Currently only uuid is returned.
623 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
625 logger.Error("Can't display md: %s" % result.fail_reason)
628 for line in result.stdout.splitlines():
630 kv = line.split(" : ", 1)
633 retval["uuid"] = kv[1].split()[0]
634 elif kv[0] == "State":
635 retval["state"] = kv[1].split(", ")
639 def _FindUnusedMinor():
640 """Compute an unused MD minor.
642 This code assumes that there are 256 minors only.
645 used_md = MDRaid1._GetUsedDevs()
652 logger.Error("Critical: Out of md minor numbers.")
653 raise errors.BlockDeviceError("Can't find a free MD minor")
657 def _FindMDByUUID(cls, uuid):
658 """Find the minor of an MD array with a given UUID.
661 md_list = cls._GetUsedDevs()
662 for minor in md_list:
663 info = cls._GetDevInfo(minor)
664 if info and info["uuid"] == uuid:
669 def _ZeroSuperblock(dev_path):
670 """Zero the possible locations for an MD superblock.
672 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
673 fails in versions 2.x with the same error code as non-writable
676 The superblocks are located at (negative values are relative to
677 the end of the block device):
678 - -128k to end for version 0.90 superblock
679 - -8k to -12k for version 1.0 superblock (included in the above)
680 - 0k to 4k for version 1.1 superblock
681 - 4k to 8k for version 1.2 superblock
683 To cover all situations, the zero-ing will be:
687 As such, the minimum device size must be 128k, otherwise we'll get
690 Note that this function depends on the fact that one can open,
691 read and write block devices normally.
694 overwrite_size = 128 * 1024
695 empty_buf = '\0' * overwrite_size
696 fd = open(dev_path, "r+")
702 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
703 fd.seek(-overwrite_size, 2)
707 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
712 def Create(cls, unique_id, children, size):
713 """Create a new MD raid1 array.
716 if not isinstance(children, (tuple, list)):
717 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
720 if not isinstance(i, BlockDev):
721 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
724 cls._ZeroSuperblock(i.dev_path)
725 except EnvironmentError, err:
726 logger.Error("Can't zero superblock for %s: %s" %
727 (i.dev_path, str(err)))
729 minor = cls._FindUnusedMinor()
730 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
731 "--auto=yes", "--force", "-l1",
732 "-n%d" % len(children)] +
733 [dev.dev_path for dev in children])
736 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
739 info = cls._GetDevInfo(minor)
740 if not info or not "uuid" in info:
741 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
743 return MDRaid1(info["uuid"], children)
746 """Stub remove function for MD RAID 1 arrays.
748 We don't remove the superblock right now. Mark a to do.
751 #TODO: maybe zero superblock on child devices?
752 return self.Shutdown()
754 def Rename(self, new_id):
757 This is not supported for md raid1 devices.
760 raise errors.ProgrammerError("Can't rename a md raid1 device")
762 def AddChildren(self, devices):
763 """Add new member(s) to the md raid1.
766 if self.minor is None and not self.Attach():
767 raise errors.BlockDeviceError("Can't attach to device")
769 args = ["mdadm", "-a", self.dev_path]
771 if dev.dev_path is None:
772 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
774 args.append(dev.dev_path)
775 result = utils.RunCmd(args)
777 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
779 new_len = len(self._children) + len(devices)
780 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
782 raise errors.BlockDeviceError("Can't grow md array: %s" %
784 self._children.extend(devices)
786 def RemoveChildren(self, devices):
787 """Remove member(s) from the md raid1.
790 if self.minor is None and not self.Attach():
791 raise errors.BlockDeviceError("Can't attach to device")
792 new_len = len(self._children) - len(devices)
794 raise errors.BlockDeviceError("Can't reduce to less than one child")
795 args = ["mdadm", "-f", self.dev_path]
799 for c in self._children:
800 if c.dev_path == dev:
804 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
806 result = utils.RunCmd(args)
808 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
811 # it seems here we need a short delay for MD to update its
815 result = utils.RunCmd(args)
817 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
818 " %s" % result.output)
819 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
822 raise errors.BlockDeviceError("Can't shrink md array: %s" %
824 for dev in orig_devs:
825 self._children.remove(dev)
828 """Return the status of the device.
832 if self.minor is None:
833 retval = self.STATUS_UNKNOWN
835 retval = self.STATUS_ONLINE
838 def _SetFromMinor(self, minor):
839 """Set our parameters based on the given minor.
841 This sets our minor variable and our dev_path.
845 self.dev_path = "/dev/md%d" % minor
848 """Assemble the MD device.
850 At this point we should have:
851 - list of children devices
855 result = super(MDRaid1, self).Assemble()
858 md_list = self._GetUsedDevs()
859 for minor in md_list:
860 info = self._GetDevInfo(minor)
861 if info and info["uuid"] == self.unique_id:
862 self._SetFromMinor(minor)
863 logger.Info("MD array %s already started" % str(self))
865 free_minor = self._FindUnusedMinor()
866 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
867 self.unique_id, "/dev/md%d" % free_minor] +
868 [bdev.dev_path for bdev in self._children])
870 logger.Error("Can't assemble MD array: %s: %s" %
871 (result.fail_reason, result.output))
874 self.minor = free_minor
875 return not result.failed
878 """Tear down the MD array.
880 This does a 'mdadm --stop' so after this command, the array is no
884 if self.minor is None and not self.Attach():
885 logger.Info("MD object not attached to a device")
888 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
890 logger.Error("Can't stop MD array: %s" % result.fail_reason)
896 def SetSyncSpeed(self, kbytes):
897 """Set the maximum sync speed for the MD array.
900 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
901 if self.minor is None:
902 logger.Error("MD array not attached to a device")
904 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
906 f.write("%d" % kbytes)
909 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
911 f.write("%d" % (kbytes/2))
916 def GetSyncStatus(self):
917 """Returns the sync status of the device.
920 (sync_percent, estimated_time, is_degraded, ldisk)
922 If sync_percent is None, it means all is ok
923 If estimated_time is None, it means we can't esimate
924 the time needed, otherwise it's the time left in seconds.
926 The ldisk parameter is always true for MD devices.
929 if self.minor is None and not self.Attach():
930 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
931 dev_info = self._GetDevInfo(self.minor)
932 is_clean = ("state" in dev_info and
933 len(dev_info["state"]) == 1 and
934 dev_info["state"][0] in ("clean", "active"))
935 sys_path = "/sys/block/md%s/md/" % self.minor
936 f = file(sys_path + "sync_action")
937 sync_status = f.readline().strip()
939 if sync_status == "idle":
940 return None, None, not is_clean, False
941 f = file(sys_path + "sync_completed")
942 sync_completed = f.readline().strip().split(" / ")
944 if len(sync_completed) != 2:
945 return 0, None, not is_clean, False
946 sync_done, sync_total = [float(i) for i in sync_completed]
947 sync_percent = 100.0*sync_done/sync_total
948 f = file(sys_path + "sync_speed")
949 sync_speed_k = int(f.readline().strip())
950 if sync_speed_k == 0:
953 time_est = (sync_total - sync_done) / 2 / sync_speed_k
954 return sync_percent, time_est, not is_clean, False
956 def Open(self, force=False):
957 """Make the device ready for I/O.
959 This is a no-op for the MDRaid1 device type, although we could use
960 the 2.6.18's new array_state thing.
966 """Notifies that the device will no longer be used for I/O.
968 This is a no-op for the MDRaid1 device type, but see comment for
975 class BaseDRBD(BlockDev):
978 This class contains a few bits of common functionality between the
979 0.7 and 8.x versions of DRBD.
982 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
983 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
986 _ST_UNCONFIGURED = "Unconfigured"
987 _ST_WFCONNECTION = "WFConnection"
988 _ST_CONNECTED = "Connected"
992 """Return data from /proc/drbd.
995 stat = open("/proc/drbd", "r")
997 data = stat.read().splitlines()
1001 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
1005 def _MassageProcData(data):
1006 """Transform the output of _GetProdData into a nicer form.
1009 a dictionary of minor: joined lines from /proc/drbd for that minor
1012 lmatch = re.compile("^ *([0-9]+):.*$")
1014 old_minor = old_line = None
1016 lresult = lmatch.match(line)
1017 if lresult is not None:
1018 if old_minor is not None:
1019 results[old_minor] = old_line
1020 old_minor = int(lresult.group(1))
1023 if old_minor is not None:
1024 old_line += " " + line.strip()
1026 if old_minor is not None:
1027 results[old_minor] = old_line
1031 def _GetVersion(cls):
1032 """Return the DRBD version.
1034 This will return a dict with keys:
1040 proto2 (only on drbd > 8.2.X)
1043 proc_data = cls._GetProcData()
1044 first_line = proc_data[0].strip()
1045 version = cls._VERSION_RE.match(first_line)
1047 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1050 values = version.groups()
1051 retval = {'k_major': int(values[0]),
1052 'k_minor': int(values[1]),
1053 'k_point': int(values[2]),
1054 'api': int(values[3]),
1055 'proto': int(values[4]),
1057 if values[5] is not None:
1058 retval['proto2'] = values[5]
1063 def _DevPath(minor):
1064 """Return the path to a drbd device for a given minor.
1067 return "/dev/drbd%d" % minor
1070 def _GetUsedDevs(cls):
1071 """Compute the list of used DRBD devices.
1074 data = cls._GetProcData()
1077 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1079 match = valid_line.match(line)
1082 minor = int(match.group(1))
1083 state = match.group(2)
1084 if state == cls._ST_UNCONFIGURED:
1086 used_devs[minor] = state, line
1090 def _SetFromMinor(self, minor):
1091 """Set our parameters based on the given minor.
1093 This sets our minor variable and our dev_path.
1097 self.minor = self.dev_path = None
1100 self.dev_path = self._DevPath(minor)
1103 def _CheckMetaSize(meta_device):
1104 """Check if the given meta device looks like a valid one.
1106 This currently only check the size, which must be around
1110 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1112 logger.Error("Failed to get device size: %s" % result.fail_reason)
1115 sectors = int(result.stdout)
1117 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1119 bytes = sectors * 512
1120 if bytes < 128 * 1024 * 1024: # less than 128MiB
1121 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1123 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1124 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1128 def Rename(self, new_id):
1131 This is not supported for drbd devices.
1134 raise errors.ProgrammerError("Can't rename a drbd device")
1137 class DRBDev(BaseDRBD):
1138 """DRBD block device.
1140 This implements the local host part of the DRBD device, i.e. it
1141 doesn't do anything to the supposed peer. If you need a fully
1142 connected DRBD pair, you need to use this class on both hosts.
1144 The unique_id for the drbd device is the (local_ip, local_port,
1145 remote_ip, remote_port) tuple, and it must have two children: the
1146 data device and the meta_device. The meta device is checked for
1147 valid size and is zeroed on create.
1150 def __init__(self, unique_id, children):
1151 super(DRBDev, self).__init__(unique_id, children)
1152 self.major = self._DRBD_MAJOR
1153 version = self._GetVersion()
1154 if version['k_major'] != 0 and version['k_minor'] != 7:
1155 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1156 " requested ganeti usage: kernel is"
1157 " %s.%s, ganeti wants 0.7" %
1158 (version['k_major'], version['k_minor']))
1159 if len(children) != 2:
1160 raise ValueError("Invalid configuration data %s" % str(children))
1161 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1162 raise ValueError("Invalid configuration data %s" % str(unique_id))
1163 self._lhost, self._lport, self._rhost, self._rport = unique_id
1167 def _FindUnusedMinor(cls):
1168 """Find an unused DRBD device.
1171 data = cls._GetProcData()
1173 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1175 match = valid_line.match(line)
1177 return int(match.group(1))
1178 logger.Error("Error: no free drbd minors!")
1179 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1182 def _GetDevInfo(cls, minor):
1183 """Get details about a given DRBD minor.
1185 This return, if available, the local backing device in (major,
1186 minor) formant and the local and remote (ip, port) information.
1190 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1192 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1195 if out == "Not configured\n":
1197 for line in out.splitlines():
1198 if "local_dev" not in data:
1199 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1201 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1203 if "meta_dev" not in data:
1204 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1206 if match.group(2) is not None and match.group(3) is not None:
1207 # matched on the major/minor
1208 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1210 # matched on the "internal" string
1211 data["meta_dev"] = match.group(1)
1212 # in this case, no meta_index is in the output
1213 data["meta_index"] = -1
1215 if "meta_index" not in data:
1216 match = re.match("^Meta index: ([0-9]+).*$", line)
1218 data["meta_index"] = int(match.group(1))
1220 if "local_addr" not in data:
1221 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1223 data["local_addr"] = (match.group(1), int(match.group(2)))
1225 if "remote_addr" not in data:
1226 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1228 data["remote_addr"] = (match.group(1), int(match.group(2)))
1232 def _MatchesLocal(self, info):
1233 """Test if our local config matches with an existing device.
1235 The parameter should be as returned from `_GetDevInfo()`. This
1236 method tests if our local backing device is the same as the one in
1237 the info parameter, in effect testing if we look like the given
1241 if not ("local_dev" in info and "meta_dev" in info and
1242 "meta_index" in info):
1245 backend = self._children[0]
1246 if backend is not None:
1247 retval = (info["local_dev"] == (backend.major, backend.minor))
1249 retval = (info["local_dev"] == (0, 0))
1250 meta = self._children[1]
1251 if meta is not None:
1252 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1253 retval = retval and (info["meta_index"] == 0)
1255 retval = retval and (info["meta_dev"] == "internal" and
1256 info["meta_index"] == -1)
1259 def _MatchesNet(self, info):
1260 """Test if our network config matches with an existing device.
1262 The parameter should be as returned from `_GetDevInfo()`. This
1263 method tests if our network configuration is the same as the one
1264 in the info parameter, in effect testing if we look like the given
1268 if (((self._lhost is None and not ("local_addr" in info)) and
1269 (self._rhost is None and not ("remote_addr" in info)))):
1272 if self._lhost is None:
1275 if not ("local_addr" in info and
1276 "remote_addr" in info):
1279 retval = (info["local_addr"] == (self._lhost, self._lport))
1280 retval = (retval and
1281 info["remote_addr"] == (self._rhost, self._rport))
1285 def _AssembleLocal(cls, minor, backend, meta):
1286 """Configure the local part of a DRBD device.
1288 This is the first thing that must be done on an unconfigured DRBD
1289 device. And it must be done only once.
1292 if not cls._CheckMetaSize(meta):
1294 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1295 backend, meta, "0", "-e", "detach"])
1297 logger.Error("Can't attach local disk: %s" % result.output)
1298 return not result.failed
1301 def _ShutdownLocal(cls, minor):
1302 """Detach from the local device.
1304 I/Os will continue to be served from the remote device. If we
1305 don't have a remote device, this operation will fail.
1308 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1310 logger.Error("Can't detach local device: %s" % result.output)
1311 return not result.failed
1314 def _ShutdownAll(minor):
1315 """Deactivate the device.
1317 This will, of course, fail if the device is in use.
1320 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1322 logger.Error("Can't shutdown drbd device: %s" % result.output)
1323 return not result.failed
1326 def _AssembleNet(cls, minor, net_info, protocol):
1327 """Configure the network part of the device.
1329 This operation can be, in theory, done multiple times, but there
1330 have been cases (in lab testing) in which the network part of the
1331 device had become stuck and couldn't be shut down because activity
1332 from the new peer (also stuck) triggered a timer re-init and
1333 needed remote peer interface shutdown in order to clear. So please
1334 don't change online the net config.
1337 lhost, lport, rhost, rport = net_info
1338 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1339 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1342 logger.Error("Can't setup network for dbrd device: %s" %
1346 timeout = time.time() + 10
1348 while time.time() < timeout:
1349 info = cls._GetDevInfo(minor)
1350 if not "local_addr" in info or not "remote_addr" in info:
1353 if (info["local_addr"] != (lhost, lport) or
1354 info["remote_addr"] != (rhost, rport)):
1360 logger.Error("Timeout while configuring network")
1365 def _ShutdownNet(cls, minor):
1366 """Disconnect from the remote peer.
1368 This fails if we don't have a local device.
1371 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1373 logger.Error("Can't shutdown network: %s" % result.output)
1374 return not result.failed
1377 """Assemble the drbd.
1380 - if we have a local backing device, we bind to it by:
1381 - checking the list of used drbd devices
1382 - check if the local minor use of any of them is our own device
1385 - if we have a local/remote net info:
1386 - redo the local backing device step for the remote device
1387 - check if any drbd device is using the local port,
1389 - check if any remote drbd device is using the remote
1390 port, if yes abort (for now)
1392 - bind the remote net port
1396 if self.minor is not None:
1397 logger.Info("Already assembled")
1400 result = super(DRBDev, self).Assemble()
1404 minor = self._FindUnusedMinor()
1405 need_localdev_teardown = False
1406 if self._children[0]:
1407 result = self._AssembleLocal(minor, self._children[0].dev_path,
1408 self._children[1].dev_path)
1411 need_localdev_teardown = True
1412 if self._lhost and self._lport and self._rhost and self._rport:
1413 result = self._AssembleNet(minor,
1414 (self._lhost, self._lport,
1415 self._rhost, self._rport),
1418 if need_localdev_teardown:
1419 # we will ignore failures from this
1420 logger.Error("net setup failed, tearing down local device")
1421 self._ShutdownAll(minor)
1423 self._SetFromMinor(minor)
1427 """Shutdown the DRBD device.
1430 if self.minor is None and not self.Attach():
1431 logger.Info("DRBD device not attached to a device during Shutdown")
1433 if not self._ShutdownAll(self.minor):
1436 self.dev_path = None
1440 """Find a DRBD device which matches our config and attach to it.
1442 In case of partially attached (local device matches but no network
1443 setup), we perform the network attach. If successful, we re-test
1444 the attach if can return success.
1447 for minor in self._GetUsedDevs():
1448 info = self._GetDevInfo(minor)
1449 match_l = self._MatchesLocal(info)
1450 match_r = self._MatchesNet(info)
1451 if match_l and match_r:
1453 if match_l and not match_r and "local_addr" not in info:
1454 res_r = self._AssembleNet(minor,
1455 (self._lhost, self._lport,
1456 self._rhost, self._rport),
1458 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1463 self._SetFromMinor(minor)
1464 return minor is not None
1466 def Open(self, force=False):
1467 """Make the local state primary.
1469 If the 'force' parameter is given, the '--do-what-I-say' parameter
1470 is given. Since this is a pottentialy dangerous operation, the
1471 force flag should be only given after creation, when it actually
1475 if self.minor is None and not self.Attach():
1476 logger.Error("DRBD cannot attach to a device during open")
1478 cmd = ["drbdsetup", self.dev_path, "primary"]
1480 cmd.append("--do-what-I-say")
1481 result = utils.RunCmd(cmd)
1483 msg = ("Can't make drbd device primary: %s" % result.output)
1485 raise errors.BlockDeviceError(msg)
1488 """Make the local state secondary.
1490 This will, of course, fail if the device is in use.
1493 if self.minor is None and not self.Attach():
1494 logger.Info("Instance not attached to a device")
1495 raise errors.BlockDeviceError("Can't find device")
1496 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1498 msg = ("Can't switch drbd device to"
1499 " secondary: %s" % result.output)
1501 raise errors.BlockDeviceError(msg)
1503 def SetSyncSpeed(self, kbytes):
1504 """Set the speed of the DRBD syncer.
1507 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1508 if self.minor is None:
1509 logger.Info("Instance not attached to a device")
1511 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1514 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1515 return not result.failed and children_result
1517 def GetSyncStatus(self):
1518 """Returns the sync status of the device.
1521 (sync_percent, estimated_time, is_degraded, ldisk)
1523 If sync_percent is None, it means all is ok
1524 If estimated_time is None, it means we can't esimate
1525 the time needed, otherwise it's the time left in seconds.
1527 The ldisk parameter will be returned as True, since the DRBD7
1528 devices have not been converted.
1531 if self.minor is None and not self.Attach():
1532 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1533 proc_info = self._MassageProcData(self._GetProcData())
1534 if self.minor not in proc_info:
1535 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1537 line = proc_info[self.minor]
1538 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1539 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1541 sync_percent = float(match.group(1))
1542 hours = int(match.group(2))
1543 minutes = int(match.group(3))
1544 seconds = int(match.group(4))
1545 est_time = hours * 3600 + minutes * 60 + seconds
1549 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1551 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1553 client_state = match.group(1)
1554 is_degraded = client_state != "Connected"
1555 return sync_percent, est_time, is_degraded, False
1557 def GetStatus(self):
1558 """Compute the status of the DRBD device
1560 Note that DRBD devices don't have the STATUS_EXISTING state.
1563 if self.minor is None and not self.Attach():
1564 return self.STATUS_UNKNOWN
1566 data = self._GetProcData()
1567 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1570 mresult = match.match(line)
1574 logger.Error("Can't find myself!")
1575 return self.STATUS_UNKNOWN
1577 state = mresult.group(2)
1578 if state == "Primary":
1579 result = self.STATUS_ONLINE
1581 result = self.STATUS_STANDBY
1586 def _ZeroDevice(device):
1589 This writes until we get ENOSPC.
1592 f = open(device, "w")
1593 buf = "\0" * 1048576
1597 except IOError, err:
1598 if err.errno != errno.ENOSPC:
1602 def Create(cls, unique_id, children, size):
1603 """Create a new DRBD device.
1605 Since DRBD devices are not created per se, just assembled, this
1606 function just zeroes the meta device.
1609 if len(children) != 2:
1610 raise errors.ProgrammerError("Invalid setup for the drbd device")
1613 if not meta.Attach():
1614 raise errors.BlockDeviceError("Can't attach to meta device")
1615 if not cls._CheckMetaSize(meta.dev_path):
1616 raise errors.BlockDeviceError("Invalid meta device")
1617 logger.Info("Started zeroing device %s" % meta.dev_path)
1618 cls._ZeroDevice(meta.dev_path)
1619 logger.Info("Done zeroing device %s" % meta.dev_path)
1620 return cls(unique_id, children)
1623 """Stub remove for DRBD devices.
1626 return self.Shutdown()
1629 class DRBD8(BaseDRBD):
1630 """DRBD v8.x block device.
1632 This implements the local host part of the DRBD device, i.e. it
1633 doesn't do anything to the supposed peer. If you need a fully
1634 connected DRBD pair, you need to use this class on both hosts.
1636 The unique_id for the drbd device is the (local_ip, local_port,
1637 remote_ip, remote_port) tuple, and it must have two children: the
1638 data device and the meta_device. The meta device is checked for
1639 valid size and is zeroed on create.
1645 def __init__(self, unique_id, children):
1646 if children and children.count(None) > 0:
1648 super(DRBD8, self).__init__(unique_id, children)
1649 self.major = self._DRBD_MAJOR
1650 version = self._GetVersion()
1651 if version['k_major'] != 8 :
1652 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1653 " requested ganeti usage: kernel is"
1654 " %s.%s, ganeti wants 8.x" %
1655 (version['k_major'], version['k_minor']))
1657 if len(children) not in (0, 2):
1658 raise ValueError("Invalid configuration data %s" % str(children))
1659 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1660 raise ValueError("Invalid configuration data %s" % str(unique_id))
1661 self._lhost, self._lport, self._rhost, self._rport = unique_id
1665 def _InitMeta(cls, minor, dev_path):
1666 """Initialize a meta device.
1668 This will not work if the given minor is in use.
1671 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1672 "v08", dev_path, "0", "create-md"])
1674 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1678 def _FindUnusedMinor(cls):
1679 """Find an unused DRBD device.
1681 This is specific to 8.x as the minors are allocated dynamically,
1682 so non-existing numbers up to a max minor count are actually free.
1685 data = cls._GetProcData()
1687 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1688 used_line = re.compile("^ *([0-9]+): cs:")
1691 match = unused_line.match(line)
1693 return int(match.group(1))
1694 match = used_line.match(line)
1696 minor = int(match.group(1))
1697 highest = max(highest, minor)
1698 if highest is None: # there are no minors in use at all
1700 if highest >= cls._MAX_MINORS:
1701 logger.Error("Error: no free drbd minors!")
1702 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1706 def _IsValidMeta(cls, meta_device):
1707 """Check if the given meta device looks like a valid one.
1710 minor = cls._FindUnusedMinor()
1711 minor_path = cls._DevPath(minor)
1712 result = utils.RunCmd(["drbdmeta", minor_path,
1713 "v08", meta_device, "0",
1716 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1721 def _GetShowParser(cls):
1722 """Return a parser for `drbd show` output.
1724 This will either create or return an already-create parser for the
1725 output of the command `drbd show`.
1728 if cls._PARSE_SHOW is not None:
1729 return cls._PARSE_SHOW
1732 lbrace = pyp.Literal("{").suppress()
1733 rbrace = pyp.Literal("}").suppress()
1734 semi = pyp.Literal(";").suppress()
1735 # this also converts the value to an int
1736 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1738 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1739 defa = pyp.Literal("_is_default").suppress()
1740 dbl_quote = pyp.Literal('"').suppress()
1742 keyword = pyp.Word(pyp.alphanums + '-')
1745 value = pyp.Word(pyp.alphanums + '_-/.:')
1746 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1747 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1749 # meta device, extended syntax
1750 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1751 number + pyp.Word(']').suppress())
1754 stmt = (~rbrace + keyword + ~lbrace +
1755 (addr_port ^ value ^ quoted ^ meta_value) +
1756 pyp.Optional(defa) + semi +
1757 pyp.Optional(pyp.restOfLine).suppress())
1760 section_name = pyp.Word(pyp.alphas + '_')
1761 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1763 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1766 cls._PARSE_SHOW = bnf
1771 def _GetShowData(cls, minor):
1772 """Return the `drbdsetup show` data for a minor.
1775 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1777 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1779 return result.stdout
1782 def _GetDevInfo(cls, out):
1783 """Parse details about a given DRBD minor.
1785 This return, if available, the local backing device (as a path)
1786 and the local and remote (ip, port) information from a string
1787 containing the output of the `drbdsetup show` command as returned
1795 bnf = cls._GetShowParser()
1799 results = bnf.parseString(out)
1800 except pyp.ParseException, err:
1801 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1804 # and massage the results into our desired format
1805 for section in results:
1807 if sname == "_this_host":
1808 for lst in section[1:]:
1809 if lst[0] == "disk":
1810 data["local_dev"] = lst[1]
1811 elif lst[0] == "meta-disk":
1812 data["meta_dev"] = lst[1]
1813 data["meta_index"] = lst[2]
1814 elif lst[0] == "address":
1815 data["local_addr"] = tuple(lst[1:])
1816 elif sname == "_remote_host":
1817 for lst in section[1:]:
1818 if lst[0] == "address":
1819 data["remote_addr"] = tuple(lst[1:])
1822 def _MatchesLocal(self, info):
1823 """Test if our local config matches with an existing device.
1825 The parameter should be as returned from `_GetDevInfo()`. This
1826 method tests if our local backing device is the same as the one in
1827 the info parameter, in effect testing if we look like the given
1832 backend, meta = self._children
1834 backend = meta = None
1836 if backend is not None:
1837 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1839 retval = ("local_dev" not in info)
1841 if meta is not None:
1842 retval = retval and ("meta_dev" in info and
1843 info["meta_dev"] == meta.dev_path)
1844 retval = retval and ("meta_index" in info and
1845 info["meta_index"] == 0)
1847 retval = retval and ("meta_dev" not in info and
1848 "meta_index" not in info)
1851 def _MatchesNet(self, info):
1852 """Test if our network config matches with an existing device.
1854 The parameter should be as returned from `_GetDevInfo()`. This
1855 method tests if our network configuration is the same as the one
1856 in the info parameter, in effect testing if we look like the given
1860 if (((self._lhost is None and not ("local_addr" in info)) and
1861 (self._rhost is None and not ("remote_addr" in info)))):
1864 if self._lhost is None:
1867 if not ("local_addr" in info and
1868 "remote_addr" in info):
1871 retval = (info["local_addr"] == (self._lhost, self._lport))
1872 retval = (retval and
1873 info["remote_addr"] == (self._rhost, self._rport))
1877 def _AssembleLocal(cls, minor, backend, meta):
1878 """Configure the local part of a DRBD device.
1880 This is the first thing that must be done on an unconfigured DRBD
1881 device. And it must be done only once.
1884 if not cls._IsValidMeta(meta):
1886 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1887 backend, meta, "0", "-e", "detach",
1890 logger.Error("Can't attach local disk: %s" % result.output)
1891 return not result.failed
1894 def _AssembleNet(cls, minor, net_info, protocol,
1895 dual_pri=False, hmac=None, secret=None):
1896 """Configure the network part of the device.
1899 lhost, lport, rhost, rport = net_info
1900 if None in net_info:
1901 # we don't want network connection and actually want to make
1903 return cls._ShutdownNet(minor)
1905 args = ["drbdsetup", cls._DevPath(minor), "net",
1906 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1907 "-A", "discard-zero-changes",
1913 args.extend(["-a", hmac, "-x", secret])
1914 result = utils.RunCmd(args)
1916 logger.Error("Can't setup network for dbrd device: %s" %
1920 timeout = time.time() + 10
1922 while time.time() < timeout:
1923 info = cls._GetDevInfo(cls._GetShowData(minor))
1924 if not "local_addr" in info or not "remote_addr" in info:
1927 if (info["local_addr"] != (lhost, lport) or
1928 info["remote_addr"] != (rhost, rport)):
1934 logger.Error("Timeout while configuring network")
1938 def AddChildren(self, devices):
1939 """Add a disk to the DRBD device.
1942 if self.minor is None:
1943 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1944 if len(devices) != 2:
1945 raise errors.BlockDeviceError("Need two devices for AddChildren")
1946 info = self._GetDevInfo(self._GetShowData(self.minor))
1947 if "local_dev" in info:
1948 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1949 backend, meta = devices
1950 if backend.dev_path is None or meta.dev_path is None:
1951 raise errors.BlockDeviceError("Children not ready during AddChildren")
1954 if not self._CheckMetaSize(meta.dev_path):
1955 raise errors.BlockDeviceError("Invalid meta device size")
1956 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1957 if not self._IsValidMeta(meta.dev_path):
1958 raise errors.BlockDeviceError("Cannot initalize meta device")
1960 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1961 raise errors.BlockDeviceError("Can't attach to local storage")
1962 self._children = devices
1964 def RemoveChildren(self, devices):
1965 """Detach the drbd device from local storage.
1968 if self.minor is None:
1969 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1971 # early return if we don't actually have backing storage
1972 info = self._GetDevInfo(self._GetShowData(self.minor))
1973 if "local_dev" not in info:
1975 if len(self._children) != 2:
1976 raise errors.BlockDeviceError("We don't have two children: %s" %
1978 if self._children.count(None) == 2: # we don't actually have children :)
1979 logger.Error("Requested detach while detached")
1981 if len(devices) != 2:
1982 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1983 for child, dev in zip(self._children, devices):
1984 if dev != child.dev_path:
1985 raise errors.BlockDeviceError("Mismatch in local storage"
1986 " (%s != %s) in RemoveChildren" %
1987 (dev, child.dev_path))
1989 if not self._ShutdownLocal(self.minor):
1990 raise errors.BlockDeviceError("Can't detach from local storage")
1993 def SetSyncSpeed(self, kbytes):
1994 """Set the speed of the DRBD syncer.
1997 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1998 if self.minor is None:
1999 logger.Info("Instance not attached to a device")
2001 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
2004 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
2005 return not result.failed and children_result
2007 def GetSyncStatus(self):
2008 """Returns the sync status of the device.
2011 (sync_percent, estimated_time, is_degraded)
2013 If sync_percent is None, it means all is ok
2014 If estimated_time is None, it means we can't esimate
2015 the time needed, otherwise it's the time left in seconds.
2018 We set the is_degraded parameter to True on two conditions:
2019 network not connected or local disk missing.
2021 We compute the ldisk parameter based on wheter we have a local
2025 if self.minor is None and not self.Attach():
2026 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
2027 proc_info = self._MassageProcData(self._GetProcData())
2028 if self.minor not in proc_info:
2029 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
2031 line = proc_info[self.minor]
2032 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
2033 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
2035 sync_percent = float(match.group(1))
2036 hours = int(match.group(2))
2037 minutes = int(match.group(3))
2038 seconds = int(match.group(4))
2039 est_time = hours * 3600 + minutes * 60 + seconds
2043 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
2045 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2047 client_state = match.group(1)
2048 local_disk_state = match.group(2)
2049 ldisk = local_disk_state != "UpToDate"
2050 is_degraded = client_state != "Connected"
2051 return sync_percent, est_time, is_degraded or ldisk, ldisk
2053 def GetStatus(self):
2054 """Compute the status of the DRBD device
2056 Note that DRBD devices don't have the STATUS_EXISTING state.
2059 if self.minor is None and not self.Attach():
2060 return self.STATUS_UNKNOWN
2062 data = self._GetProcData()
2063 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2066 mresult = match.match(line)
2070 logger.Error("Can't find myself!")
2071 return self.STATUS_UNKNOWN
2073 state = mresult.group(2)
2074 if state == "Primary":
2075 result = self.STATUS_ONLINE
2077 result = self.STATUS_STANDBY
2081 def Open(self, force=False):
2082 """Make the local state primary.
2084 If the 'force' parameter is given, the '--do-what-I-say' parameter
2085 is given. Since this is a pottentialy dangerous operation, the
2086 force flag should be only given after creation, when it actually
2090 if self.minor is None and not self.Attach():
2091 logger.Error("DRBD cannot attach to a device during open")
2093 cmd = ["drbdsetup", self.dev_path, "primary"]
2096 result = utils.RunCmd(cmd)
2098 msg = ("Can't make drbd device primary: %s" % result.output)
2100 raise errors.BlockDeviceError(msg)
2103 """Make the local state secondary.
2105 This will, of course, fail if the device is in use.
2108 if self.minor is None and not self.Attach():
2109 logger.Info("Instance not attached to a device")
2110 raise errors.BlockDeviceError("Can't find device")
2111 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2113 msg = ("Can't switch drbd device to"
2114 " secondary: %s" % result.output)
2116 raise errors.BlockDeviceError(msg)
2119 """Find a DRBD device which matches our config and attach to it.
2121 In case of partially attached (local device matches but no network
2122 setup), we perform the network attach. If successful, we re-test
2123 the attach if can return success.
2126 for minor in self._GetUsedDevs():
2127 info = self._GetDevInfo(self._GetShowData(minor))
2128 match_l = self._MatchesLocal(info)
2129 match_r = self._MatchesNet(info)
2130 if match_l and match_r:
2132 if match_l and not match_r and "local_addr" not in info:
2133 res_r = self._AssembleNet(minor,
2134 (self._lhost, self._lport,
2135 self._rhost, self._rport),
2138 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2140 # the weakest case: we find something that is only net attached
2141 # even though we were passed some children at init time
2142 if match_r and "local_dev" not in info:
2144 if match_l and not match_r and "local_addr" in info:
2145 # strange case - the device network part points to somewhere
2146 # else, even though its local storage is ours; as we own the
2147 # drbd space, we try to disconnect from the remote peer and
2148 # reconnect to our correct one
2149 if not self._ShutdownNet(minor):
2150 raise errors.BlockDeviceError("Device has correct local storage,"
2151 " wrong remote peer and is unable to"
2152 " disconnect in order to attach to"
2153 " the correct peer")
2154 # note: _AssembleNet also handles the case when we don't want
2155 # local storage (i.e. one or more of the _[lr](host|port) is
2157 if (self._AssembleNet(minor, (self._lhost, self._lport,
2158 self._rhost, self._rport), "C") and
2159 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2165 self._SetFromMinor(minor)
2166 return minor is not None
2169 """Assemble the drbd.
2172 - if we have a local backing device, we bind to it by:
2173 - checking the list of used drbd devices
2174 - check if the local minor use of any of them is our own device
2177 - if we have a local/remote net info:
2178 - redo the local backing device step for the remote device
2179 - check if any drbd device is using the local port,
2181 - check if any remote drbd device is using the remote
2182 port, if yes abort (for now)
2184 - bind the remote net port
2188 if self.minor is not None:
2189 logger.Info("Already assembled")
2192 result = super(DRBD8, self).Assemble()
2196 minor = self._FindUnusedMinor()
2197 need_localdev_teardown = False
2198 if self._children and self._children[0] and self._children[1]:
2199 result = self._AssembleLocal(minor, self._children[0].dev_path,
2200 self._children[1].dev_path)
2203 need_localdev_teardown = True
2204 if self._lhost and self._lport and self._rhost and self._rport:
2205 result = self._AssembleNet(minor,
2206 (self._lhost, self._lport,
2207 self._rhost, self._rport),
2210 if need_localdev_teardown:
2211 # we will ignore failures from this
2212 logger.Error("net setup failed, tearing down local device")
2213 self._ShutdownAll(minor)
2215 self._SetFromMinor(minor)
2219 def _ShutdownLocal(cls, minor):
2220 """Detach from the local device.
2222 I/Os will continue to be served from the remote device. If we
2223 don't have a remote device, this operation will fail.
2226 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2228 logger.Error("Can't detach local device: %s" % result.output)
2229 return not result.failed
2232 def _ShutdownNet(cls, minor):
2233 """Disconnect from the remote peer.
2235 This fails if we don't have a local device.
2238 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2240 logger.Error("Can't shutdown network: %s" % result.output)
2241 return not result.failed
2244 def _ShutdownAll(cls, minor):
2245 """Deactivate the device.
2247 This will, of course, fail if the device is in use.
2250 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2252 logger.Error("Can't shutdown drbd device: %s" % result.output)
2253 return not result.failed
2256 """Shutdown the DRBD device.
2259 if self.minor is None and not self.Attach():
2260 logger.Info("DRBD device not attached to a device during Shutdown")
2262 if not self._ShutdownAll(self.minor):
2265 self.dev_path = None
2269 """Stub remove for DRBD devices.
2272 return self.Shutdown()
2275 def Create(cls, unique_id, children, size):
2276 """Create a new DRBD8 device.
2278 Since DRBD devices are not created per se, just assembled, this
2279 function only initializes the metadata.
2282 if len(children) != 2:
2283 raise errors.ProgrammerError("Invalid setup for the drbd device")
2286 if not meta.Attach():
2287 raise errors.BlockDeviceError("Can't attach to meta device")
2288 if not cls._CheckMetaSize(meta.dev_path):
2289 raise errors.BlockDeviceError("Invalid meta device size")
2290 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2291 if not cls._IsValidMeta(meta.dev_path):
2292 raise errors.BlockDeviceError("Cannot initalize meta device")
2293 return cls(unique_id, children)
2297 constants.LD_LV: LogicalVolume,
2298 constants.LD_MD_R1: MDRaid1,
2299 constants.LD_DRBD7: DRBDev,
2300 constants.LD_DRBD8: DRBD8,
2304 def FindDevice(dev_type, unique_id, children):
2305 """Search for an existing, assembled device.
2307 This will succeed only if the device exists and is assembled, but it
2308 does not do any actions in order to activate the device.
2311 if dev_type not in DEV_MAP:
2312 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2313 device = DEV_MAP[dev_type](unique_id, children)
2314 if not device.Attach():
2319 def AttachOrAssemble(dev_type, unique_id, children):
2320 """Try to attach or assemble an existing device.
2322 This will attach to an existing assembled device or will assemble
2323 the device, as needed, to bring it fully up.
2326 if dev_type not in DEV_MAP:
2327 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2328 device = DEV_MAP[dev_type](unique_id, children)
2329 if not device.Attach():
2331 if not device.Attach():
2332 raise errors.BlockDeviceError("Can't find a valid block device for"
2334 (dev_type, unique_id, children))
2338 def Create(dev_type, unique_id, children, size):
2342 if dev_type not in DEV_MAP:
2343 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2344 device = DEV_MAP[dev_type].Create(unique_id, children, size)