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"""
28 from ganeti import utils
29 from ganeti import logger
30 from ganeti import errors
33 class BlockDev(object):
34 """Block device abstract class.
36 A block device can be in the following states:
37 - not existing on the system, and by `Create()` it goes into:
38 - existing but not setup/not active, and by `Assemble()` goes into:
39 - active read-write and by `Open()` it goes into
40 - online (=used, or ready for use)
42 A device can also be online but read-only, however we are not using
43 the readonly state (MD and LV have it, if needed in the future)
44 and we are usually looking at this like at a stack, so it's easier
45 to conceptualise the transition from not-existing to online and back
48 The many different states of the device are due to the fact that we
49 need to cover many device types:
50 - logical volumes are created, lvchange -a y $lv, and used
51 - md arrays are created or assembled and used
52 - drbd devices are attached to a local disk/remote peer and made primary
54 The status of the device can be examined by `GetStatus()`, which
55 returns a numerical value, depending on the position in the
56 transition stack of the device.
58 A block device is identified by three items:
59 - the /dev path of the device (dynamic)
60 - a unique ID of the device (static)
61 - it's major/minor pair (dynamic)
63 Not all devices implement both the first two as distinct items. LVM
64 logical volumes have their unique ID (the pair volume group, logical
65 volume name) in a 1-to-1 relation to the dev path. For MD devices,
66 the /dev path is dynamic and the unique ID is the UUID generated at
67 array creation plus the slave list. For DRBD devices, the /dev path
68 is again dynamic and the unique id is the pair (host1, dev1),
71 You can get to a device in two ways:
72 - creating the (real) device, which returns you
73 an attached instance (lvcreate, mdadm --create)
74 - attaching of a python instance to an existing (real) device
76 The second point, the attachement to a device, is different
77 depending on whether the device is assembled or not. At init() time,
78 we search for a device with the same unique_id as us. If found,
79 good. It also means that the device is already assembled. If not,
80 after assembly we'll have our correct major/minor.
89 STATUS_UNKNOWN: "unknown",
90 STATUS_EXISTING: "existing",
91 STATUS_STANDBY: "ready for use",
92 STATUS_ONLINE: "online",
96 def __init__(self, unique_id, children):
97 self._children = children
99 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:
134 """Find a device which matches our config and attach to it.
137 raise NotImplementedError
141 """Notifies that the device will no longer be used for I/O.
144 raise NotImplementedError
148 def Create(cls, unique_id, children, size):
149 """Create the device.
151 If the device cannot be created, it will return None
152 instead. Error messages go to the logging system.
154 Note that for some devices, the unique_id is used, and for other,
155 the children. The idea is that these two, taken together, are
156 enough for both creation and assembly (later).
159 raise NotImplementedError
163 """Remove this device.
165 This makes sense only for some of the device types: LV and to a
166 lesser degree, md devices. Also note that if the device can't
167 attach, the removal can't be completed.
170 raise NotImplementedError
174 """Return the status of the device.
177 raise NotImplementedError
180 def Open(self, force=False):
181 """Make the device ready for use.
183 This makes the device ready for I/O. For now, just the DRBD
186 The force parameter signifies that if the device has any kind of
187 --force thing, it should be used, we know what we are doing.
190 raise NotImplementedError
194 """Shut down the device, freeing its children.
196 This undoes the `Assemble()` work, except for the child
197 assembling; as such, the children on the device are still
198 assembled after this call.
201 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)
217 def GetSyncStatus(self):
218 """Returns the sync status of the device.
220 If this device is a mirroring device, this function returns the
221 status of the mirror.
224 (sync_percent, estimated_time, is_degraded)
226 If sync_percent is None, it means all is ok
227 If estimated_time is None, it means we can't estimate
228 the time needed, otherwise it's the time left in seconds
229 If is_degraded is True, it means the device is missing
230 redundancy. This is usually a sign that something went wrong in
231 the device setup, if sync_percent is None.
234 return None, None, False
237 def CombinedSyncStatus(self):
238 """Calculate the mirror status recursively for our children.
240 The return value is the same as for `GetSyncStatus()` except the
241 minimum percent and maximum time are calculated across our
245 min_percent, max_time, is_degraded = self.GetSyncStatus()
247 for child in self._children:
248 c_percent, c_time, c_degraded = child.GetSyncStatus()
249 if min_percent is None:
250 min_percent = c_percent
251 elif c_percent is not None:
252 min_percent = min(min_percent, c_percent)
255 elif c_time is not None:
256 max_time = max(max_time, c_time)
257 is_degraded = is_degraded or c_degraded
258 return min_percent, max_time, is_degraded
262 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
263 (self.__class__, self.unique_id, self._children,
264 self.major, self.minor, self.dev_path))
267 class LogicalVolume(BlockDev):
268 """Logical Volume block device.
271 def __init__(self, unique_id, children):
272 """Attaches to a LV device.
274 The unique_id is a tuple (vg_name, lv_name)
277 super(LogicalVolume, self).__init__(unique_id, children)
278 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
279 raise ValueError("Invalid configuration data %s" % str(unique_id))
280 self._vg_name, self._lv_name = unique_id
281 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
286 def Create(cls, unique_id, children, size):
287 """Create a new logical volume.
290 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
291 raise ValueError("Invalid configuration data %s" % str(unique_id))
292 vg_name, lv_name = unique_id
293 pvs_info = cls.GetPVInfo(vg_name)
295 raise errors.BlockDeviceError, ("Can't compute PV info for vg %s" %
300 pvlist = [ pv[1] for pv in pvs_info ]
301 free_size = sum([ pv[0] for pv in pvs_info ])
303 # The size constraint should have been checked from the master before
304 # calling the create function.
306 raise errors.BlockDeviceError, ("Not enough free space: required %s,"
307 " available %s" % (size, free_size))
308 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
311 raise errors.BlockDeviceError(result.fail_reason)
312 return LogicalVolume(unique_id, children)
315 def GetPVInfo(vg_name):
316 """Get the free space info for PVs in a volume group.
319 vg_name: the volume group name
322 list of (free_space, name) with free_space in mebibytes
325 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
326 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
328 result = utils.RunCmd(command)
330 logger.Error("Can't get the PV information: %s" % result.fail_reason)
333 for line in result.stdout.splitlines():
334 fields = line.strip().split(':')
336 logger.Error("Can't parse pvs output: line '%s'" % line)
338 # skip over pvs from another vg or ones which are not allocatable
339 if fields[1] != vg_name or fields[3][0] != 'a':
341 data.append((float(fields[2]), fields[0]))
346 """Remove this logical volume.
349 if not self.minor and not self.Attach():
350 # the LV does not exist
352 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
353 (self._vg_name, self._lv_name)])
355 logger.Error("Can't lvremove: %s" % result.fail_reason)
357 return not result.failed
361 """Attach to an existing LV.
363 This method will try to see if an existing and active LV exists
364 which matches the our name. If so, its major/minor will be
368 result = utils.RunCmd(["lvdisplay", self.dev_path])
370 logger.Error("Can't find LV %s: %s" %
371 (self.dev_path, result.fail_reason))
373 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
374 for line in result.stdout.splitlines():
375 match_result = match.match(line)
377 self.major = int(match_result.group(1))
378 self.minor = int(match_result.group(2))
384 """Assemble the device.
386 This is a no-op for the LV device type. Eventually, we could
387 lvchange -ay here if we see that the LV is not active.
394 """Shutdown the device.
396 This is a no-op for the LV device type, as we don't deactivate the
404 """Return the status of the device.
406 Logical volumes will can be in all four states, although we don't
407 deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
408 should not be seen for our devices.
411 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
413 logger.Error("Can't display lv: %s" % result.fail_reason)
414 return self.STATUS_UNKNOWN
415 out = result.stdout.strip()
416 # format: type/permissions/alloc/fixed_minor/state/open
418 return self.STATUS_UNKNOWN
419 #writable = (out[1] == "w")
420 active = (out[4] == "a")
421 online = (out[5] == "o")
423 retval = self.STATUS_ONLINE
425 retval = self.STATUS_STANDBY
427 retval = self.STATUS_EXISTING
432 def Open(self, force=False):
433 """Make the device ready for I/O.
435 This is a no-op for the LV device type.
442 """Notifies that the device will no longer be used for I/O.
444 This is a no-op for the LV device type.
450 def Snapshot(self, size):
451 """Create a snapshot copy of an lvm block device.
454 snap_name = self._lv_name + ".snap"
456 # remove existing snapshot if found
457 snap = LogicalVolume((self._vg_name, snap_name), None)
460 pvs_info = self.GetPVInfo(self._vg_name)
462 raise errors.BlockDeviceError, ("Can't compute PV info for vg %s" %
466 free_size, pv_name = pvs_info[0]
468 raise errors.BlockDeviceError, ("Not enough free space: required %s,"
469 " available %s" % (size, free_size))
471 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
472 "-n%s" % snap_name, self.dev_path])
474 raise errors.BlockDeviceError, ("command: %s error: %s" %
475 (result.cmd, result.fail_reason))
480 class MDRaid1(BlockDev):
481 """raid1 device implemented via md.
484 def __init__(self, unique_id, children):
485 super(MDRaid1, self).__init__(unique_id, children)
491 """Find an array which matches our config and attach to it.
493 This tries to find a MD array which has the same UUID as our own.
496 minor = self._FindMDByUUID(self.unique_id)
497 if minor is not None:
498 self._SetFromMinor(minor)
503 return (minor is not None)
508 """Compute the list of in-use MD devices.
510 It doesn't matter if the used device have other raid level, just
511 that they are in use.
514 mdstat = open("/proc/mdstat", "r")
515 data = mdstat.readlines()
519 valid_line = re.compile("^md([0-9]+) : .*$")
521 match = valid_line.match(line)
523 md_no = int(match.group(1))
524 used_md[md_no] = line
530 def _GetDevInfo(minor):
531 """Get info about a MD device.
533 Currently only uuid is returned.
536 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
538 logger.Error("Can't display md: %s" % result.fail_reason)
541 for line in result.stdout.splitlines():
543 kv = line.split(" : ", 1)
546 retval["uuid"] = kv[1]
547 elif kv[0] == "State":
548 retval["state"] = kv[1].split(", ")
553 def _FindUnusedMinor():
554 """Compute an unused MD minor.
556 This code assumes that there are 256 minors only.
559 used_md = MDRaid1._GetUsedDevs()
566 logger.Error("Critical: Out of md minor numbers.")
572 def _FindMDByUUID(cls, uuid):
573 """Find the minor of an MD array with a given UUID.
576 md_list = cls._GetUsedDevs()
577 for minor in md_list:
578 info = cls._GetDevInfo(minor)
579 if info and info["uuid"] == uuid:
585 def Create(cls, unique_id, children, size):
586 """Create a new MD raid1 array.
589 if not isinstance(children, (tuple, list)):
590 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
593 if not isinstance(i, BlockDev):
594 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
596 result = utils.RunCmd(["mdadm", "--zero-superblock", "--force",
599 logger.Error("Can't zero superblock: %s" % result.fail_reason)
601 minor = cls._FindUnusedMinor()
602 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
603 "--auto=yes", "--force", "-l1",
604 "-n%d" % len(children)] +
605 [dev.dev_path for dev in children])
608 logger.Error("Can't create md: %s" % result.fail_reason)
610 info = cls._GetDevInfo(minor)
611 if not info or not "uuid" in info:
612 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
614 return MDRaid1(info["uuid"], children)
618 """Stub remove function for MD RAID 1 arrays.
620 We don't remove the superblock right now. Mark a to do.
623 #TODO: maybe zero superblock on child devices?
624 return self.Shutdown()
627 def AddChild(self, device):
628 """Add a new member to the md raid1.
631 if self.minor is None and not self.Attach():
632 raise errors.BlockDeviceError, "Can't attach to device"
633 if device.dev_path is None:
634 raise errors.BlockDeviceError, "New child is not initialised"
635 result = utils.RunCmd(["mdadm", "-a", self.dev_path, device.dev_path])
637 raise errors.BlockDeviceError, ("Failed to add new device to array: %s" %
639 new_len = len(self._children) + 1
640 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
642 raise errors.BlockDeviceError, ("Can't grow md array: %s" %
644 self._children.append(device)
647 def RemoveChild(self, dev_path):
648 """Remove member from the md raid1.
651 if self.minor is None and not self.Attach():
652 raise errors.BlockDeviceError, "Can't attach to device"
653 if len(self._children) == 1:
654 raise errors.BlockDeviceError, ("Can't reduce member when only one"
656 for device in self._children:
657 if device.dev_path == dev_path:
660 raise errors.BlockDeviceError, "Can't find child with this path"
661 new_len = len(self._children) - 1
662 result = utils.RunCmd(["mdadm", "-f", self.dev_path, dev_path])
664 raise errors.BlockDeviceError, ("Failed to mark device as failed: %s" %
667 # it seems here we need a short delay for MD to update its
670 result = utils.RunCmd(["mdadm", "-r", self.dev_path, dev_path])
672 raise errors.BlockDeviceError, ("Failed to remove device from array:"
673 " %s" % result.output)
674 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
677 raise errors.BlockDeviceError, ("Can't shrink md array: %s" %
679 self._children.remove(device)
683 """Return the status of the device.
687 if self.minor is None:
688 retval = self.STATUS_UNKNOWN
690 retval = self.STATUS_ONLINE
694 def _SetFromMinor(self, minor):
695 """Set our parameters based on the given minor.
697 This sets our minor variable and our dev_path.
701 self.dev_path = "/dev/md%d" % minor
705 """Assemble the MD device.
707 At this point we should have:
708 - list of children devices
712 result = super(MDRaid1, self).Assemble()
715 md_list = self._GetUsedDevs()
716 for minor in md_list:
717 info = self._GetDevInfo(minor)
718 if info and info["uuid"] == self.unique_id:
719 self._SetFromMinor(minor)
720 logger.Info("MD array %s already started" % str(self))
722 free_minor = self._FindUnusedMinor()
723 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
724 self.unique_id, "/dev/md%d" % free_minor] +
725 [bdev.dev_path for bdev in self._children])
727 logger.Error("Can't assemble MD array: %s" % result.fail_reason)
730 self.minor = free_minor
731 return not result.failed
735 """Tear down the MD array.
737 This does a 'mdadm --stop' so after this command, the array is no
741 if self.minor is None and not self.Attach():
742 logger.Info("MD object not attached to a device")
745 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
747 logger.Error("Can't stop MD array: %s" % result.fail_reason)
754 def SetSyncSpeed(self, kbytes):
755 """Set the maximum sync speed for the MD array.
758 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
759 if self.minor is None:
760 logger.Error("MD array not attached to a device")
762 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
764 f.write("%d" % kbytes)
767 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
769 f.write("%d" % (kbytes/2))
775 def GetSyncStatus(self):
776 """Returns the sync status of the device.
779 (sync_percent, estimated_time)
781 If sync_percent is None, it means all is ok
782 If estimated_time is None, it means we can't esimate
783 the time needed, otherwise it's the time left in seconds
786 if self.minor is None and not self.Attach():
787 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
788 dev_info = self._GetDevInfo(self.minor)
789 is_clean = ("state" in dev_info and
790 len(dev_info["state"]) == 1 and
791 dev_info["state"][0] in ("clean", "active"))
792 sys_path = "/sys/block/md%s/md/" % self.minor
793 f = file(sys_path + "sync_action")
794 sync_status = f.readline().strip()
796 if sync_status == "idle":
797 return None, None, not is_clean
798 f = file(sys_path + "sync_completed")
799 sync_completed = f.readline().strip().split(" / ")
801 if len(sync_completed) != 2:
802 return 0, None, not is_clean
803 sync_done, sync_total = [float(i) for i in sync_completed]
804 sync_percent = 100.0*sync_done/sync_total
805 f = file(sys_path + "sync_speed")
806 sync_speed_k = int(f.readline().strip())
807 if sync_speed_k == 0:
810 time_est = (sync_total - sync_done) / 2 / sync_speed_k
811 return sync_percent, time_est, not is_clean
814 def Open(self, force=False):
815 """Make the device ready for I/O.
817 This is a no-op for the MDRaid1 device type, although we could use
818 the 2.6.18's new array_state thing.
825 """Notifies that the device will no longer be used for I/O.
827 This is a no-op for the MDRaid1 device type, but see comment for
835 class DRBDev(BlockDev):
836 """DRBD block device.
838 This implements the local host part of the DRBD device, i.e. it
839 doesn't do anything to the supposed peer. If you need a fully
840 connected DRBD pair, you need to use this class on both hosts.
842 The unique_id for the drbd device is the (local_ip, local_port,
843 remote_ip, remote_port) tuple, and it must have two children: the
844 data device and the meta_device. The meta device is checked for
845 valid size and is zeroed on create.
849 _ST_UNCONFIGURED = "Unconfigured"
850 _ST_WFCONNECTION = "WFConnection"
851 _ST_CONNECTED = "Connected"
853 def __init__(self, unique_id, children):
854 super(DRBDev, self).__init__(unique_id, children)
855 self.major = self._DRBD_MAJOR
856 if len(children) != 2:
857 raise ValueError("Invalid configuration data %s" % str(children))
858 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
859 raise ValueError("Invalid configuration data %s" % str(unique_id))
860 self._lhost, self._lport, self._rhost, self._rport = unique_id
865 """Return the path to a drbd device for a given minor.
868 return "/dev/drbd%d" % minor
872 """Return data from /proc/drbd.
875 stat = open("/proc/drbd", "r")
876 data = stat.read().splitlines()
882 def _GetUsedDevs(cls):
883 """Compute the list of used DRBD devices.
886 data = cls._GetProcData()
889 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
891 match = valid_line.match(line)
894 minor = int(match.group(1))
895 state = match.group(2)
896 if state == cls._ST_UNCONFIGURED:
898 used_devs[minor] = state, line
904 def _FindUnusedMinor(cls):
905 """Find an unused DRBD device.
908 data = cls._GetProcData()
910 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
912 match = valid_line.match(line)
914 return int(match.group(1))
915 logger.Error("Error: no free drbd minors!")
920 def _GetDevInfo(cls, minor):
921 """Get details about a given DRBD minor.
923 This return, if available, the local backing device in (major,
924 minor) formant and the local and remote (ip, port) information.
928 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
930 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
933 if out == "Not configured\n":
935 for line in out.splitlines():
936 if "local_dev" not in data:
937 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
939 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
941 if "meta_dev" not in data:
942 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
944 if match.group(2) is not None and match.group(3) is not None:
945 # matched on the major/minor
946 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
948 # matched on the "internal" string
949 data["meta_dev"] = match.group(1)
950 # in this case, no meta_index is in the output
951 data["meta_index"] = -1
953 if "meta_index" not in data:
954 match = re.match("^Meta index: ([0-9]+).*$", line)
956 data["meta_index"] = int(match.group(1))
958 if "local_addr" not in data:
959 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
961 data["local_addr"] = (match.group(1), int(match.group(2)))
963 if "remote_addr" not in data:
964 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
966 data["remote_addr"] = (match.group(1), int(match.group(2)))
971 def _MatchesLocal(self, info):
972 """Test if our local config matches with an existing device.
974 The parameter should be as returned from `_GetDevInfo()`. This
975 method tests if our local backing device is the same as the one in
976 the info parameter, in effect testing if we look like the given
980 if not ("local_dev" in info and "meta_dev" in info and
981 "meta_index" in info):
984 backend = self._children[0]
985 if backend is not None:
986 retval = (info["local_dev"] == (backend.major, backend.minor))
988 retval = (info["local_dev"] == (0, 0))
989 meta = self._children[1]
991 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
992 retval = retval and (info["meta_index"] == 0)
994 retval = retval and (info["meta_dev"] == "internal" and
995 info["meta_index"] == -1)
999 def _MatchesNet(self, info):
1000 """Test if our network config matches with an existing device.
1002 The parameter should be as returned from `_GetDevInfo()`. This
1003 method tests if our network configuration is the same as the one
1004 in the info parameter, in effect testing if we look like the given
1008 if (((self._lhost is None and not ("local_addr" in info)) and
1009 (self._rhost is None and not ("remote_addr" in info)))):
1012 if self._lhost is None:
1015 if not ("local_addr" in info and
1016 "remote_addr" in info):
1019 retval = (info["local_addr"] == (self._lhost, self._lport))
1020 retval = (retval and
1021 info["remote_addr"] == (self._rhost, self._rport))
1026 def _IsValidMeta(meta_device):
1027 """Check if the given meta device looks like a valid one.
1029 This currently only check the size, which must be around
1033 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1035 logger.Error("Failed to get device size: %s" % result.fail_reason)
1038 sectors = int(result.stdout)
1040 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1042 bytes = sectors * 512
1043 if bytes < 128*1024*1024: # less than 128MiB
1044 logger.Error("Meta device too small (%.2fMib)" % (bytes/1024/1024))
1046 if bytes > (128+32)*1024*1024: # account for an extra (big) PE on LVM
1047 logger.Error("Meta device too big (%.2fMiB)" % (bytes/1024/1024))
1053 def _AssembleLocal(cls, minor, backend, meta):
1054 """Configure the local part of a DRBD device.
1056 This is the first thing that must be done on an unconfigured DRBD
1057 device. And it must be done only once.
1060 if not cls._IsValidMeta(meta):
1062 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1063 backend, meta, "0", "-e", "detach"])
1065 logger.Error("Can't attach local disk: %s" % result.output)
1066 return not result.failed
1070 def _ShutdownLocal(cls, minor):
1071 """Detach from the local device.
1073 I/Os will continue to be served from the remote device. If we
1074 don't have a remote device, this operation will fail.
1077 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1079 logger.Error("Can't detach local device: %s" % result.output)
1080 return not result.failed
1084 def _ShutdownAll(minor):
1085 """Deactivate the device.
1087 This will, of course, fail if the device is in use.
1090 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1092 logger.Error("Can't shutdown drbd device: %s" % result.output)
1093 return not result.failed
1097 def _AssembleNet(cls, minor, net_info, protocol):
1098 """Configure the network part of the device.
1100 This operation can be, in theory, done multiple times, but there
1101 have been cases (in lab testing) in which the network part of the
1102 device had become stuck and couldn't be shut down because activity
1103 from the new peer (also stuck) triggered a timer re-init and
1104 needed remote peer interface shutdown in order to clear. So please
1105 don't change online the net config.
1108 lhost, lport, rhost, rport = net_info
1109 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1110 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1113 logger.Error("Can't setup network for dbrd device: %s" %
1117 timeout = time.time() + 10
1119 while time.time() < timeout:
1120 info = cls._GetDevInfo(minor)
1121 if not "local_addr" in info or not "remote_addr" in info:
1124 if (info["local_addr"] != (lhost, lport) or
1125 info["remote_addr"] != (rhost, rport)):
1131 logger.Error("Timeout while configuring network")
1137 def _ShutdownNet(cls, minor):
1138 """Disconnect from the remote peer.
1140 This fails if we don't have a local device.
1143 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1144 logger.Error("Can't shutdown network: %s" % result.output)
1145 return not result.failed
1148 def _SetFromMinor(self, minor):
1149 """Set our parameters based on the given minor.
1151 This sets our minor variable and our dev_path.
1155 self.minor = self.dev_path = None
1158 self.dev_path = self._DevPath(minor)
1162 """Assemble the drbd.
1165 - if we have a local backing device, we bind to it by:
1166 - checking the list of used drbd devices
1167 - check if the local minor use of any of them is our own device
1170 - if we have a local/remote net info:
1171 - redo the local backing device step for the remote device
1172 - check if any drbd device is using the local port,
1174 - check if any remote drbd device is using the remote
1175 port, if yes abort (for now)
1177 - bind the remote net port
1181 if self.minor is not None:
1182 logger.Info("Already assembled")
1185 result = super(DRBDev, self).Assemble()
1189 minor = self._FindUnusedMinor()
1191 raise errors.BlockDeviceError, "Not enough free minors for DRBD!"
1192 need_localdev_teardown = False
1193 if self._children[0]:
1194 result = self._AssembleLocal(minor, self._children[0].dev_path,
1195 self._children[1].dev_path)
1198 need_localdev_teardown = True
1199 if self._lhost and self._lport and self._rhost and self._rport:
1200 result = self._AssembleNet(minor,
1201 (self._lhost, self._lport,
1202 self._rhost, self._rport),
1205 if need_localdev_teardown:
1206 # we will ignore failures from this
1207 logger.Error("net setup failed, tearing down local device")
1208 self._ShutdownAll(minor)
1210 self._SetFromMinor(minor)
1215 """Shutdown the DRBD device.
1218 if self.minor is None and not self.Attach():
1219 logger.Info("DRBD device not attached to a device during Shutdown")
1221 if not self._ShutdownAll(self.minor):
1224 self.dev_path = None
1229 """Find a DRBD device which matches our config and attach to it.
1231 In case of partially attached (local device matches but no network
1232 setup), we perform the network attach. If successful, we re-test
1233 the attach if can return success.
1236 for minor in self._GetUsedDevs():
1237 info = self._GetDevInfo(minor)
1238 match_l = self._MatchesLocal(info)
1239 match_r = self._MatchesNet(info)
1240 if match_l and match_r:
1242 if match_l and not match_r and "local_addr" not in info:
1243 res_r = self._AssembleNet(minor,
1244 (self._lhost, self._lport,
1245 self._rhost, self._rport),
1247 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1252 self._SetFromMinor(minor)
1253 return minor is not None
1256 def Open(self, force=False):
1257 """Make the local state primary.
1259 If the 'force' parameter is given, the '--do-what-I-say' parameter
1260 is given. Since this is a pottentialy dangerous operation, the
1261 force flag should be only given after creation, when it actually
1265 if self.minor is None and not self.Attach():
1266 logger.Error("DRBD cannot attach to a device during open")
1268 cmd = ["drbdsetup", self.dev_path, "primary"]
1270 cmd.append("--do-what-I-say")
1271 result = utils.RunCmd(cmd)
1273 logger.Error("Can't make drbd device primary: %s" % result.output)
1279 """Make the local state secondary.
1281 This will, of course, fail if the device is in use.
1284 if self.minor is None and not self.Attach():
1285 logger.Info("Instance not attached to a device")
1286 raise errors.BlockDeviceError("Can't find device")
1287 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1289 logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1290 raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1293 def SetSyncSpeed(self, kbytes):
1294 """Set the speed of the DRBD syncer.
1297 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1298 if self.minor is None:
1299 logger.Info("Instance not attached to a device")
1301 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1304 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1305 return not result.failed and children_result
1308 def GetSyncStatus(self):
1309 """Returns the sync status of the device.
1312 (sync_percent, estimated_time)
1314 If sync_percent is None, it means all is ok
1315 If estimated_time is None, it means we can't esimate
1316 the time needed, otherwise it's the time left in seconds
1319 if self.minor is None and not self.Attach():
1320 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1321 proc_info = self._MassageProcData(self._GetProcData())
1322 if self.minor not in proc_info:
1323 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1325 line = proc_info[self.minor]
1326 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1327 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1329 sync_percent = float(match.group(1))
1330 hours = int(match.group(2))
1331 minutes = int(match.group(3))
1332 seconds = int(match.group(4))
1333 est_time = hours * 3600 + minutes * 60 + seconds
1337 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1339 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1341 client_state = match.group(1)
1342 is_degraded = client_state != "Connected"
1343 return sync_percent, est_time, is_degraded
1347 def _MassageProcData(data):
1348 """Transform the output of _GetProdData into a nicer form.
1351 a dictionary of minor: joined lines from /proc/drbd for that minor
1354 lmatch = re.compile("^ *([0-9]+):.*$")
1356 old_minor = old_line = None
1358 lresult = lmatch.match(line)
1359 if lresult is not None:
1360 if old_minor is not None:
1361 results[old_minor] = old_line
1362 old_minor = int(lresult.group(1))
1365 if old_minor is not None:
1366 old_line += " " + line.strip()
1368 if old_minor is not None:
1369 results[old_minor] = old_line
1373 def GetStatus(self):
1374 """Compute the status of the DRBD device
1376 Note that DRBD devices don't have the STATUS_EXISTING state.
1379 if self.minor is None and not self.Attach():
1380 return self.STATUS_UNKNOWN
1382 data = self._GetProcData()
1383 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1386 mresult = match.match(line)
1390 logger.Error("Can't find myself!")
1391 return self.STATUS_UNKNOWN
1393 state = mresult.group(2)
1394 if state == "Primary":
1395 result = self.STATUS_ONLINE
1397 result = self.STATUS_STANDBY
1403 def _ZeroDevice(device):
1406 This writes until we get ENOSPC.
1409 f = open(device, "w")
1410 buf = "\0" * 1048576
1414 except IOError, err:
1415 if err.errno != errno.ENOSPC:
1420 def Create(cls, unique_id, children, size):
1421 """Create a new DRBD device.
1423 Since DRBD devices are not created per se, just assembled, this
1424 function just zeroes the meta device.
1427 if len(children) != 2:
1428 raise errors.ProgrammerError("Invalid setup for the drbd device")
1431 if not meta.Attach():
1432 raise errors.BlockDeviceError("Can't attach to meta device")
1433 if not cls._IsValidMeta(meta.dev_path):
1434 raise errors.BlockDeviceError("Invalid meta device")
1435 logger.Info("Started zeroing device %s" % meta.dev_path)
1436 cls._ZeroDevice(meta.dev_path)
1437 logger.Info("Done zeroing device %s" % meta.dev_path)
1438 return cls(unique_id, children)
1442 """Stub remove for DRBD devices.
1445 return self.Shutdown()
1449 "lvm": LogicalVolume,
1450 "md_raid1": MDRaid1,
1455 def FindDevice(dev_type, unique_id, children):
1456 """Search for an existing, assembled device.
1458 This will succeed only if the device exists and is assembled, but it
1459 does not do any actions in order to activate the device.
1462 if dev_type not in DEV_MAP:
1463 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1464 device = DEV_MAP[dev_type](unique_id, children)
1465 if not device.Attach():
1470 def AttachOrAssemble(dev_type, unique_id, children):
1471 """Try to attach or assemble an existing device.
1473 This will attach to an existing assembled device or will assemble
1474 the device, as needed, to bring it fully up.
1477 if dev_type not in DEV_MAP:
1478 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1479 device = DEV_MAP[dev_type](unique_id, children)
1480 if not device.Attach():
1482 if not device.Attach():
1483 raise errors.BlockDeviceError("Can't find a valid block device for"
1485 (dev_type, unique_id, children))
1489 def Create(dev_type, unique_id, children, size):
1493 if dev_type not in DEV_MAP:
1494 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1495 device = DEV_MAP[dev_type].Create(unique_id, children, size)