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()
125 status = status and child.Open()
128 for child in self._children:
133 """Find a device which matches our config and attach to it.
136 raise NotImplementedError
139 """Notifies that the device will no longer be used for I/O.
142 raise NotImplementedError
145 def Create(cls, unique_id, children, size):
146 """Create the device.
148 If the device cannot be created, it will return None
149 instead. Error messages go to the logging system.
151 Note that for some devices, the unique_id is used, and for other,
152 the children. The idea is that these two, taken together, are
153 enough for both creation and assembly (later).
156 raise NotImplementedError
159 """Remove this device.
161 This makes sense only for some of the device types: LV and to a
162 lesser degree, md devices. Also note that if the device can't
163 attach, the removal can't be completed.
166 raise NotImplementedError
168 def Rename(self, new_id):
169 """Rename this device.
171 This may or may not make sense for a given device type.
174 raise NotImplementedError
177 """Return the status of the device.
180 raise NotImplementedError
182 def Open(self, force=False):
183 """Make the device ready for use.
185 This makes the device ready for I/O. For now, just the DRBD
188 The force parameter signifies that if the device has any kind of
189 --force thing, it should be used, we know what we are doing.
192 raise NotImplementedError
195 """Shut down the device, freeing its children.
197 This undoes the `Assemble()` work, except for the child
198 assembling; as such, the children on the device are still
199 assembled after this call.
202 raise NotImplementedError
204 def SetSyncSpeed(self, speed):
205 """Adjust the sync speed of the mirror.
207 In case this is not a mirroring device, this is no-op.
212 for child in self._children:
213 result = result and child.SetSyncSpeed(speed)
216 def GetSyncStatus(self):
217 """Returns the sync status of the device.
219 If this device is a mirroring device, this function returns the
220 status of the mirror.
223 (sync_percent, estimated_time, is_degraded)
225 If sync_percent is None, it means all is ok
226 If estimated_time is None, it means we can't estimate
227 the time needed, otherwise it's the time left in seconds
228 If is_degraded is True, it means the device is missing
229 redundancy. This is usually a sign that something went wrong in
230 the device setup, if sync_percent is None.
233 return None, None, False
236 def CombinedSyncStatus(self):
237 """Calculate the mirror status recursively for our children.
239 The return value is the same as for `GetSyncStatus()` except the
240 minimum percent and maximum time are calculated across our
244 min_percent, max_time, is_degraded = self.GetSyncStatus()
246 for child in self._children:
247 c_percent, c_time, c_degraded = child.GetSyncStatus()
248 if min_percent is None:
249 min_percent = c_percent
250 elif c_percent is not None:
251 min_percent = min(min_percent, c_percent)
254 elif c_time is not None:
255 max_time = max(max_time, c_time)
256 is_degraded = is_degraded or c_degraded
257 return min_percent, max_time, is_degraded
260 def SetInfo(self, text):
261 """Update metadata with info text.
263 Only supported for some device types.
266 for child in self._children:
271 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
272 (self.__class__, self.unique_id, self._children,
273 self.major, self.minor, self.dev_path))
276 class LogicalVolume(BlockDev):
277 """Logical Volume block device.
280 def __init__(self, unique_id, children):
281 """Attaches to a LV device.
283 The unique_id is a tuple (vg_name, lv_name)
286 super(LogicalVolume, self).__init__(unique_id, children)
287 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
288 raise ValueError("Invalid configuration data %s" % str(unique_id))
289 self._vg_name, self._lv_name = unique_id
290 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
294 def Create(cls, unique_id, children, size):
295 """Create a new logical volume.
298 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
299 raise ValueError("Invalid configuration data %s" % str(unique_id))
300 vg_name, lv_name = unique_id
301 pvs_info = cls.GetPVInfo(vg_name)
303 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
308 pvlist = [ pv[1] for pv in pvs_info ]
309 free_size = sum([ pv[0] for pv in pvs_info ])
311 # The size constraint should have been checked from the master before
312 # calling the create function.
314 raise errors.BlockDeviceError("Not enough free space: required %s,"
315 " available %s" % (size, free_size))
316 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
319 raise errors.BlockDeviceError(result.fail_reason)
320 return LogicalVolume(unique_id, children)
323 def GetPVInfo(vg_name):
324 """Get the free space info for PVs in a volume group.
327 vg_name: the volume group name
330 list of (free_space, name) with free_space in mebibytes
333 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
334 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
336 result = utils.RunCmd(command)
338 logger.Error("Can't get the PV information: %s" % result.fail_reason)
341 for line in result.stdout.splitlines():
342 fields = line.strip().split(':')
344 logger.Error("Can't parse pvs output: line '%s'" % line)
346 # skip over pvs from another vg or ones which are not allocatable
347 if fields[1] != vg_name or fields[3][0] != 'a':
349 data.append((float(fields[2]), fields[0]))
354 """Remove this logical volume.
357 if not self.minor and not self.Attach():
358 # the LV does not exist
360 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
361 (self._vg_name, self._lv_name)])
363 logger.Error("Can't lvremove: %s" % result.fail_reason)
365 return not result.failed
367 def Rename(self, new_id):
368 """Rename this logical volume.
371 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
372 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
373 new_vg, new_name = new_id
374 if new_vg != self._vg_name:
375 raise errors.ProgrammerError("Can't move a logical volume across"
376 " volume groups (from %s to to %s)" %
377 (self._vg_name, new_vg))
378 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
380 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
382 self._lv_name = new_name
383 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
387 """Attach to an existing LV.
389 This method will try to see if an existing and active LV exists
390 which matches the our name. If so, its major/minor will be
394 result = utils.RunCmd(["lvdisplay", self.dev_path])
396 logger.Error("Can't find LV %s: %s" %
397 (self.dev_path, result.fail_reason))
399 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
400 for line in result.stdout.splitlines():
401 match_result = match.match(line)
403 self.major = int(match_result.group(1))
404 self.minor = int(match_result.group(2))
409 """Assemble the device.
411 This is a no-op for the LV device type. Eventually, we could
412 lvchange -ay here if we see that the LV is not active.
418 """Shutdown the device.
420 This is a no-op for the LV device type, as we don't deactivate the
427 """Return the status of the device.
429 Logical volumes will can be in all four states, although we don't
430 deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
431 should not be seen for our devices.
434 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
436 logger.Error("Can't display lv: %s" % result.fail_reason)
437 return self.STATUS_UNKNOWN
438 out = result.stdout.strip()
439 # format: type/permissions/alloc/fixed_minor/state/open
441 return self.STATUS_UNKNOWN
442 #writable = (out[1] == "w")
443 active = (out[4] == "a")
444 online = (out[5] == "o")
446 retval = self.STATUS_ONLINE
448 retval = self.STATUS_STANDBY
450 retval = self.STATUS_EXISTING
454 def GetSyncStatus(self):
455 """Returns the sync status of the device.
457 If this device is a mirroring device, this function returns the
458 status of the mirror.
461 (sync_percent, estimated_time, is_degraded)
463 For logical volumes, sync_percent and estimated_time are always
464 None (no recovery in progress, as we don't handle the mirrored LV
467 For the is_degraded parameter, we check if the logical volume has
468 the 'virtual' type, which means it's not backed by existing
469 storage anymore (read from it return I/O error). This happens
470 after a physical disk failure and subsequent 'vgreduce
471 --removemissing' on the volume group.
474 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
476 logger.Error("Can't display lv: %s" % result.fail_reason)
477 return None, None, True
478 out = result.stdout.strip()
479 # format: type/permissions/alloc/fixed_minor/state/open
481 return None, None, True
482 is_degraded = out[0] == 'v' # virtual volume, i.e. doesn't have
484 return None, None, is_degraded
486 def Open(self, force=False):
487 """Make the device ready for I/O.
489 This is a no-op for the LV device type.
495 """Notifies that the device will no longer be used for I/O.
497 This is a no-op for the LV device type.
502 def Snapshot(self, size):
503 """Create a snapshot copy of an lvm block device.
506 snap_name = self._lv_name + ".snap"
508 # remove existing snapshot if found
509 snap = LogicalVolume((self._vg_name, snap_name), None)
512 pvs_info = self.GetPVInfo(self._vg_name)
514 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
518 free_size, pv_name = pvs_info[0]
520 raise errors.BlockDeviceError("Not enough free space: required %s,"
521 " available %s" % (size, free_size))
523 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
524 "-n%s" % snap_name, self.dev_path])
526 raise errors.BlockDeviceError("command: %s error: %s" %
527 (result.cmd, result.fail_reason))
531 def SetInfo(self, text):
532 """Update metadata with info text.
535 BlockDev.SetInfo(self, text)
537 # Replace invalid characters
538 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
539 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
541 # Only up to 128 characters are allowed
544 result = utils.RunCmd(["lvchange", "--addtag", text,
547 raise errors.BlockDeviceError("Command: %s error: %s" %
548 (result.cmd, result.fail_reason))
551 class MDRaid1(BlockDev):
552 """raid1 device implemented via md.
555 def __init__(self, unique_id, children):
556 super(MDRaid1, self).__init__(unique_id, children)
561 """Find an array which matches our config and attach to it.
563 This tries to find a MD array which has the same UUID as our own.
566 minor = self._FindMDByUUID(self.unique_id)
567 if minor is not None:
568 self._SetFromMinor(minor)
573 return (minor is not None)
577 """Compute the list of in-use MD devices.
579 It doesn't matter if the used device have other raid level, just
580 that they are in use.
583 mdstat = open("/proc/mdstat", "r")
584 data = mdstat.readlines()
588 valid_line = re.compile("^md([0-9]+) : .*$")
590 match = valid_line.match(line)
592 md_no = int(match.group(1))
593 used_md[md_no] = line
598 def _GetDevInfo(minor):
599 """Get info about a MD device.
601 Currently only uuid is returned.
604 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
606 logger.Error("Can't display md: %s" % result.fail_reason)
609 for line in result.stdout.splitlines():
611 kv = line.split(" : ", 1)
614 retval["uuid"] = kv[1].split()[0]
615 elif kv[0] == "State":
616 retval["state"] = kv[1].split(", ")
620 def _FindUnusedMinor():
621 """Compute an unused MD minor.
623 This code assumes that there are 256 minors only.
626 used_md = MDRaid1._GetUsedDevs()
633 logger.Error("Critical: Out of md minor numbers.")
634 raise errors.BlockDeviceError("Can't find a free MD minor")
638 def _FindMDByUUID(cls, uuid):
639 """Find the minor of an MD array with a given UUID.
642 md_list = cls._GetUsedDevs()
643 for minor in md_list:
644 info = cls._GetDevInfo(minor)
645 if info and info["uuid"] == uuid:
650 def _ZeroSuperblock(dev_path):
651 """Zero the possible locations for an MD superblock.
653 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
654 fails in versions 2.x with the same error code as non-writable
657 The superblocks are located at (negative values are relative to
658 the end of the block device):
659 - -128k to end for version 0.90 superblock
660 - -8k to -12k for version 1.0 superblock (included in the above)
661 - 0k to 4k for version 1.1 superblock
662 - 4k to 8k for version 1.2 superblock
664 To cover all situations, the zero-ing will be:
668 As such, the minimum device size must be 128k, otherwise we'll get
671 Note that this function depends on the fact that one can open,
672 read and write block devices normally.
675 overwrite_size = 128 * 1024
676 empty_buf = '\0' * overwrite_size
677 fd = open(dev_path, "r+")
683 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
684 fd.seek(-overwrite_size, 2)
688 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
693 def Create(cls, unique_id, children, size):
694 """Create a new MD raid1 array.
697 if not isinstance(children, (tuple, list)):
698 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
701 if not isinstance(i, BlockDev):
702 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
705 cls._ZeroSuperblock(i.dev_path)
706 except EnvironmentError, err:
707 logger.Error("Can't zero superblock for %s: %s" %
708 (i.dev_path, str(err)))
710 minor = cls._FindUnusedMinor()
711 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
712 "--auto=yes", "--force", "-l1",
713 "-n%d" % len(children)] +
714 [dev.dev_path for dev in children])
717 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
720 info = cls._GetDevInfo(minor)
721 if not info or not "uuid" in info:
722 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
724 return MDRaid1(info["uuid"], children)
727 """Stub remove function for MD RAID 1 arrays.
729 We don't remove the superblock right now. Mark a to do.
732 #TODO: maybe zero superblock on child devices?
733 return self.Shutdown()
735 def Rename(self, new_id):
738 This is not supported for md raid1 devices.
741 raise errors.ProgrammerError("Can't rename a md raid1 device")
743 def AddChildren(self, devices):
744 """Add new member(s) to the md raid1.
747 if self.minor is None and not self.Attach():
748 raise errors.BlockDeviceError("Can't attach to device")
750 args = ["mdadm", "-a", self.dev_path]
752 if dev.dev_path is None:
753 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
755 args.append(dev.dev_path)
756 result = utils.RunCmd(args)
758 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
760 new_len = len(self._children) + len(devices)
761 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
763 raise errors.BlockDeviceError("Can't grow md array: %s" %
765 self._children.extend(devices)
767 def RemoveChildren(self, devices):
768 """Remove member(s) from the md raid1.
771 if self.minor is None and not self.Attach():
772 raise errors.BlockDeviceError("Can't attach to device")
773 new_len = len(self._children) - len(devices)
775 raise errors.BlockDeviceError("Can't reduce to less than one child")
776 args = ["mdadm", "-f", self.dev_path]
780 for c in self._children:
781 if c.dev_path == dev:
785 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
787 result = utils.RunCmd(args)
789 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
792 # it seems here we need a short delay for MD to update its
796 result = utils.RunCmd(args)
798 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
799 " %s" % result.output)
800 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
803 raise errors.BlockDeviceError("Can't shrink md array: %s" %
805 for dev in orig_devs:
806 self._children.remove(dev)
809 """Return the status of the device.
813 if self.minor is None:
814 retval = self.STATUS_UNKNOWN
816 retval = self.STATUS_ONLINE
819 def _SetFromMinor(self, minor):
820 """Set our parameters based on the given minor.
822 This sets our minor variable and our dev_path.
826 self.dev_path = "/dev/md%d" % minor
829 """Assemble the MD device.
831 At this point we should have:
832 - list of children devices
836 result = super(MDRaid1, self).Assemble()
839 md_list = self._GetUsedDevs()
840 for minor in md_list:
841 info = self._GetDevInfo(minor)
842 if info and info["uuid"] == self.unique_id:
843 self._SetFromMinor(minor)
844 logger.Info("MD array %s already started" % str(self))
846 free_minor = self._FindUnusedMinor()
847 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
848 self.unique_id, "/dev/md%d" % free_minor] +
849 [bdev.dev_path for bdev in self._children])
851 logger.Error("Can't assemble MD array: %s: %s" %
852 (result.fail_reason, result.output))
855 self.minor = free_minor
856 return not result.failed
859 """Tear down the MD array.
861 This does a 'mdadm --stop' so after this command, the array is no
865 if self.minor is None and not self.Attach():
866 logger.Info("MD object not attached to a device")
869 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
871 logger.Error("Can't stop MD array: %s" % result.fail_reason)
877 def SetSyncSpeed(self, kbytes):
878 """Set the maximum sync speed for the MD array.
881 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
882 if self.minor is None:
883 logger.Error("MD array not attached to a device")
885 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
887 f.write("%d" % kbytes)
890 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
892 f.write("%d" % (kbytes/2))
897 def GetSyncStatus(self):
898 """Returns the sync status of the device.
901 (sync_percent, estimated_time, is_degraded)
903 If sync_percent is None, it means all is ok
904 If estimated_time is None, it means we can't esimate
905 the time needed, otherwise it's the time left in seconds
908 if self.minor is None and not self.Attach():
909 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
910 dev_info = self._GetDevInfo(self.minor)
911 is_clean = ("state" in dev_info and
912 len(dev_info["state"]) == 1 and
913 dev_info["state"][0] in ("clean", "active"))
914 sys_path = "/sys/block/md%s/md/" % self.minor
915 f = file(sys_path + "sync_action")
916 sync_status = f.readline().strip()
918 if sync_status == "idle":
919 return None, None, not is_clean
920 f = file(sys_path + "sync_completed")
921 sync_completed = f.readline().strip().split(" / ")
923 if len(sync_completed) != 2:
924 return 0, None, not is_clean
925 sync_done, sync_total = [float(i) for i in sync_completed]
926 sync_percent = 100.0*sync_done/sync_total
927 f = file(sys_path + "sync_speed")
928 sync_speed_k = int(f.readline().strip())
929 if sync_speed_k == 0:
932 time_est = (sync_total - sync_done) / 2 / sync_speed_k
933 return sync_percent, time_est, not is_clean
935 def Open(self, force=False):
936 """Make the device ready for I/O.
938 This is a no-op for the MDRaid1 device type, although we could use
939 the 2.6.18's new array_state thing.
945 """Notifies that the device will no longer be used for I/O.
947 This is a no-op for the MDRaid1 device type, but see comment for
954 class BaseDRBD(BlockDev):
957 This class contains a few bits of common functionality between the
958 0.7 and 8.x versions of DRBD.
961 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
962 r" \(api:(\d+)/proto:(\d+)\)")
964 _ST_UNCONFIGURED = "Unconfigured"
965 _ST_WFCONNECTION = "WFConnection"
966 _ST_CONNECTED = "Connected"
970 """Return data from /proc/drbd.
973 stat = open("/proc/drbd", "r")
975 data = stat.read().splitlines()
979 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
983 def _MassageProcData(data):
984 """Transform the output of _GetProdData into a nicer form.
987 a dictionary of minor: joined lines from /proc/drbd for that minor
990 lmatch = re.compile("^ *([0-9]+):.*$")
992 old_minor = old_line = None
994 lresult = lmatch.match(line)
995 if lresult is not None:
996 if old_minor is not None:
997 results[old_minor] = old_line
998 old_minor = int(lresult.group(1))
1001 if old_minor is not None:
1002 old_line += " " + line.strip()
1004 if old_minor is not None:
1005 results[old_minor] = old_line
1009 def _GetVersion(cls):
1010 """Return the DRBD version.
1012 This will return a list [k_major, k_minor, k_point, api, proto].
1015 proc_data = cls._GetProcData()
1016 first_line = proc_data[0].strip()
1017 version = cls._VERSION_RE.match(first_line)
1019 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1021 return [int(val) for val in version.groups()]
1024 def _DevPath(minor):
1025 """Return the path to a drbd device for a given minor.
1028 return "/dev/drbd%d" % minor
1031 def _GetUsedDevs(cls):
1032 """Compute the list of used DRBD devices.
1035 data = cls._GetProcData()
1038 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1040 match = valid_line.match(line)
1043 minor = int(match.group(1))
1044 state = match.group(2)
1045 if state == cls._ST_UNCONFIGURED:
1047 used_devs[minor] = state, line
1051 def _SetFromMinor(self, minor):
1052 """Set our parameters based on the given minor.
1054 This sets our minor variable and our dev_path.
1058 self.minor = self.dev_path = None
1061 self.dev_path = self._DevPath(minor)
1064 def _CheckMetaSize(meta_device):
1065 """Check if the given meta device looks like a valid one.
1067 This currently only check the size, which must be around
1071 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1073 logger.Error("Failed to get device size: %s" % result.fail_reason)
1076 sectors = int(result.stdout)
1078 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1080 bytes = sectors * 512
1081 if bytes < 128 * 1024 * 1024: # less than 128MiB
1082 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1084 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1085 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1089 def Rename(self, new_id):
1092 This is not supported for drbd devices.
1095 raise errors.ProgrammerError("Can't rename a drbd device")
1098 class DRBDev(BaseDRBD):
1099 """DRBD block device.
1101 This implements the local host part of the DRBD device, i.e. it
1102 doesn't do anything to the supposed peer. If you need a fully
1103 connected DRBD pair, you need to use this class on both hosts.
1105 The unique_id for the drbd device is the (local_ip, local_port,
1106 remote_ip, remote_port) tuple, and it must have two children: the
1107 data device and the meta_device. The meta device is checked for
1108 valid size and is zeroed on create.
1111 def __init__(self, unique_id, children):
1112 super(DRBDev, self).__init__(unique_id, children)
1113 self.major = self._DRBD_MAJOR
1114 [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1115 if kmaj != 0 and kmin != 7:
1116 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1117 " requested ganeti usage: kernel is"
1118 " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1120 if len(children) != 2:
1121 raise ValueError("Invalid configuration data %s" % str(children))
1122 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1123 raise ValueError("Invalid configuration data %s" % str(unique_id))
1124 self._lhost, self._lport, self._rhost, self._rport = unique_id
1128 def _FindUnusedMinor(cls):
1129 """Find an unused DRBD device.
1132 data = cls._GetProcData()
1134 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1136 match = valid_line.match(line)
1138 return int(match.group(1))
1139 logger.Error("Error: no free drbd minors!")
1140 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1143 def _GetDevInfo(cls, minor):
1144 """Get details about a given DRBD minor.
1146 This return, if available, the local backing device in (major,
1147 minor) formant and the local and remote (ip, port) information.
1151 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1153 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1156 if out == "Not configured\n":
1158 for line in out.splitlines():
1159 if "local_dev" not in data:
1160 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1162 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1164 if "meta_dev" not in data:
1165 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1167 if match.group(2) is not None and match.group(3) is not None:
1168 # matched on the major/minor
1169 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1171 # matched on the "internal" string
1172 data["meta_dev"] = match.group(1)
1173 # in this case, no meta_index is in the output
1174 data["meta_index"] = -1
1176 if "meta_index" not in data:
1177 match = re.match("^Meta index: ([0-9]+).*$", line)
1179 data["meta_index"] = int(match.group(1))
1181 if "local_addr" not in data:
1182 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1184 data["local_addr"] = (match.group(1), int(match.group(2)))
1186 if "remote_addr" not in data:
1187 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1189 data["remote_addr"] = (match.group(1), int(match.group(2)))
1193 def _MatchesLocal(self, info):
1194 """Test if our local config matches with an existing device.
1196 The parameter should be as returned from `_GetDevInfo()`. This
1197 method tests if our local backing device is the same as the one in
1198 the info parameter, in effect testing if we look like the given
1202 if not ("local_dev" in info and "meta_dev" in info and
1203 "meta_index" in info):
1206 backend = self._children[0]
1207 if backend is not None:
1208 retval = (info["local_dev"] == (backend.major, backend.minor))
1210 retval = (info["local_dev"] == (0, 0))
1211 meta = self._children[1]
1212 if meta is not None:
1213 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1214 retval = retval and (info["meta_index"] == 0)
1216 retval = retval and (info["meta_dev"] == "internal" and
1217 info["meta_index"] == -1)
1220 def _MatchesNet(self, info):
1221 """Test if our network config matches with an existing device.
1223 The parameter should be as returned from `_GetDevInfo()`. This
1224 method tests if our network configuration is the same as the one
1225 in the info parameter, in effect testing if we look like the given
1229 if (((self._lhost is None and not ("local_addr" in info)) and
1230 (self._rhost is None and not ("remote_addr" in info)))):
1233 if self._lhost is None:
1236 if not ("local_addr" in info and
1237 "remote_addr" in info):
1240 retval = (info["local_addr"] == (self._lhost, self._lport))
1241 retval = (retval and
1242 info["remote_addr"] == (self._rhost, self._rport))
1246 def _AssembleLocal(cls, minor, backend, meta):
1247 """Configure the local part of a DRBD device.
1249 This is the first thing that must be done on an unconfigured DRBD
1250 device. And it must be done only once.
1253 if not cls._CheckMetaSize(meta):
1255 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1256 backend, meta, "0", "-e", "detach"])
1258 logger.Error("Can't attach local disk: %s" % result.output)
1259 return not result.failed
1262 def _ShutdownLocal(cls, minor):
1263 """Detach from the local device.
1265 I/Os will continue to be served from the remote device. If we
1266 don't have a remote device, this operation will fail.
1269 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1271 logger.Error("Can't detach local device: %s" % result.output)
1272 return not result.failed
1275 def _ShutdownAll(minor):
1276 """Deactivate the device.
1278 This will, of course, fail if the device is in use.
1281 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1283 logger.Error("Can't shutdown drbd device: %s" % result.output)
1284 return not result.failed
1287 def _AssembleNet(cls, minor, net_info, protocol):
1288 """Configure the network part of the device.
1290 This operation can be, in theory, done multiple times, but there
1291 have been cases (in lab testing) in which the network part of the
1292 device had become stuck and couldn't be shut down because activity
1293 from the new peer (also stuck) triggered a timer re-init and
1294 needed remote peer interface shutdown in order to clear. So please
1295 don't change online the net config.
1298 lhost, lport, rhost, rport = net_info
1299 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1300 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1303 logger.Error("Can't setup network for dbrd device: %s" %
1307 timeout = time.time() + 10
1309 while time.time() < timeout:
1310 info = cls._GetDevInfo(minor)
1311 if not "local_addr" in info or not "remote_addr" in info:
1314 if (info["local_addr"] != (lhost, lport) or
1315 info["remote_addr"] != (rhost, rport)):
1321 logger.Error("Timeout while configuring network")
1326 def _ShutdownNet(cls, minor):
1327 """Disconnect from the remote peer.
1329 This fails if we don't have a local device.
1332 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1333 logger.Error("Can't shutdown network: %s" % result.output)
1334 return not result.failed
1337 """Assemble the drbd.
1340 - if we have a local backing device, we bind to it by:
1341 - checking the list of used drbd devices
1342 - check if the local minor use of any of them is our own device
1345 - if we have a local/remote net info:
1346 - redo the local backing device step for the remote device
1347 - check if any drbd device is using the local port,
1349 - check if any remote drbd device is using the remote
1350 port, if yes abort (for now)
1352 - bind the remote net port
1356 if self.minor is not None:
1357 logger.Info("Already assembled")
1360 result = super(DRBDev, self).Assemble()
1364 minor = self._FindUnusedMinor()
1365 need_localdev_teardown = False
1366 if self._children[0]:
1367 result = self._AssembleLocal(minor, self._children[0].dev_path,
1368 self._children[1].dev_path)
1371 need_localdev_teardown = True
1372 if self._lhost and self._lport and self._rhost and self._rport:
1373 result = self._AssembleNet(minor,
1374 (self._lhost, self._lport,
1375 self._rhost, self._rport),
1378 if need_localdev_teardown:
1379 # we will ignore failures from this
1380 logger.Error("net setup failed, tearing down local device")
1381 self._ShutdownAll(minor)
1383 self._SetFromMinor(minor)
1387 """Shutdown the DRBD device.
1390 if self.minor is None and not self.Attach():
1391 logger.Info("DRBD device not attached to a device during Shutdown")
1393 if not self._ShutdownAll(self.minor):
1396 self.dev_path = None
1400 """Find a DRBD device which matches our config and attach to it.
1402 In case of partially attached (local device matches but no network
1403 setup), we perform the network attach. If successful, we re-test
1404 the attach if can return success.
1407 for minor in self._GetUsedDevs():
1408 info = self._GetDevInfo(minor)
1409 match_l = self._MatchesLocal(info)
1410 match_r = self._MatchesNet(info)
1411 if match_l and match_r:
1413 if match_l and not match_r and "local_addr" not in info:
1414 res_r = self._AssembleNet(minor,
1415 (self._lhost, self._lport,
1416 self._rhost, self._rport),
1418 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1423 self._SetFromMinor(minor)
1424 return minor is not None
1426 def Open(self, force=False):
1427 """Make the local state primary.
1429 If the 'force' parameter is given, the '--do-what-I-say' parameter
1430 is given. Since this is a pottentialy dangerous operation, the
1431 force flag should be only given after creation, when it actually
1435 if self.minor is None and not self.Attach():
1436 logger.Error("DRBD cannot attach to a device during open")
1438 cmd = ["drbdsetup", self.dev_path, "primary"]
1440 cmd.append("--do-what-I-say")
1441 result = utils.RunCmd(cmd)
1443 logger.Error("Can't make drbd device primary: %s" % result.output)
1448 """Make the local state secondary.
1450 This will, of course, fail if the device is in use.
1453 if self.minor is None and not self.Attach():
1454 logger.Info("Instance not attached to a device")
1455 raise errors.BlockDeviceError("Can't find device")
1456 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1458 logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1459 raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1461 def SetSyncSpeed(self, kbytes):
1462 """Set the speed of the DRBD syncer.
1465 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1466 if self.minor is None:
1467 logger.Info("Instance not attached to a device")
1469 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1472 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1473 return not result.failed and children_result
1475 def GetSyncStatus(self):
1476 """Returns the sync status of the device.
1479 (sync_percent, estimated_time, is_degraded)
1481 If sync_percent is None, it means all is ok
1482 If estimated_time is None, it means we can't esimate
1483 the time needed, otherwise it's the time left in seconds
1486 if self.minor is None and not self.Attach():
1487 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1488 proc_info = self._MassageProcData(self._GetProcData())
1489 if self.minor not in proc_info:
1490 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1492 line = proc_info[self.minor]
1493 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1494 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1496 sync_percent = float(match.group(1))
1497 hours = int(match.group(2))
1498 minutes = int(match.group(3))
1499 seconds = int(match.group(4))
1500 est_time = hours * 3600 + minutes * 60 + seconds
1504 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1506 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1508 client_state = match.group(1)
1509 is_degraded = client_state != "Connected"
1510 return sync_percent, est_time, is_degraded
1512 def GetStatus(self):
1513 """Compute the status of the DRBD device
1515 Note that DRBD devices don't have the STATUS_EXISTING state.
1518 if self.minor is None and not self.Attach():
1519 return self.STATUS_UNKNOWN
1521 data = self._GetProcData()
1522 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1525 mresult = match.match(line)
1529 logger.Error("Can't find myself!")
1530 return self.STATUS_UNKNOWN
1532 state = mresult.group(2)
1533 if state == "Primary":
1534 result = self.STATUS_ONLINE
1536 result = self.STATUS_STANDBY
1541 def _ZeroDevice(device):
1544 This writes until we get ENOSPC.
1547 f = open(device, "w")
1548 buf = "\0" * 1048576
1552 except IOError, err:
1553 if err.errno != errno.ENOSPC:
1557 def Create(cls, unique_id, children, size):
1558 """Create a new DRBD device.
1560 Since DRBD devices are not created per se, just assembled, this
1561 function just zeroes the meta device.
1564 if len(children) != 2:
1565 raise errors.ProgrammerError("Invalid setup for the drbd device")
1568 if not meta.Attach():
1569 raise errors.BlockDeviceError("Can't attach to meta device")
1570 if not cls._CheckMetaSize(meta.dev_path):
1571 raise errors.BlockDeviceError("Invalid meta device")
1572 logger.Info("Started zeroing device %s" % meta.dev_path)
1573 cls._ZeroDevice(meta.dev_path)
1574 logger.Info("Done zeroing device %s" % meta.dev_path)
1575 return cls(unique_id, children)
1578 """Stub remove for DRBD devices.
1581 return self.Shutdown()
1584 class DRBD8(BaseDRBD):
1585 """DRBD v8.x block device.
1587 This implements the local host part of the DRBD device, i.e. it
1588 doesn't do anything to the supposed peer. If you need a fully
1589 connected DRBD pair, you need to use this class on both hosts.
1591 The unique_id for the drbd device is the (local_ip, local_port,
1592 remote_ip, remote_port) tuple, and it must have two children: the
1593 data device and the meta_device. The meta device is checked for
1594 valid size and is zeroed on create.
1600 def __init__(self, unique_id, children):
1601 if children and children.count(None) > 0:
1603 super(DRBD8, self).__init__(unique_id, children)
1604 self.major = self._DRBD_MAJOR
1605 [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1607 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1608 " requested ganeti usage: kernel is"
1609 " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1611 if len(children) not in (0, 2):
1612 raise ValueError("Invalid configuration data %s" % str(children))
1613 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1614 raise ValueError("Invalid configuration data %s" % str(unique_id))
1615 self._lhost, self._lport, self._rhost, self._rport = unique_id
1619 def _InitMeta(cls, minor, dev_path):
1620 """Initialize a meta device.
1622 This will not work if the given minor is in use.
1625 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1626 "v08", dev_path, "0", "create-md"])
1628 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1632 def _FindUnusedMinor(cls):
1633 """Find an unused DRBD device.
1635 This is specific to 8.x as the minors are allocated dynamically,
1636 so non-existing numbers up to a max minor count are actually free.
1639 data = cls._GetProcData()
1641 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1642 used_line = re.compile("^ *([0-9]+): cs:")
1645 match = unused_line.match(line)
1647 return int(match.group(1))
1648 match = used_line.match(line)
1650 minor = int(match.group(1))
1651 highest = max(highest, minor)
1652 if highest is None: # there are no minors in use at all
1654 if highest >= cls._MAX_MINORS:
1655 logger.Error("Error: no free drbd minors!")
1656 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1660 def _IsValidMeta(cls, meta_device):
1661 """Check if the given meta device looks like a valid one.
1664 minor = cls._FindUnusedMinor()
1665 minor_path = cls._DevPath(minor)
1666 result = utils.RunCmd(["drbdmeta", minor_path,
1667 "v08", meta_device, "0",
1670 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1675 def _GetShowParser(cls):
1676 """Return a parser for `drbd show` output.
1678 This will either create or return an already-create parser for the
1679 output of the command `drbd show`.
1682 if cls._PARSE_SHOW is not None:
1683 return cls._PARSE_SHOW
1686 lbrace = pyp.Literal("{").suppress()
1687 rbrace = pyp.Literal("}").suppress()
1688 semi = pyp.Literal(";").suppress()
1689 # this also converts the value to an int
1690 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1692 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1693 defa = pyp.Literal("_is_default").suppress()
1694 dbl_quote = pyp.Literal('"').suppress()
1696 keyword = pyp.Word(pyp.alphanums + '-')
1699 value = pyp.Word(pyp.alphanums + '_-/.:')
1700 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1701 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1703 # meta device, extended syntax
1704 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1705 number + pyp.Word(']').suppress())
1708 stmt = (~rbrace + keyword + ~lbrace +
1709 (addr_port ^ value ^ quoted ^ meta_value) +
1710 pyp.Optional(defa) + semi +
1711 pyp.Optional(pyp.restOfLine).suppress())
1714 section_name = pyp.Word(pyp.alphas + '_')
1715 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1717 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1720 cls._PARSE_SHOW = bnf
1725 def _GetDevInfo(cls, minor):
1726 """Get details about a given DRBD minor.
1728 This return, if available, the local backing device (as a path)
1729 and the local and remote (ip, port) information.
1733 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1735 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1741 bnf = cls._GetShowParser()
1745 results = bnf.parseString(out)
1746 except pyp.ParseException, err:
1747 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1750 # and massage the results into our desired format
1751 for section in results:
1753 if sname == "_this_host":
1754 for lst in section[1:]:
1755 if lst[0] == "disk":
1756 data["local_dev"] = lst[1]
1757 elif lst[0] == "meta-disk":
1758 data["meta_dev"] = lst[1]
1759 data["meta_index"] = lst[2]
1760 elif lst[0] == "address":
1761 data["local_addr"] = tuple(lst[1:])
1762 elif sname == "_remote_host":
1763 for lst in section[1:]:
1764 if lst[0] == "address":
1765 data["remote_addr"] = tuple(lst[1:])
1768 def _MatchesLocal(self, info):
1769 """Test if our local config matches with an existing device.
1771 The parameter should be as returned from `_GetDevInfo()`. This
1772 method tests if our local backing device is the same as the one in
1773 the info parameter, in effect testing if we look like the given
1778 backend, meta = self._children
1780 backend = meta = None
1782 if backend is not None:
1783 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1785 retval = ("local_dev" not in info)
1787 if meta is not None:
1788 retval = retval and ("meta_dev" in info and
1789 info["meta_dev"] == meta.dev_path)
1790 retval = retval and ("meta_index" in info and
1791 info["meta_index"] == 0)
1793 retval = retval and ("meta_dev" not in info and
1794 "meta_index" not in info)
1797 def _MatchesNet(self, info):
1798 """Test if our network config matches with an existing device.
1800 The parameter should be as returned from `_GetDevInfo()`. This
1801 method tests if our network configuration is the same as the one
1802 in the info parameter, in effect testing if we look like the given
1806 if (((self._lhost is None and not ("local_addr" in info)) and
1807 (self._rhost is None and not ("remote_addr" in info)))):
1810 if self._lhost is None:
1813 if not ("local_addr" in info and
1814 "remote_addr" in info):
1817 retval = (info["local_addr"] == (self._lhost, self._lport))
1818 retval = (retval and
1819 info["remote_addr"] == (self._rhost, self._rport))
1823 def _AssembleLocal(cls, minor, backend, meta):
1824 """Configure the local part of a DRBD device.
1826 This is the first thing that must be done on an unconfigured DRBD
1827 device. And it must be done only once.
1830 if not cls._IsValidMeta(meta):
1832 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1833 backend, meta, "0", "-e", "detach",
1836 logger.Error("Can't attach local disk: %s" % result.output)
1837 return not result.failed
1840 def _AssembleNet(cls, minor, net_info, protocol,
1841 dual_pri=False, hmac=None, secret=None):
1842 """Configure the network part of the device.
1845 lhost, lport, rhost, rport = net_info
1846 args = ["drbdsetup", cls._DevPath(minor), "net",
1847 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1848 "-A", "discard-zero-changes",
1854 args.extend(["-a", hmac, "-x", secret])
1855 result = utils.RunCmd(args)
1857 logger.Error("Can't setup network for dbrd device: %s" %
1861 timeout = time.time() + 10
1863 while time.time() < timeout:
1864 info = cls._GetDevInfo(minor)
1865 if not "local_addr" in info or not "remote_addr" in info:
1868 if (info["local_addr"] != (lhost, lport) or
1869 info["remote_addr"] != (rhost, rport)):
1875 logger.Error("Timeout while configuring network")
1879 def AddChildren(self, devices):
1880 """Add a disk to the DRBD device.
1883 if self.minor is None:
1884 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1885 if len(devices) != 2:
1886 raise errors.BlockDeviceError("Need two devices for AddChildren")
1887 info = self._GetDevInfo(self.minor)
1888 if "local_dev" in info:
1889 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1890 backend, meta = devices
1891 if backend.dev_path is None or meta.dev_path is None:
1892 raise errors.BlockDeviceError("Children not ready during AddChildren")
1895 if not self._CheckMetaSize(meta.dev_path):
1896 raise errors.BlockDeviceError("Invalid meta device size")
1897 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1898 if not self._IsValidMeta(meta.dev_path):
1899 raise errors.BlockDeviceError("Cannot initalize meta device")
1901 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1902 raise errors.BlockDeviceError("Can't attach to local storage")
1903 self._children = devices
1905 def RemoveChildren(self, devices):
1906 """Detach the drbd device from local storage.
1909 if self.minor is None:
1910 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1912 # early return if we don't actually have backing storage
1913 info = self._GetDevInfo(self.minor)
1914 if "local_dev" not in info:
1916 if len(self._children) != 2:
1917 raise errors.BlockDeviceError("We don't have two children: %s" %
1919 if self._children.count(None) == 2: # we don't actually have children :)
1920 logger.Error("Requested detach while detached")
1922 if len(devices) != 2:
1923 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1924 for child, dev in zip(self._children, devices):
1925 if dev != child.dev_path:
1926 raise errors.BlockDeviceError("Mismatch in local storage"
1927 " (%s != %s) in RemoveChildren" %
1928 (dev, child.dev_path))
1930 if not self._ShutdownLocal(self.minor):
1931 raise errors.BlockDeviceError("Can't detach from local storage")
1934 def SetSyncSpeed(self, kbytes):
1935 """Set the speed of the DRBD syncer.
1938 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1939 if self.minor is None:
1940 logger.Info("Instance not attached to a device")
1942 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1945 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1946 return not result.failed and children_result
1948 def GetSyncStatus(self):
1949 """Returns the sync status of the device.
1952 (sync_percent, estimated_time, is_degraded)
1954 If sync_percent is None, it means all is ok
1955 If estimated_time is None, it means we can't esimate
1956 the time needed, otherwise it's the time left in seconds
1959 if self.minor is None and not self.Attach():
1960 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1961 proc_info = self._MassageProcData(self._GetProcData())
1962 if self.minor not in proc_info:
1963 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1965 line = proc_info[self.minor]
1966 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1967 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1969 sync_percent = float(match.group(1))
1970 hours = int(match.group(2))
1971 minutes = int(match.group(3))
1972 seconds = int(match.group(4))
1973 est_time = hours * 3600 + minutes * 60 + seconds
1977 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1979 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1981 client_state = match.group(1)
1982 local_disk_state = match.group(2)
1983 is_degraded = (client_state != "Connected" or
1984 local_disk_state != "UpToDate")
1985 return sync_percent, est_time, is_degraded
1987 def GetStatus(self):
1988 """Compute the status of the DRBD device
1990 Note that DRBD devices don't have the STATUS_EXISTING state.
1993 if self.minor is None and not self.Attach():
1994 return self.STATUS_UNKNOWN
1996 data = self._GetProcData()
1997 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2000 mresult = match.match(line)
2004 logger.Error("Can't find myself!")
2005 return self.STATUS_UNKNOWN
2007 state = mresult.group(2)
2008 if state == "Primary":
2009 result = self.STATUS_ONLINE
2011 result = self.STATUS_STANDBY
2015 def Open(self, force=False):
2016 """Make the local state primary.
2018 If the 'force' parameter is given, the '--do-what-I-say' parameter
2019 is given. Since this is a pottentialy dangerous operation, the
2020 force flag should be only given after creation, when it actually
2024 if self.minor is None and not self.Attach():
2025 logger.Error("DRBD cannot attach to a device during open")
2027 cmd = ["drbdsetup", self.dev_path, "primary"]
2030 result = utils.RunCmd(cmd)
2032 logger.Error("Can't make drbd device primary: %s" % result.output)
2037 """Make the local state secondary.
2039 This will, of course, fail if the device is in use.
2042 if self.minor is None and not self.Attach():
2043 logger.Info("Instance not attached to a device")
2044 raise errors.BlockDeviceError("Can't find device")
2045 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2047 logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2048 raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2051 """Find a DRBD device which matches our config and attach to it.
2053 In case of partially attached (local device matches but no network
2054 setup), we perform the network attach. If successful, we re-test
2055 the attach if can return success.
2058 for minor in self._GetUsedDevs():
2059 info = self._GetDevInfo(minor)
2060 match_l = self._MatchesLocal(info)
2061 match_r = self._MatchesNet(info)
2062 if match_l and match_r:
2064 if match_l and not match_r and "local_addr" not in info:
2065 res_r = self._AssembleNet(minor,
2066 (self._lhost, self._lport,
2067 self._rhost, self._rport),
2069 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
2071 # the weakest case: we find something that is only net attached
2072 # even though we were passed some children at init time
2073 if match_r and "local_dev" not in info:
2078 self._SetFromMinor(minor)
2079 return minor is not None
2082 """Assemble the drbd.
2085 - if we have a local backing device, we bind to it by:
2086 - checking the list of used drbd devices
2087 - check if the local minor use of any of them is our own device
2090 - if we have a local/remote net info:
2091 - redo the local backing device step for the remote device
2092 - check if any drbd device is using the local port,
2094 - check if any remote drbd device is using the remote
2095 port, if yes abort (for now)
2097 - bind the remote net port
2101 if self.minor is not None:
2102 logger.Info("Already assembled")
2105 result = super(DRBD8, self).Assemble()
2109 minor = self._FindUnusedMinor()
2110 need_localdev_teardown = False
2111 if self._children and self._children[0] and self._children[1]:
2112 result = self._AssembleLocal(minor, self._children[0].dev_path,
2113 self._children[1].dev_path)
2116 need_localdev_teardown = True
2117 if self._lhost and self._lport and self._rhost and self._rport:
2118 result = self._AssembleNet(minor,
2119 (self._lhost, self._lport,
2120 self._rhost, self._rport),
2123 if need_localdev_teardown:
2124 # we will ignore failures from this
2125 logger.Error("net setup failed, tearing down local device")
2126 self._ShutdownAll(minor)
2128 self._SetFromMinor(minor)
2132 def _ShutdownLocal(cls, minor):
2133 """Detach from the local device.
2135 I/Os will continue to be served from the remote device. If we
2136 don't have a remote device, this operation will fail.
2139 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2141 logger.Error("Can't detach local device: %s" % result.output)
2142 return not result.failed
2145 def _ShutdownNet(cls, minor):
2146 """Disconnect from the remote peer.
2148 This fails if we don't have a local device.
2151 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2152 logger.Error("Can't shutdown network: %s" % result.output)
2153 return not result.failed
2156 def _ShutdownAll(cls, minor):
2157 """Deactivate the device.
2159 This will, of course, fail if the device is in use.
2162 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2164 logger.Error("Can't shutdown drbd device: %s" % result.output)
2165 return not result.failed
2168 """Shutdown the DRBD device.
2171 if self.minor is None and not self.Attach():
2172 logger.Info("DRBD device not attached to a device during Shutdown")
2174 if not self._ShutdownAll(self.minor):
2177 self.dev_path = None
2180 def Rename(self, new_uid):
2181 """Re-connect this device to another peer.
2184 if self.minor is None:
2185 raise errors.BlockDeviceError("Device not attached during rename")
2186 if self._rhost is not None:
2187 # this means we did have a host when we attached, so we are connected
2188 if not self._ShutdownNet(self.minor):
2189 raise errors.BlockDeviceError("Can't disconnect from remote peer")
2190 old_id = self.unique_id
2193 self.unique_id = new_uid
2194 if not self._AssembleNet(self.minor, self.unique_id, "C"):
2195 logger.Error("Can't attach to new peer!")
2196 if old_id is not None:
2197 self._AssembleNet(self.minor, old_id, "C")
2198 self.unique_id = old_id
2199 raise errors.BlockDeviceError("Can't attach to new peer")
2202 """Stub remove for DRBD devices.
2205 return self.Shutdown()
2208 def Create(cls, unique_id, children, size):
2209 """Create a new DRBD8 device.
2211 Since DRBD devices are not created per se, just assembled, this
2212 function only initializes the metadata.
2215 if len(children) != 2:
2216 raise errors.ProgrammerError("Invalid setup for the drbd device")
2219 if not meta.Attach():
2220 raise errors.BlockDeviceError("Can't attach to meta device")
2221 if not cls._CheckMetaSize(meta.dev_path):
2222 raise errors.BlockDeviceError("Invalid meta device size")
2223 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2224 if not cls._IsValidMeta(meta.dev_path):
2225 raise errors.BlockDeviceError("Cannot initalize meta device")
2226 return cls(unique_id, children)
2230 constants.LD_LV: LogicalVolume,
2231 constants.LD_MD_R1: MDRaid1,
2232 constants.LD_DRBD7: DRBDev,
2233 constants.LD_DRBD8: DRBD8,
2237 def FindDevice(dev_type, unique_id, children):
2238 """Search for an existing, assembled device.
2240 This will succeed only if the device exists and is assembled, but it
2241 does not do any actions in order to activate the device.
2244 if dev_type not in DEV_MAP:
2245 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2246 device = DEV_MAP[dev_type](unique_id, children)
2247 if not device.Attach():
2252 def AttachOrAssemble(dev_type, unique_id, children):
2253 """Try to attach or assemble an existing device.
2255 This will attach to an existing assembled device or will assemble
2256 the device, as needed, to bring it fully up.
2259 if dev_type not in DEV_MAP:
2260 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2261 device = DEV_MAP[dev_type](unique_id, children)
2262 if not device.Attach():
2264 if not device.Attach():
2265 raise errors.BlockDeviceError("Can't find a valid block device for"
2267 (dev_type, unique_id, children))
2271 def Create(dev_type, unique_id, children, size):
2275 if dev_type not in DEV_MAP:
2276 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2277 device = DEV_MAP[dev_type].Create(unique_id, children, size)