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)
399 """Attach to an existing LV.
401 This method will try to see if an existing and active LV exists
402 which matches our name. If so, its major/minor will be
406 result = utils.RunCmd(["lvdisplay", self.dev_path])
408 logger.Error("Can't find LV %s: %s, %s" %
409 (self.dev_path, result.fail_reason, result.output))
411 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
412 for line in result.stdout.splitlines():
413 match_result = match.match(line)
415 self.major = int(match_result.group(1))
416 self.minor = int(match_result.group(2))
421 """Assemble the device.
423 We alway run `lvchange -ay` on the LV to ensure it's active before
424 use, as there were cases when xenvg was not active after boot
425 (also possibly after disk issues).
428 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
430 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
431 return not result.failed
434 """Shutdown the device.
436 This is a no-op for the LV device type, as we don't deactivate the
443 """Return the status of the device.
445 Logical volumes will can be in all four states, although we don't
446 deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
447 should not be seen for our devices.
450 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
452 logger.Error("Can't display lv: %s" % result.fail_reason)
453 return self.STATUS_UNKNOWN
454 out = result.stdout.strip()
455 # format: type/permissions/alloc/fixed_minor/state/open
457 return self.STATUS_UNKNOWN
458 #writable = (out[1] == "w")
459 active = (out[4] == "a")
460 online = (out[5] == "o")
462 retval = self.STATUS_ONLINE
464 retval = self.STATUS_STANDBY
466 retval = self.STATUS_EXISTING
470 def GetSyncStatus(self):
471 """Returns the sync status of the device.
473 If this device is a mirroring device, this function returns the
474 status of the mirror.
477 (sync_percent, estimated_time, is_degraded, ldisk)
479 For logical volumes, sync_percent and estimated_time are always
480 None (no recovery in progress, as we don't handle the mirrored LV
481 case). The is_degraded parameter is the inverse of the ldisk
484 For the ldisk parameter, we check if the logical volume has the
485 'virtual' type, which means it's not backed by existing storage
486 anymore (read from it return I/O error). This happens after a
487 physical disk failure and subsequent 'vgreduce --removemissing' on
491 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
493 logger.Error("Can't display lv: %s" % result.fail_reason)
494 return None, None, True, True
495 out = result.stdout.strip()
496 # format: type/permissions/alloc/fixed_minor/state/open
498 logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
499 return None, None, True, True
500 ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
502 return None, None, ldisk, ldisk
504 def Open(self, force=False):
505 """Make the device ready for I/O.
507 This is a no-op for the LV device type.
513 """Notifies that the device will no longer be used for I/O.
515 This is a no-op for the LV device type.
520 def Snapshot(self, size):
521 """Create a snapshot copy of an lvm block device.
524 snap_name = self._lv_name + ".snap"
526 # remove existing snapshot if found
527 snap = LogicalVolume((self._vg_name, snap_name), None)
530 pvs_info = self.GetPVInfo(self._vg_name)
532 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
536 free_size, pv_name = pvs_info[0]
538 raise errors.BlockDeviceError("Not enough free space: required %s,"
539 " available %s" % (size, free_size))
541 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
542 "-n%s" % snap_name, self.dev_path])
544 raise errors.BlockDeviceError("command: %s error: %s" %
545 (result.cmd, result.fail_reason))
549 def SetInfo(self, text):
550 """Update metadata with info text.
553 BlockDev.SetInfo(self, text)
555 # Replace invalid characters
556 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
557 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
559 # Only up to 128 characters are allowed
562 result = utils.RunCmd(["lvchange", "--addtag", text,
565 raise errors.BlockDeviceError("Command: %s error: %s" %
566 (result.cmd, result.fail_reason))
569 class MDRaid1(BlockDev):
570 """raid1 device implemented via md.
573 def __init__(self, unique_id, children):
574 super(MDRaid1, self).__init__(unique_id, children)
579 """Find an array which matches our config and attach to it.
581 This tries to find a MD array which has the same UUID as our own.
584 minor = self._FindMDByUUID(self.unique_id)
585 if minor is not None:
586 self._SetFromMinor(minor)
591 return (minor is not None)
595 """Compute the list of in-use MD devices.
597 It doesn't matter if the used device have other raid level, just
598 that they are in use.
601 mdstat = open("/proc/mdstat", "r")
602 data = mdstat.readlines()
606 valid_line = re.compile("^md([0-9]+) : .*$")
608 match = valid_line.match(line)
610 md_no = int(match.group(1))
611 used_md[md_no] = line
616 def _GetDevInfo(minor):
617 """Get info about a MD device.
619 Currently only uuid is returned.
622 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
624 logger.Error("Can't display md: %s" % result.fail_reason)
627 for line in result.stdout.splitlines():
629 kv = line.split(" : ", 1)
632 retval["uuid"] = kv[1].split()[0]
633 elif kv[0] == "State":
634 retval["state"] = kv[1].split(", ")
638 def _FindUnusedMinor():
639 """Compute an unused MD minor.
641 This code assumes that there are 256 minors only.
644 used_md = MDRaid1._GetUsedDevs()
651 logger.Error("Critical: Out of md minor numbers.")
652 raise errors.BlockDeviceError("Can't find a free MD minor")
656 def _FindMDByUUID(cls, uuid):
657 """Find the minor of an MD array with a given UUID.
660 md_list = cls._GetUsedDevs()
661 for minor in md_list:
662 info = cls._GetDevInfo(minor)
663 if info and info["uuid"] == uuid:
668 def _ZeroSuperblock(dev_path):
669 """Zero the possible locations for an MD superblock.
671 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
672 fails in versions 2.x with the same error code as non-writable
675 The superblocks are located at (negative values are relative to
676 the end of the block device):
677 - -128k to end for version 0.90 superblock
678 - -8k to -12k for version 1.0 superblock (included in the above)
679 - 0k to 4k for version 1.1 superblock
680 - 4k to 8k for version 1.2 superblock
682 To cover all situations, the zero-ing will be:
686 As such, the minimum device size must be 128k, otherwise we'll get
689 Note that this function depends on the fact that one can open,
690 read and write block devices normally.
693 overwrite_size = 128 * 1024
694 empty_buf = '\0' * overwrite_size
695 fd = open(dev_path, "r+")
701 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
702 fd.seek(-overwrite_size, 2)
706 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
711 def Create(cls, unique_id, children, size):
712 """Create a new MD raid1 array.
715 if not isinstance(children, (tuple, list)):
716 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
719 if not isinstance(i, BlockDev):
720 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
723 cls._ZeroSuperblock(i.dev_path)
724 except EnvironmentError, err:
725 logger.Error("Can't zero superblock for %s: %s" %
726 (i.dev_path, str(err)))
728 minor = cls._FindUnusedMinor()
729 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
730 "--auto=yes", "--force", "-l1",
731 "-n%d" % len(children)] +
732 [dev.dev_path for dev in children])
735 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
738 info = cls._GetDevInfo(minor)
739 if not info or not "uuid" in info:
740 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
742 return MDRaid1(info["uuid"], children)
745 """Stub remove function for MD RAID 1 arrays.
747 We don't remove the superblock right now. Mark a to do.
750 #TODO: maybe zero superblock on child devices?
751 return self.Shutdown()
753 def Rename(self, new_id):
756 This is not supported for md raid1 devices.
759 raise errors.ProgrammerError("Can't rename a md raid1 device")
761 def AddChildren(self, devices):
762 """Add new member(s) to the md raid1.
765 if self.minor is None and not self.Attach():
766 raise errors.BlockDeviceError("Can't attach to device")
768 args = ["mdadm", "-a", self.dev_path]
770 if dev.dev_path is None:
771 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
773 args.append(dev.dev_path)
774 result = utils.RunCmd(args)
776 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
778 new_len = len(self._children) + len(devices)
779 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
781 raise errors.BlockDeviceError("Can't grow md array: %s" %
783 self._children.extend(devices)
785 def RemoveChildren(self, devices):
786 """Remove member(s) from the md raid1.
789 if self.minor is None and not self.Attach():
790 raise errors.BlockDeviceError("Can't attach to device")
791 new_len = len(self._children) - len(devices)
793 raise errors.BlockDeviceError("Can't reduce to less than one child")
794 args = ["mdadm", "-f", self.dev_path]
798 for c in self._children:
799 if c.dev_path == dev:
803 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
805 result = utils.RunCmd(args)
807 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
810 # it seems here we need a short delay for MD to update its
814 result = utils.RunCmd(args)
816 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
817 " %s" % result.output)
818 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
821 raise errors.BlockDeviceError("Can't shrink md array: %s" %
823 for dev in orig_devs:
824 self._children.remove(dev)
827 """Return the status of the device.
831 if self.minor is None:
832 retval = self.STATUS_UNKNOWN
834 retval = self.STATUS_ONLINE
837 def _SetFromMinor(self, minor):
838 """Set our parameters based on the given minor.
840 This sets our minor variable and our dev_path.
844 self.dev_path = "/dev/md%d" % minor
847 """Assemble the MD device.
849 At this point we should have:
850 - list of children devices
854 result = super(MDRaid1, self).Assemble()
857 md_list = self._GetUsedDevs()
858 for minor in md_list:
859 info = self._GetDevInfo(minor)
860 if info and info["uuid"] == self.unique_id:
861 self._SetFromMinor(minor)
862 logger.Info("MD array %s already started" % str(self))
864 free_minor = self._FindUnusedMinor()
865 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
866 self.unique_id, "/dev/md%d" % free_minor] +
867 [bdev.dev_path for bdev in self._children])
869 logger.Error("Can't assemble MD array: %s: %s" %
870 (result.fail_reason, result.output))
873 self.minor = free_minor
874 return not result.failed
877 """Tear down the MD array.
879 This does a 'mdadm --stop' so after this command, the array is no
883 if self.minor is None and not self.Attach():
884 logger.Info("MD object not attached to a device")
887 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
889 logger.Error("Can't stop MD array: %s" % result.fail_reason)
895 def SetSyncSpeed(self, kbytes):
896 """Set the maximum sync speed for the MD array.
899 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
900 if self.minor is None:
901 logger.Error("MD array not attached to a device")
903 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
905 f.write("%d" % kbytes)
908 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
910 f.write("%d" % (kbytes/2))
915 def GetSyncStatus(self):
916 """Returns the sync status of the device.
919 (sync_percent, estimated_time, is_degraded, ldisk)
921 If sync_percent is None, it means all is ok
922 If estimated_time is None, it means we can't esimate
923 the time needed, otherwise it's the time left in seconds.
925 The ldisk parameter is always true for MD devices.
928 if self.minor is None and not self.Attach():
929 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
930 dev_info = self._GetDevInfo(self.minor)
931 is_clean = ("state" in dev_info and
932 len(dev_info["state"]) == 1 and
933 dev_info["state"][0] in ("clean", "active"))
934 sys_path = "/sys/block/md%s/md/" % self.minor
935 f = file(sys_path + "sync_action")
936 sync_status = f.readline().strip()
938 if sync_status == "idle":
939 return None, None, not is_clean, False
940 f = file(sys_path + "sync_completed")
941 sync_completed = f.readline().strip().split(" / ")
943 if len(sync_completed) != 2:
944 return 0, None, not is_clean, False
945 sync_done, sync_total = [float(i) for i in sync_completed]
946 sync_percent = 100.0*sync_done/sync_total
947 f = file(sys_path + "sync_speed")
948 sync_speed_k = int(f.readline().strip())
949 if sync_speed_k == 0:
952 time_est = (sync_total - sync_done) / 2 / sync_speed_k
953 return sync_percent, time_est, not is_clean, False
955 def Open(self, force=False):
956 """Make the device ready for I/O.
958 This is a no-op for the MDRaid1 device type, although we could use
959 the 2.6.18's new array_state thing.
965 """Notifies that the device will no longer be used for I/O.
967 This is a no-op for the MDRaid1 device type, but see comment for
974 class BaseDRBD(BlockDev):
977 This class contains a few bits of common functionality between the
978 0.7 and 8.x versions of DRBD.
981 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
982 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
985 _ST_UNCONFIGURED = "Unconfigured"
986 _ST_WFCONNECTION = "WFConnection"
987 _ST_CONNECTED = "Connected"
991 """Return data from /proc/drbd.
994 stat = open("/proc/drbd", "r")
996 data = stat.read().splitlines()
1000 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
1004 def _MassageProcData(data):
1005 """Transform the output of _GetProdData into a nicer form.
1008 a dictionary of minor: joined lines from /proc/drbd for that minor
1011 lmatch = re.compile("^ *([0-9]+):.*$")
1013 old_minor = old_line = None
1015 lresult = lmatch.match(line)
1016 if lresult is not None:
1017 if old_minor is not None:
1018 results[old_minor] = old_line
1019 old_minor = int(lresult.group(1))
1022 if old_minor is not None:
1023 old_line += " " + line.strip()
1025 if old_minor is not None:
1026 results[old_minor] = old_line
1030 def _GetVersion(cls):
1031 """Return the DRBD version.
1033 This will return a dict with keys:
1039 proto2 (only on drbd > 8.2.X)
1042 proc_data = cls._GetProcData()
1043 first_line = proc_data[0].strip()
1044 version = cls._VERSION_RE.match(first_line)
1046 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1049 values = version.groups()
1050 retval = {'k_major': int(values[0]),
1051 'k_minor': int(values[1]),
1052 'k_point': int(values[2]),
1053 'api': int(values[3]),
1054 'proto': int(values[4]),
1056 if values[5] is not None:
1057 retval['proto2'] = values[5]
1062 def _DevPath(minor):
1063 """Return the path to a drbd device for a given minor.
1066 return "/dev/drbd%d" % minor
1069 def _GetUsedDevs(cls):
1070 """Compute the list of used DRBD devices.
1073 data = cls._GetProcData()
1076 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1078 match = valid_line.match(line)
1081 minor = int(match.group(1))
1082 state = match.group(2)
1083 if state == cls._ST_UNCONFIGURED:
1085 used_devs[minor] = state, line
1089 def _SetFromMinor(self, minor):
1090 """Set our parameters based on the given minor.
1092 This sets our minor variable and our dev_path.
1096 self.minor = self.dev_path = None
1099 self.dev_path = self._DevPath(minor)
1102 def _CheckMetaSize(meta_device):
1103 """Check if the given meta device looks like a valid one.
1105 This currently only check the size, which must be around
1109 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1111 logger.Error("Failed to get device size: %s" % result.fail_reason)
1114 sectors = int(result.stdout)
1116 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1118 bytes = sectors * 512
1119 if bytes < 128 * 1024 * 1024: # less than 128MiB
1120 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1122 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1123 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1127 def Rename(self, new_id):
1130 This is not supported for drbd devices.
1133 raise errors.ProgrammerError("Can't rename a drbd device")
1136 class DRBDev(BaseDRBD):
1137 """DRBD block device.
1139 This implements the local host part of the DRBD device, i.e. it
1140 doesn't do anything to the supposed peer. If you need a fully
1141 connected DRBD pair, you need to use this class on both hosts.
1143 The unique_id for the drbd device is the (local_ip, local_port,
1144 remote_ip, remote_port) tuple, and it must have two children: the
1145 data device and the meta_device. The meta device is checked for
1146 valid size and is zeroed on create.
1149 def __init__(self, unique_id, children):
1150 super(DRBDev, self).__init__(unique_id, children)
1151 self.major = self._DRBD_MAJOR
1152 version = self._GetVersion()
1153 if version['k_major'] != 0 and version['k_minor'] != 7:
1154 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1155 " requested ganeti usage: kernel is"
1156 " %s.%s, ganeti wants 0.7" %
1157 (version['k_major'], version['k_minor']))
1158 if len(children) != 2:
1159 raise ValueError("Invalid configuration data %s" % str(children))
1160 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1161 raise ValueError("Invalid configuration data %s" % str(unique_id))
1162 self._lhost, self._lport, self._rhost, self._rport = unique_id
1166 def _FindUnusedMinor(cls):
1167 """Find an unused DRBD device.
1170 data = cls._GetProcData()
1172 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1174 match = valid_line.match(line)
1176 return int(match.group(1))
1177 logger.Error("Error: no free drbd minors!")
1178 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1181 def _GetDevInfo(cls, minor):
1182 """Get details about a given DRBD minor.
1184 This return, if available, the local backing device in (major,
1185 minor) formant and the local and remote (ip, port) information.
1189 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1191 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1194 if out == "Not configured\n":
1196 for line in out.splitlines():
1197 if "local_dev" not in data:
1198 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1200 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1202 if "meta_dev" not in data:
1203 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1205 if match.group(2) is not None and match.group(3) is not None:
1206 # matched on the major/minor
1207 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1209 # matched on the "internal" string
1210 data["meta_dev"] = match.group(1)
1211 # in this case, no meta_index is in the output
1212 data["meta_index"] = -1
1214 if "meta_index" not in data:
1215 match = re.match("^Meta index: ([0-9]+).*$", line)
1217 data["meta_index"] = int(match.group(1))
1219 if "local_addr" not in data:
1220 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1222 data["local_addr"] = (match.group(1), int(match.group(2)))
1224 if "remote_addr" not in data:
1225 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1227 data["remote_addr"] = (match.group(1), int(match.group(2)))
1231 def _MatchesLocal(self, info):
1232 """Test if our local config matches with an existing device.
1234 The parameter should be as returned from `_GetDevInfo()`. This
1235 method tests if our local backing device is the same as the one in
1236 the info parameter, in effect testing if we look like the given
1240 if not ("local_dev" in info and "meta_dev" in info and
1241 "meta_index" in info):
1244 backend = self._children[0]
1245 if backend is not None:
1246 retval = (info["local_dev"] == (backend.major, backend.minor))
1248 retval = (info["local_dev"] == (0, 0))
1249 meta = self._children[1]
1250 if meta is not None:
1251 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1252 retval = retval and (info["meta_index"] == 0)
1254 retval = retval and (info["meta_dev"] == "internal" and
1255 info["meta_index"] == -1)
1258 def _MatchesNet(self, info):
1259 """Test if our network config matches with an existing device.
1261 The parameter should be as returned from `_GetDevInfo()`. This
1262 method tests if our network configuration is the same as the one
1263 in the info parameter, in effect testing if we look like the given
1267 if (((self._lhost is None and not ("local_addr" in info)) and
1268 (self._rhost is None and not ("remote_addr" in info)))):
1271 if self._lhost is None:
1274 if not ("local_addr" in info and
1275 "remote_addr" in info):
1278 retval = (info["local_addr"] == (self._lhost, self._lport))
1279 retval = (retval and
1280 info["remote_addr"] == (self._rhost, self._rport))
1284 def _AssembleLocal(cls, minor, backend, meta):
1285 """Configure the local part of a DRBD device.
1287 This is the first thing that must be done on an unconfigured DRBD
1288 device. And it must be done only once.
1291 if not cls._CheckMetaSize(meta):
1293 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1294 backend, meta, "0", "-e", "detach"])
1296 logger.Error("Can't attach local disk: %s" % result.output)
1297 return not result.failed
1300 def _ShutdownLocal(cls, minor):
1301 """Detach from the local device.
1303 I/Os will continue to be served from the remote device. If we
1304 don't have a remote device, this operation will fail.
1307 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1309 logger.Error("Can't detach local device: %s" % result.output)
1310 return not result.failed
1313 def _ShutdownAll(minor):
1314 """Deactivate the device.
1316 This will, of course, fail if the device is in use.
1319 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1321 logger.Error("Can't shutdown drbd device: %s" % result.output)
1322 return not result.failed
1325 def _AssembleNet(cls, minor, net_info, protocol):
1326 """Configure the network part of the device.
1328 This operation can be, in theory, done multiple times, but there
1329 have been cases (in lab testing) in which the network part of the
1330 device had become stuck and couldn't be shut down because activity
1331 from the new peer (also stuck) triggered a timer re-init and
1332 needed remote peer interface shutdown in order to clear. So please
1333 don't change online the net config.
1336 lhost, lport, rhost, rport = net_info
1337 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1338 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1341 logger.Error("Can't setup network for dbrd device: %s" %
1345 timeout = time.time() + 10
1347 while time.time() < timeout:
1348 info = cls._GetDevInfo(minor)
1349 if not "local_addr" in info or not "remote_addr" in info:
1352 if (info["local_addr"] != (lhost, lport) or
1353 info["remote_addr"] != (rhost, rport)):
1359 logger.Error("Timeout while configuring network")
1364 def _ShutdownNet(cls, minor):
1365 """Disconnect from the remote peer.
1367 This fails if we don't have a local device.
1370 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1372 logger.Error("Can't shutdown network: %s" % result.output)
1373 return not result.failed
1376 """Assemble the drbd.
1379 - if we have a local backing device, we bind to it by:
1380 - checking the list of used drbd devices
1381 - check if the local minor use of any of them is our own device
1384 - if we have a local/remote net info:
1385 - redo the local backing device step for the remote device
1386 - check if any drbd device is using the local port,
1388 - check if any remote drbd device is using the remote
1389 port, if yes abort (for now)
1391 - bind the remote net port
1395 if self.minor is not None:
1396 logger.Info("Already assembled")
1399 result = super(DRBDev, self).Assemble()
1403 minor = self._FindUnusedMinor()
1404 need_localdev_teardown = False
1405 if self._children[0]:
1406 result = self._AssembleLocal(minor, self._children[0].dev_path,
1407 self._children[1].dev_path)
1410 need_localdev_teardown = True
1411 if self._lhost and self._lport and self._rhost and self._rport:
1412 result = self._AssembleNet(minor,
1413 (self._lhost, self._lport,
1414 self._rhost, self._rport),
1417 if need_localdev_teardown:
1418 # we will ignore failures from this
1419 logger.Error("net setup failed, tearing down local device")
1420 self._ShutdownAll(minor)
1422 self._SetFromMinor(minor)
1426 """Shutdown the DRBD device.
1429 if self.minor is None and not self.Attach():
1430 logger.Info("DRBD device not attached to a device during Shutdown")
1432 if not self._ShutdownAll(self.minor):
1435 self.dev_path = None
1439 """Find a DRBD device which matches our config and attach to it.
1441 In case of partially attached (local device matches but no network
1442 setup), we perform the network attach. If successful, we re-test
1443 the attach if can return success.
1446 for minor in self._GetUsedDevs():
1447 info = self._GetDevInfo(minor)
1448 match_l = self._MatchesLocal(info)
1449 match_r = self._MatchesNet(info)
1450 if match_l and match_r:
1452 if match_l and not match_r and "local_addr" not in info:
1453 res_r = self._AssembleNet(minor,
1454 (self._lhost, self._lport,
1455 self._rhost, self._rport),
1457 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1462 self._SetFromMinor(minor)
1463 return minor is not None
1465 def Open(self, force=False):
1466 """Make the local state primary.
1468 If the 'force' parameter is given, the '--do-what-I-say' parameter
1469 is given. Since this is a pottentialy dangerous operation, the
1470 force flag should be only given after creation, when it actually
1474 if self.minor is None and not self.Attach():
1475 logger.Error("DRBD cannot attach to a device during open")
1477 cmd = ["drbdsetup", self.dev_path, "primary"]
1479 cmd.append("--do-what-I-say")
1480 result = utils.RunCmd(cmd)
1482 msg = ("Can't make drbd device primary: %s" % result.output)
1484 raise errors.BlockDeviceError(msg)
1487 """Make the local state secondary.
1489 This will, of course, fail if the device is in use.
1492 if self.minor is None and not self.Attach():
1493 logger.Info("Instance not attached to a device")
1494 raise errors.BlockDeviceError("Can't find device")
1495 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1497 msg = ("Can't switch drbd device to"
1498 " secondary: %s" % result.output)
1500 raise errors.BlockDeviceError(msg)
1502 def SetSyncSpeed(self, kbytes):
1503 """Set the speed of the DRBD syncer.
1506 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1507 if self.minor is None:
1508 logger.Info("Instance not attached to a device")
1510 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1513 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1514 return not result.failed and children_result
1516 def GetSyncStatus(self):
1517 """Returns the sync status of the device.
1520 (sync_percent, estimated_time, is_degraded, ldisk)
1522 If sync_percent is None, it means all is ok
1523 If estimated_time is None, it means we can't esimate
1524 the time needed, otherwise it's the time left in seconds.
1526 The ldisk parameter will be returned as True, since the DRBD7
1527 devices have not been converted.
1530 if self.minor is None and not self.Attach():
1531 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1532 proc_info = self._MassageProcData(self._GetProcData())
1533 if self.minor not in proc_info:
1534 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1536 line = proc_info[self.minor]
1537 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1538 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1540 sync_percent = float(match.group(1))
1541 hours = int(match.group(2))
1542 minutes = int(match.group(3))
1543 seconds = int(match.group(4))
1544 est_time = hours * 3600 + minutes * 60 + seconds
1548 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1550 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1552 client_state = match.group(1)
1553 is_degraded = client_state != "Connected"
1554 return sync_percent, est_time, is_degraded, False
1556 def GetStatus(self):
1557 """Compute the status of the DRBD device
1559 Note that DRBD devices don't have the STATUS_EXISTING state.
1562 if self.minor is None and not self.Attach():
1563 return self.STATUS_UNKNOWN
1565 data = self._GetProcData()
1566 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1569 mresult = match.match(line)
1573 logger.Error("Can't find myself!")
1574 return self.STATUS_UNKNOWN
1576 state = mresult.group(2)
1577 if state == "Primary":
1578 result = self.STATUS_ONLINE
1580 result = self.STATUS_STANDBY
1585 def _ZeroDevice(device):
1588 This writes until we get ENOSPC.
1591 f = open(device, "w")
1592 buf = "\0" * 1048576
1596 except IOError, err:
1597 if err.errno != errno.ENOSPC:
1601 def Create(cls, unique_id, children, size):
1602 """Create a new DRBD device.
1604 Since DRBD devices are not created per se, just assembled, this
1605 function just zeroes the meta device.
1608 if len(children) != 2:
1609 raise errors.ProgrammerError("Invalid setup for the drbd device")
1612 if not meta.Attach():
1613 raise errors.BlockDeviceError("Can't attach to meta device")
1614 if not cls._CheckMetaSize(meta.dev_path):
1615 raise errors.BlockDeviceError("Invalid meta device")
1616 logger.Info("Started zeroing device %s" % meta.dev_path)
1617 cls._ZeroDevice(meta.dev_path)
1618 logger.Info("Done zeroing device %s" % meta.dev_path)
1619 return cls(unique_id, children)
1622 """Stub remove for DRBD devices.
1625 return self.Shutdown()
1628 class DRBD8(BaseDRBD):
1629 """DRBD v8.x block device.
1631 This implements the local host part of the DRBD device, i.e. it
1632 doesn't do anything to the supposed peer. If you need a fully
1633 connected DRBD pair, you need to use this class on both hosts.
1635 The unique_id for the drbd device is the (local_ip, local_port,
1636 remote_ip, remote_port) tuple, and it must have two children: the
1637 data device and the meta_device. The meta device is checked for
1638 valid size and is zeroed on create.
1644 def __init__(self, unique_id, children):
1645 if children and children.count(None) > 0:
1647 super(DRBD8, self).__init__(unique_id, children)
1648 self.major = self._DRBD_MAJOR
1649 version = self._GetVersion()
1650 if version['k_major'] != 8 :
1651 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1652 " requested ganeti usage: kernel is"
1653 " %s.%s, ganeti wants 8.x" %
1654 (version['k_major'], version['k_minor']))
1656 if len(children) not in (0, 2):
1657 raise ValueError("Invalid configuration data %s" % str(children))
1658 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1659 raise ValueError("Invalid configuration data %s" % str(unique_id))
1660 self._lhost, self._lport, self._rhost, self._rport = unique_id
1664 def _InitMeta(cls, minor, dev_path):
1665 """Initialize a meta device.
1667 This will not work if the given minor is in use.
1670 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1671 "v08", dev_path, "0", "create-md"])
1673 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1677 def _FindUnusedMinor(cls):
1678 """Find an unused DRBD device.
1680 This is specific to 8.x as the minors are allocated dynamically,
1681 so non-existing numbers up to a max minor count are actually free.
1684 data = cls._GetProcData()
1686 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1687 used_line = re.compile("^ *([0-9]+): cs:")
1690 match = unused_line.match(line)
1692 return int(match.group(1))
1693 match = used_line.match(line)
1695 minor = int(match.group(1))
1696 highest = max(highest, minor)
1697 if highest is None: # there are no minors in use at all
1699 if highest >= cls._MAX_MINORS:
1700 logger.Error("Error: no free drbd minors!")
1701 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1705 def _IsValidMeta(cls, meta_device):
1706 """Check if the given meta device looks like a valid one.
1709 minor = cls._FindUnusedMinor()
1710 minor_path = cls._DevPath(minor)
1711 result = utils.RunCmd(["drbdmeta", minor_path,
1712 "v08", meta_device, "0",
1715 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1720 def _GetShowParser(cls):
1721 """Return a parser for `drbd show` output.
1723 This will either create or return an already-create parser for the
1724 output of the command `drbd show`.
1727 if cls._PARSE_SHOW is not None:
1728 return cls._PARSE_SHOW
1731 lbrace = pyp.Literal("{").suppress()
1732 rbrace = pyp.Literal("}").suppress()
1733 semi = pyp.Literal(";").suppress()
1734 # this also converts the value to an int
1735 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1737 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1738 defa = pyp.Literal("_is_default").suppress()
1739 dbl_quote = pyp.Literal('"').suppress()
1741 keyword = pyp.Word(pyp.alphanums + '-')
1744 value = pyp.Word(pyp.alphanums + '_-/.:')
1745 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1746 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1748 # meta device, extended syntax
1749 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1750 number + pyp.Word(']').suppress())
1753 stmt = (~rbrace + keyword + ~lbrace +
1754 (addr_port ^ value ^ quoted ^ meta_value) +
1755 pyp.Optional(defa) + semi +
1756 pyp.Optional(pyp.restOfLine).suppress())
1759 section_name = pyp.Word(pyp.alphas + '_')
1760 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1762 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1765 cls._PARSE_SHOW = bnf
1770 def _GetShowData(cls, minor):
1771 """Return the `drbdsetup show` data for a minor.
1774 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1776 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1778 return result.stdout
1781 def _GetDevInfo(cls, out):
1782 """Parse details about a given DRBD minor.
1784 This return, if available, the local backing device (as a path)
1785 and the local and remote (ip, port) information from a string
1786 containing the output of the `drbdsetup show` command as returned
1794 bnf = cls._GetShowParser()
1798 results = bnf.parseString(out)
1799 except pyp.ParseException, err:
1800 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1803 # and massage the results into our desired format
1804 for section in results:
1806 if sname == "_this_host":
1807 for lst in section[1:]:
1808 if lst[0] == "disk":
1809 data["local_dev"] = lst[1]
1810 elif lst[0] == "meta-disk":
1811 data["meta_dev"] = lst[1]
1812 data["meta_index"] = lst[2]
1813 elif lst[0] == "address":
1814 data["local_addr"] = tuple(lst[1:])
1815 elif sname == "_remote_host":
1816 for lst in section[1:]:
1817 if lst[0] == "address":
1818 data["remote_addr"] = tuple(lst[1:])
1821 def _MatchesLocal(self, info):
1822 """Test if our local config matches with an existing device.
1824 The parameter should be as returned from `_GetDevInfo()`. This
1825 method tests if our local backing device is the same as the one in
1826 the info parameter, in effect testing if we look like the given
1831 backend, meta = self._children
1833 backend = meta = None
1835 if backend is not None:
1836 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1838 retval = ("local_dev" not in info)
1840 if meta is not None:
1841 retval = retval and ("meta_dev" in info and
1842 info["meta_dev"] == meta.dev_path)
1843 retval = retval and ("meta_index" in info and
1844 info["meta_index"] == 0)
1846 retval = retval and ("meta_dev" not in info and
1847 "meta_index" not in info)
1850 def _MatchesNet(self, info):
1851 """Test if our network config matches with an existing device.
1853 The parameter should be as returned from `_GetDevInfo()`. This
1854 method tests if our network configuration is the same as the one
1855 in the info parameter, in effect testing if we look like the given
1859 if (((self._lhost is None and not ("local_addr" in info)) and
1860 (self._rhost is None and not ("remote_addr" in info)))):
1863 if self._lhost is None:
1866 if not ("local_addr" in info and
1867 "remote_addr" in info):
1870 retval = (info["local_addr"] == (self._lhost, self._lport))
1871 retval = (retval and
1872 info["remote_addr"] == (self._rhost, self._rport))
1876 def _AssembleLocal(cls, minor, backend, meta):
1877 """Configure the local part of a DRBD device.
1879 This is the first thing that must be done on an unconfigured DRBD
1880 device. And it must be done only once.
1883 if not cls._IsValidMeta(meta):
1885 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1886 backend, meta, "0", "-e", "detach",
1889 logger.Error("Can't attach local disk: %s" % result.output)
1890 return not result.failed
1893 def _AssembleNet(cls, minor, net_info, protocol,
1894 dual_pri=False, hmac=None, secret=None):
1895 """Configure the network part of the device.
1898 lhost, lport, rhost, rport = net_info
1899 if None in net_info:
1900 # we don't want network connection and actually want to make
1902 return cls._ShutdownNet(minor)
1904 args = ["drbdsetup", cls._DevPath(minor), "net",
1905 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1906 "-A", "discard-zero-changes",
1912 args.extend(["-a", hmac, "-x", secret])
1913 result = utils.RunCmd(args)
1915 logger.Error("Can't setup network for dbrd device: %s" %
1919 timeout = time.time() + 10
1921 while time.time() < timeout:
1922 info = cls._GetDevInfo(cls._GetShowData(minor))
1923 if not "local_addr" in info or not "remote_addr" in info:
1926 if (info["local_addr"] != (lhost, lport) or
1927 info["remote_addr"] != (rhost, rport)):
1933 logger.Error("Timeout while configuring network")
1937 def AddChildren(self, devices):
1938 """Add a disk to the DRBD device.
1941 if self.minor is None:
1942 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1943 if len(devices) != 2:
1944 raise errors.BlockDeviceError("Need two devices for AddChildren")
1945 info = self._GetDevInfo(self._GetShowData(self.minor))
1946 if "local_dev" in info:
1947 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1948 backend, meta = devices
1949 if backend.dev_path is None or meta.dev_path is None:
1950 raise errors.BlockDeviceError("Children not ready during AddChildren")
1953 if not self._CheckMetaSize(meta.dev_path):
1954 raise errors.BlockDeviceError("Invalid meta device size")
1955 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1956 if not self._IsValidMeta(meta.dev_path):
1957 raise errors.BlockDeviceError("Cannot initalize meta device")
1959 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1960 raise errors.BlockDeviceError("Can't attach to local storage")
1961 self._children = devices
1963 def RemoveChildren(self, devices):
1964 """Detach the drbd device from local storage.
1967 if self.minor is None:
1968 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1970 # early return if we don't actually have backing storage
1971 info = self._GetDevInfo(self._GetShowData(self.minor))
1972 if "local_dev" not in info:
1974 if len(self._children) != 2:
1975 raise errors.BlockDeviceError("We don't have two children: %s" %
1977 if self._children.count(None) == 2: # we don't actually have children :)
1978 logger.Error("Requested detach while detached")
1980 if len(devices) != 2:
1981 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1982 for child, dev in zip(self._children, devices):
1983 if dev != child.dev_path:
1984 raise errors.BlockDeviceError("Mismatch in local storage"
1985 " (%s != %s) in RemoveChildren" %
1986 (dev, child.dev_path))
1988 if not self._ShutdownLocal(self.minor):
1989 raise errors.BlockDeviceError("Can't detach from local storage")
1992 def SetSyncSpeed(self, kbytes):
1993 """Set the speed of the DRBD syncer.
1996 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1997 if self.minor is None:
1998 logger.Info("Instance not attached to a device")
2000 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
2003 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
2004 return not result.failed and children_result
2006 def GetSyncStatus(self):
2007 """Returns the sync status of the device.
2010 (sync_percent, estimated_time, is_degraded)
2012 If sync_percent is None, it means all is ok
2013 If estimated_time is None, it means we can't esimate
2014 the time needed, otherwise it's the time left in seconds.
2017 We set the is_degraded parameter to True on two conditions:
2018 network not connected or local disk missing.
2020 We compute the ldisk parameter based on wheter we have a local
2024 if self.minor is None and not self.Attach():
2025 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
2026 proc_info = self._MassageProcData(self._GetProcData())
2027 if self.minor not in proc_info:
2028 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
2030 line = proc_info[self.minor]
2031 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
2032 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
2034 sync_percent = float(match.group(1))
2035 hours = int(match.group(2))
2036 minutes = int(match.group(3))
2037 seconds = int(match.group(4))
2038 est_time = hours * 3600 + minutes * 60 + seconds
2042 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
2044 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2046 client_state = match.group(1)
2047 local_disk_state = match.group(2)
2048 ldisk = local_disk_state != "UpToDate"
2049 is_degraded = client_state != "Connected"
2050 return sync_percent, est_time, is_degraded or ldisk, ldisk
2052 def GetStatus(self):
2053 """Compute the status of the DRBD device
2055 Note that DRBD devices don't have the STATUS_EXISTING state.
2058 if self.minor is None and not self.Attach():
2059 return self.STATUS_UNKNOWN
2061 data = self._GetProcData()
2062 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2065 mresult = match.match(line)
2069 logger.Error("Can't find myself!")
2070 return self.STATUS_UNKNOWN
2072 state = mresult.group(2)
2073 if state == "Primary":
2074 result = self.STATUS_ONLINE
2076 result = self.STATUS_STANDBY
2080 def Open(self, force=False):
2081 """Make the local state primary.
2083 If the 'force' parameter is given, the '--do-what-I-say' parameter
2084 is given. Since this is a pottentialy dangerous operation, the
2085 force flag should be only given after creation, when it actually
2089 if self.minor is None and not self.Attach():
2090 logger.Error("DRBD cannot attach to a device during open")
2092 cmd = ["drbdsetup", self.dev_path, "primary"]
2095 result = utils.RunCmd(cmd)
2097 msg = ("Can't make drbd device primary: %s" % result.output)
2099 raise errors.BlockDeviceError(msg)
2102 """Make the local state secondary.
2104 This will, of course, fail if the device is in use.
2107 if self.minor is None and not self.Attach():
2108 logger.Info("Instance not attached to a device")
2109 raise errors.BlockDeviceError("Can't find device")
2110 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2112 msg = ("Can't switch drbd device to"
2113 " secondary: %s" % result.output)
2115 raise errors.BlockDeviceError(msg)
2118 """Find a DRBD device which matches our config and attach to it.
2120 In case of partially attached (local device matches but no network
2121 setup), we perform the network attach. If successful, we re-test
2122 the attach if can return success.
2125 for minor in self._GetUsedDevs():
2126 info = self._GetDevInfo(self._GetShowData(minor))
2127 match_l = self._MatchesLocal(info)
2128 match_r = self._MatchesNet(info)
2129 if match_l and match_r:
2131 if match_l and not match_r and "local_addr" not in info:
2132 res_r = self._AssembleNet(minor,
2133 (self._lhost, self._lport,
2134 self._rhost, self._rport),
2137 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2139 # the weakest case: we find something that is only net attached
2140 # even though we were passed some children at init time
2141 if match_r and "local_dev" not in info:
2143 if match_l and not match_r and "local_addr" in info:
2144 # strange case - the device network part points to somewhere
2145 # else, even though its local storage is ours; as we own the
2146 # drbd space, we try to disconnect from the remote peer and
2147 # reconnect to our correct one
2148 if not self._ShutdownNet(minor):
2149 raise errors.BlockDeviceError("Device has correct local storage,"
2150 " wrong remote peer and is unable to"
2151 " disconnect in order to attach to"
2152 " the correct peer")
2153 # note: _AssembleNet also handles the case when we don't want
2154 # local storage (i.e. one or more of the _[lr](host|port) is
2156 if (self._AssembleNet(minor, (self._lhost, self._lport,
2157 self._rhost, self._rport), "C") and
2158 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2164 self._SetFromMinor(minor)
2165 return minor is not None
2168 """Assemble the drbd.
2171 - if we have a local backing device, we bind to it by:
2172 - checking the list of used drbd devices
2173 - check if the local minor use of any of them is our own device
2176 - if we have a local/remote net info:
2177 - redo the local backing device step for the remote device
2178 - check if any drbd device is using the local port,
2180 - check if any remote drbd device is using the remote
2181 port, if yes abort (for now)
2183 - bind the remote net port
2187 if self.minor is not None:
2188 logger.Info("Already assembled")
2191 result = super(DRBD8, self).Assemble()
2195 minor = self._FindUnusedMinor()
2196 need_localdev_teardown = False
2197 if self._children and self._children[0] and self._children[1]:
2198 result = self._AssembleLocal(minor, self._children[0].dev_path,
2199 self._children[1].dev_path)
2202 need_localdev_teardown = True
2203 if self._lhost and self._lport and self._rhost and self._rport:
2204 result = self._AssembleNet(minor,
2205 (self._lhost, self._lport,
2206 self._rhost, self._rport),
2209 if need_localdev_teardown:
2210 # we will ignore failures from this
2211 logger.Error("net setup failed, tearing down local device")
2212 self._ShutdownAll(minor)
2214 self._SetFromMinor(minor)
2218 def _ShutdownLocal(cls, minor):
2219 """Detach from the local device.
2221 I/Os will continue to be served from the remote device. If we
2222 don't have a remote device, this operation will fail.
2225 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2227 logger.Error("Can't detach local device: %s" % result.output)
2228 return not result.failed
2231 def _ShutdownNet(cls, minor):
2232 """Disconnect from the remote peer.
2234 This fails if we don't have a local device.
2237 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2239 logger.Error("Can't shutdown network: %s" % result.output)
2240 return not result.failed
2243 def _ShutdownAll(cls, minor):
2244 """Deactivate the device.
2246 This will, of course, fail if the device is in use.
2249 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2251 logger.Error("Can't shutdown drbd device: %s" % result.output)
2252 return not result.failed
2255 """Shutdown the DRBD device.
2258 if self.minor is None and not self.Attach():
2259 logger.Info("DRBD device not attached to a device during Shutdown")
2261 if not self._ShutdownAll(self.minor):
2264 self.dev_path = None
2268 """Stub remove for DRBD devices.
2271 return self.Shutdown()
2274 def Create(cls, unique_id, children, size):
2275 """Create a new DRBD8 device.
2277 Since DRBD devices are not created per se, just assembled, this
2278 function only initializes the metadata.
2281 if len(children) != 2:
2282 raise errors.ProgrammerError("Invalid setup for the drbd device")
2285 if not meta.Attach():
2286 raise errors.BlockDeviceError("Can't attach to meta device")
2287 if not cls._CheckMetaSize(meta.dev_path):
2288 raise errors.BlockDeviceError("Invalid meta device size")
2289 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2290 if not cls._IsValidMeta(meta.dev_path):
2291 raise errors.BlockDeviceError("Cannot initalize meta device")
2292 return cls(unique_id, children)
2296 constants.LD_LV: LogicalVolume,
2297 constants.LD_MD_R1: MDRaid1,
2298 constants.LD_DRBD7: DRBDev,
2299 constants.LD_DRBD8: DRBD8,
2303 def FindDevice(dev_type, unique_id, children):
2304 """Search for an existing, assembled device.
2306 This will succeed only if the device exists and is assembled, but it
2307 does not do any actions in order to activate the device.
2310 if dev_type not in DEV_MAP:
2311 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2312 device = DEV_MAP[dev_type](unique_id, children)
2313 if not device.Attach():
2318 def AttachOrAssemble(dev_type, unique_id, children):
2319 """Try to attach or assemble an existing device.
2321 This will attach to an existing assembled device or will assemble
2322 the device, as needed, to bring it fully up.
2325 if dev_type not in DEV_MAP:
2326 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2327 device = DEV_MAP[dev_type](unique_id, children)
2328 if not device.Attach():
2330 if not device.Attach():
2331 raise errors.BlockDeviceError("Can't find a valid block device for"
2333 (dev_type, unique_id, children))
2337 def Create(dev_type, unique_id, children, size):
2341 if dev_type not in DEV_MAP:
2342 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2343 device = DEV_MAP[dev_type].Create(unique_id, children, size)