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 Open(self, force=False):
455 """Make the device ready for I/O.
457 This is a no-op for the LV device type.
463 """Notifies that the device will no longer be used for I/O.
465 This is a no-op for the LV device type.
470 def Snapshot(self, size):
471 """Create a snapshot copy of an lvm block device.
474 snap_name = self._lv_name + ".snap"
476 # remove existing snapshot if found
477 snap = LogicalVolume((self._vg_name, snap_name), None)
480 pvs_info = self.GetPVInfo(self._vg_name)
482 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
486 free_size, pv_name = pvs_info[0]
488 raise errors.BlockDeviceError("Not enough free space: required %s,"
489 " available %s" % (size, free_size))
491 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
492 "-n%s" % snap_name, self.dev_path])
494 raise errors.BlockDeviceError("command: %s error: %s" %
495 (result.cmd, result.fail_reason))
499 def SetInfo(self, text):
500 """Update metadata with info text.
503 BlockDev.SetInfo(self, text)
505 # Replace invalid characters
506 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
507 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
509 # Only up to 128 characters are allowed
512 result = utils.RunCmd(["lvchange", "--addtag", text,
515 raise errors.BlockDeviceError("Command: %s error: %s" %
516 (result.cmd, result.fail_reason))
519 class MDRaid1(BlockDev):
520 """raid1 device implemented via md.
523 def __init__(self, unique_id, children):
524 super(MDRaid1, self).__init__(unique_id, children)
529 """Find an array which matches our config and attach to it.
531 This tries to find a MD array which has the same UUID as our own.
534 minor = self._FindMDByUUID(self.unique_id)
535 if minor is not None:
536 self._SetFromMinor(minor)
541 return (minor is not None)
545 """Compute the list of in-use MD devices.
547 It doesn't matter if the used device have other raid level, just
548 that they are in use.
551 mdstat = open("/proc/mdstat", "r")
552 data = mdstat.readlines()
556 valid_line = re.compile("^md([0-9]+) : .*$")
558 match = valid_line.match(line)
560 md_no = int(match.group(1))
561 used_md[md_no] = line
566 def _GetDevInfo(minor):
567 """Get info about a MD device.
569 Currently only uuid is returned.
572 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
574 logger.Error("Can't display md: %s" % result.fail_reason)
577 for line in result.stdout.splitlines():
579 kv = line.split(" : ", 1)
582 retval["uuid"] = kv[1].split()[0]
583 elif kv[0] == "State":
584 retval["state"] = kv[1].split(", ")
588 def _FindUnusedMinor():
589 """Compute an unused MD minor.
591 This code assumes that there are 256 minors only.
594 used_md = MDRaid1._GetUsedDevs()
601 logger.Error("Critical: Out of md minor numbers.")
602 raise errors.BlockDeviceError("Can't find a free MD minor")
606 def _FindMDByUUID(cls, uuid):
607 """Find the minor of an MD array with a given UUID.
610 md_list = cls._GetUsedDevs()
611 for minor in md_list:
612 info = cls._GetDevInfo(minor)
613 if info and info["uuid"] == uuid:
618 def _ZeroSuperblock(dev_path):
619 """Zero the possible locations for an MD superblock.
621 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
622 fails in versions 2.x with the same error code as non-writable
625 The superblocks are located at (negative values are relative to
626 the end of the block device):
627 - -128k to end for version 0.90 superblock
628 - -8k to -12k for version 1.0 superblock (included in the above)
629 - 0k to 4k for version 1.1 superblock
630 - 4k to 8k for version 1.2 superblock
632 To cover all situations, the zero-ing will be:
636 As such, the minimum device size must be 128k, otherwise we'll get
639 Note that this function depends on the fact that one can open,
640 read and write block devices normally.
643 overwrite_size = 128 * 1024
644 empty_buf = '\0' * overwrite_size
645 fd = open(dev_path, "r+")
651 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
652 fd.seek(-overwrite_size, 2)
656 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
661 def Create(cls, unique_id, children, size):
662 """Create a new MD raid1 array.
665 if not isinstance(children, (tuple, list)):
666 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
669 if not isinstance(i, BlockDev):
670 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
673 cls._ZeroSuperblock(i.dev_path)
674 except EnvironmentError, err:
675 logger.Error("Can't zero superblock for %s: %s" %
676 (i.dev_path, str(err)))
678 minor = cls._FindUnusedMinor()
679 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
680 "--auto=yes", "--force", "-l1",
681 "-n%d" % len(children)] +
682 [dev.dev_path for dev in children])
685 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
688 info = cls._GetDevInfo(minor)
689 if not info or not "uuid" in info:
690 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
692 return MDRaid1(info["uuid"], children)
695 """Stub remove function for MD RAID 1 arrays.
697 We don't remove the superblock right now. Mark a to do.
700 #TODO: maybe zero superblock on child devices?
701 return self.Shutdown()
703 def Rename(self, new_id):
706 This is not supported for md raid1 devices.
709 raise errors.ProgrammerError("Can't rename a md raid1 device")
711 def AddChildren(self, devices):
712 """Add new member(s) to the md raid1.
715 if self.minor is None and not self.Attach():
716 raise errors.BlockDeviceError("Can't attach to device")
718 args = ["mdadm", "-a", self.dev_path]
720 if dev.dev_path is None:
721 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
723 args.append(dev.dev_path)
724 result = utils.RunCmd(args)
726 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
728 new_len = len(self._children) + len(devices)
729 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
731 raise errors.BlockDeviceError("Can't grow md array: %s" %
733 self._children.extend(devices)
735 def RemoveChildren(self, devices):
736 """Remove member(s) from the md raid1.
739 if self.minor is None and not self.Attach():
740 raise errors.BlockDeviceError("Can't attach to device")
741 new_len = len(self._children) - len(devices)
743 raise errors.BlockDeviceError("Can't reduce to less than one child")
744 args = ["mdadm", "-f", self.dev_path]
748 for c in self._children:
749 if c.dev_path == dev:
753 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
755 result = utils.RunCmd(args)
757 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
760 # it seems here we need a short delay for MD to update its
764 result = utils.RunCmd(args)
766 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
767 " %s" % result.output)
768 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
771 raise errors.BlockDeviceError("Can't shrink md array: %s" %
773 for dev in orig_devs:
774 self._children.remove(dev)
777 """Return the status of the device.
781 if self.minor is None:
782 retval = self.STATUS_UNKNOWN
784 retval = self.STATUS_ONLINE
787 def _SetFromMinor(self, minor):
788 """Set our parameters based on the given minor.
790 This sets our minor variable and our dev_path.
794 self.dev_path = "/dev/md%d" % minor
797 """Assemble the MD device.
799 At this point we should have:
800 - list of children devices
804 result = super(MDRaid1, self).Assemble()
807 md_list = self._GetUsedDevs()
808 for minor in md_list:
809 info = self._GetDevInfo(minor)
810 if info and info["uuid"] == self.unique_id:
811 self._SetFromMinor(minor)
812 logger.Info("MD array %s already started" % str(self))
814 free_minor = self._FindUnusedMinor()
815 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
816 self.unique_id, "/dev/md%d" % free_minor] +
817 [bdev.dev_path for bdev in self._children])
819 logger.Error("Can't assemble MD array: %s: %s" %
820 (result.fail_reason, result.output))
823 self.minor = free_minor
824 return not result.failed
827 """Tear down the MD array.
829 This does a 'mdadm --stop' so after this command, the array is no
833 if self.minor is None and not self.Attach():
834 logger.Info("MD object not attached to a device")
837 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
839 logger.Error("Can't stop MD array: %s" % result.fail_reason)
845 def SetSyncSpeed(self, kbytes):
846 """Set the maximum sync speed for the MD array.
849 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
850 if self.minor is None:
851 logger.Error("MD array not attached to a device")
853 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
855 f.write("%d" % kbytes)
858 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
860 f.write("%d" % (kbytes/2))
865 def GetSyncStatus(self):
866 """Returns the sync status of the device.
869 (sync_percent, estimated_time, is_degraded)
871 If sync_percent is None, it means all is ok
872 If estimated_time is None, it means we can't esimate
873 the time needed, otherwise it's the time left in seconds
876 if self.minor is None and not self.Attach():
877 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
878 dev_info = self._GetDevInfo(self.minor)
879 is_clean = ("state" in dev_info and
880 len(dev_info["state"]) == 1 and
881 dev_info["state"][0] in ("clean", "active"))
882 sys_path = "/sys/block/md%s/md/" % self.minor
883 f = file(sys_path + "sync_action")
884 sync_status = f.readline().strip()
886 if sync_status == "idle":
887 return None, None, not is_clean
888 f = file(sys_path + "sync_completed")
889 sync_completed = f.readline().strip().split(" / ")
891 if len(sync_completed) != 2:
892 return 0, None, not is_clean
893 sync_done, sync_total = [float(i) for i in sync_completed]
894 sync_percent = 100.0*sync_done/sync_total
895 f = file(sys_path + "sync_speed")
896 sync_speed_k = int(f.readline().strip())
897 if sync_speed_k == 0:
900 time_est = (sync_total - sync_done) / 2 / sync_speed_k
901 return sync_percent, time_est, not is_clean
903 def Open(self, force=False):
904 """Make the device ready for I/O.
906 This is a no-op for the MDRaid1 device type, although we could use
907 the 2.6.18's new array_state thing.
913 """Notifies that the device will no longer be used for I/O.
915 This is a no-op for the MDRaid1 device type, but see comment for
922 class BaseDRBD(BlockDev):
925 This class contains a few bits of common functionality between the
926 0.7 and 8.x versions of DRBD.
929 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
930 r" \(api:(\d+)/proto:(\d+)\)")
932 _ST_UNCONFIGURED = "Unconfigured"
933 _ST_WFCONNECTION = "WFConnection"
934 _ST_CONNECTED = "Connected"
938 """Return data from /proc/drbd.
941 stat = open("/proc/drbd", "r")
943 data = stat.read().splitlines()
947 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
951 def _MassageProcData(data):
952 """Transform the output of _GetProdData into a nicer form.
955 a dictionary of minor: joined lines from /proc/drbd for that minor
958 lmatch = re.compile("^ *([0-9]+):.*$")
960 old_minor = old_line = None
962 lresult = lmatch.match(line)
963 if lresult is not None:
964 if old_minor is not None:
965 results[old_minor] = old_line
966 old_minor = int(lresult.group(1))
969 if old_minor is not None:
970 old_line += " " + line.strip()
972 if old_minor is not None:
973 results[old_minor] = old_line
977 def _GetVersion(cls):
978 """Return the DRBD version.
980 This will return a list [k_major, k_minor, k_point, api, proto].
983 proc_data = cls._GetProcData()
984 first_line = proc_data[0].strip()
985 version = cls._VERSION_RE.match(first_line)
987 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
989 return [int(val) for val in version.groups()]
993 """Return the path to a drbd device for a given minor.
996 return "/dev/drbd%d" % minor
999 def _GetUsedDevs(cls):
1000 """Compute the list of used DRBD devices.
1003 data = cls._GetProcData()
1006 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1008 match = valid_line.match(line)
1011 minor = int(match.group(1))
1012 state = match.group(2)
1013 if state == cls._ST_UNCONFIGURED:
1015 used_devs[minor] = state, line
1019 def _SetFromMinor(self, minor):
1020 """Set our parameters based on the given minor.
1022 This sets our minor variable and our dev_path.
1026 self.minor = self.dev_path = None
1029 self.dev_path = self._DevPath(minor)
1032 def _CheckMetaSize(meta_device):
1033 """Check if the given meta device looks like a valid one.
1035 This currently only check the size, which must be around
1039 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1041 logger.Error("Failed to get device size: %s" % result.fail_reason)
1044 sectors = int(result.stdout)
1046 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1048 bytes = sectors * 512
1049 if bytes < 128 * 1024 * 1024: # less than 128MiB
1050 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1052 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1053 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1057 def Rename(self, new_id):
1060 This is not supported for drbd devices.
1063 raise errors.ProgrammerError("Can't rename a drbd device")
1066 class DRBDev(BaseDRBD):
1067 """DRBD block device.
1069 This implements the local host part of the DRBD device, i.e. it
1070 doesn't do anything to the supposed peer. If you need a fully
1071 connected DRBD pair, you need to use this class on both hosts.
1073 The unique_id for the drbd device is the (local_ip, local_port,
1074 remote_ip, remote_port) tuple, and it must have two children: the
1075 data device and the meta_device. The meta device is checked for
1076 valid size and is zeroed on create.
1079 def __init__(self, unique_id, children):
1080 super(DRBDev, self).__init__(unique_id, children)
1081 self.major = self._DRBD_MAJOR
1082 [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1083 if kmaj != 0 and kmin != 7:
1084 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1085 " requested ganeti usage: kernel is"
1086 " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1088 if len(children) != 2:
1089 raise ValueError("Invalid configuration data %s" % str(children))
1090 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1091 raise ValueError("Invalid configuration data %s" % str(unique_id))
1092 self._lhost, self._lport, self._rhost, self._rport = unique_id
1096 def _FindUnusedMinor(cls):
1097 """Find an unused DRBD device.
1100 data = cls._GetProcData()
1102 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1104 match = valid_line.match(line)
1106 return int(match.group(1))
1107 logger.Error("Error: no free drbd minors!")
1108 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1111 def _GetDevInfo(cls, minor):
1112 """Get details about a given DRBD minor.
1114 This return, if available, the local backing device in (major,
1115 minor) formant and the local and remote (ip, port) information.
1119 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1121 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1124 if out == "Not configured\n":
1126 for line in out.splitlines():
1127 if "local_dev" not in data:
1128 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1130 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1132 if "meta_dev" not in data:
1133 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1135 if match.group(2) is not None and match.group(3) is not None:
1136 # matched on the major/minor
1137 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1139 # matched on the "internal" string
1140 data["meta_dev"] = match.group(1)
1141 # in this case, no meta_index is in the output
1142 data["meta_index"] = -1
1144 if "meta_index" not in data:
1145 match = re.match("^Meta index: ([0-9]+).*$", line)
1147 data["meta_index"] = int(match.group(1))
1149 if "local_addr" not in data:
1150 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1152 data["local_addr"] = (match.group(1), int(match.group(2)))
1154 if "remote_addr" not in data:
1155 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1157 data["remote_addr"] = (match.group(1), int(match.group(2)))
1161 def _MatchesLocal(self, info):
1162 """Test if our local config matches with an existing device.
1164 The parameter should be as returned from `_GetDevInfo()`. This
1165 method tests if our local backing device is the same as the one in
1166 the info parameter, in effect testing if we look like the given
1170 if not ("local_dev" in info and "meta_dev" in info and
1171 "meta_index" in info):
1174 backend = self._children[0]
1175 if backend is not None:
1176 retval = (info["local_dev"] == (backend.major, backend.minor))
1178 retval = (info["local_dev"] == (0, 0))
1179 meta = self._children[1]
1180 if meta is not None:
1181 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1182 retval = retval and (info["meta_index"] == 0)
1184 retval = retval and (info["meta_dev"] == "internal" and
1185 info["meta_index"] == -1)
1188 def _MatchesNet(self, info):
1189 """Test if our network config matches with an existing device.
1191 The parameter should be as returned from `_GetDevInfo()`. This
1192 method tests if our network configuration is the same as the one
1193 in the info parameter, in effect testing if we look like the given
1197 if (((self._lhost is None and not ("local_addr" in info)) and
1198 (self._rhost is None and not ("remote_addr" in info)))):
1201 if self._lhost is None:
1204 if not ("local_addr" in info and
1205 "remote_addr" in info):
1208 retval = (info["local_addr"] == (self._lhost, self._lport))
1209 retval = (retval and
1210 info["remote_addr"] == (self._rhost, self._rport))
1214 def _AssembleLocal(cls, minor, backend, meta):
1215 """Configure the local part of a DRBD device.
1217 This is the first thing that must be done on an unconfigured DRBD
1218 device. And it must be done only once.
1221 if not cls._CheckMetaSize(meta):
1223 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1224 backend, meta, "0", "-e", "detach"])
1226 logger.Error("Can't attach local disk: %s" % result.output)
1227 return not result.failed
1230 def _ShutdownLocal(cls, minor):
1231 """Detach from the local device.
1233 I/Os will continue to be served from the remote device. If we
1234 don't have a remote device, this operation will fail.
1237 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1239 logger.Error("Can't detach local device: %s" % result.output)
1240 return not result.failed
1243 def _ShutdownAll(minor):
1244 """Deactivate the device.
1246 This will, of course, fail if the device is in use.
1249 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1251 logger.Error("Can't shutdown drbd device: %s" % result.output)
1252 return not result.failed
1255 def _AssembleNet(cls, minor, net_info, protocol):
1256 """Configure the network part of the device.
1258 This operation can be, in theory, done multiple times, but there
1259 have been cases (in lab testing) in which the network part of the
1260 device had become stuck and couldn't be shut down because activity
1261 from the new peer (also stuck) triggered a timer re-init and
1262 needed remote peer interface shutdown in order to clear. So please
1263 don't change online the net config.
1266 lhost, lport, rhost, rport = net_info
1267 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1268 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1271 logger.Error("Can't setup network for dbrd device: %s" %
1275 timeout = time.time() + 10
1277 while time.time() < timeout:
1278 info = cls._GetDevInfo(minor)
1279 if not "local_addr" in info or not "remote_addr" in info:
1282 if (info["local_addr"] != (lhost, lport) or
1283 info["remote_addr"] != (rhost, rport)):
1289 logger.Error("Timeout while configuring network")
1294 def _ShutdownNet(cls, minor):
1295 """Disconnect from the remote peer.
1297 This fails if we don't have a local device.
1300 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1301 logger.Error("Can't shutdown network: %s" % result.output)
1302 return not result.failed
1305 """Assemble the drbd.
1308 - if we have a local backing device, we bind to it by:
1309 - checking the list of used drbd devices
1310 - check if the local minor use of any of them is our own device
1313 - if we have a local/remote net info:
1314 - redo the local backing device step for the remote device
1315 - check if any drbd device is using the local port,
1317 - check if any remote drbd device is using the remote
1318 port, if yes abort (for now)
1320 - bind the remote net port
1324 if self.minor is not None:
1325 logger.Info("Already assembled")
1328 result = super(DRBDev, self).Assemble()
1332 minor = self._FindUnusedMinor()
1333 need_localdev_teardown = False
1334 if self._children[0]:
1335 result = self._AssembleLocal(minor, self._children[0].dev_path,
1336 self._children[1].dev_path)
1339 need_localdev_teardown = True
1340 if self._lhost and self._lport and self._rhost and self._rport:
1341 result = self._AssembleNet(minor,
1342 (self._lhost, self._lport,
1343 self._rhost, self._rport),
1346 if need_localdev_teardown:
1347 # we will ignore failures from this
1348 logger.Error("net setup failed, tearing down local device")
1349 self._ShutdownAll(minor)
1351 self._SetFromMinor(minor)
1355 """Shutdown the DRBD device.
1358 if self.minor is None and not self.Attach():
1359 logger.Info("DRBD device not attached to a device during Shutdown")
1361 if not self._ShutdownAll(self.minor):
1364 self.dev_path = None
1368 """Find a DRBD device which matches our config and attach to it.
1370 In case of partially attached (local device matches but no network
1371 setup), we perform the network attach. If successful, we re-test
1372 the attach if can return success.
1375 for minor in self._GetUsedDevs():
1376 info = self._GetDevInfo(minor)
1377 match_l = self._MatchesLocal(info)
1378 match_r = self._MatchesNet(info)
1379 if match_l and match_r:
1381 if match_l and not match_r and "local_addr" not in info:
1382 res_r = self._AssembleNet(minor,
1383 (self._lhost, self._lport,
1384 self._rhost, self._rport),
1386 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1391 self._SetFromMinor(minor)
1392 return minor is not None
1394 def Open(self, force=False):
1395 """Make the local state primary.
1397 If the 'force' parameter is given, the '--do-what-I-say' parameter
1398 is given. Since this is a pottentialy dangerous operation, the
1399 force flag should be only given after creation, when it actually
1403 if self.minor is None and not self.Attach():
1404 logger.Error("DRBD cannot attach to a device during open")
1406 cmd = ["drbdsetup", self.dev_path, "primary"]
1408 cmd.append("--do-what-I-say")
1409 result = utils.RunCmd(cmd)
1411 logger.Error("Can't make drbd device primary: %s" % result.output)
1416 """Make the local state secondary.
1418 This will, of course, fail if the device is in use.
1421 if self.minor is None and not self.Attach():
1422 logger.Info("Instance not attached to a device")
1423 raise errors.BlockDeviceError("Can't find device")
1424 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1426 logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1427 raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1429 def SetSyncSpeed(self, kbytes):
1430 """Set the speed of the DRBD syncer.
1433 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1434 if self.minor is None:
1435 logger.Info("Instance not attached to a device")
1437 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1440 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1441 return not result.failed and children_result
1443 def GetSyncStatus(self):
1444 """Returns the sync status of the device.
1447 (sync_percent, estimated_time, is_degraded)
1449 If sync_percent is None, it means all is ok
1450 If estimated_time is None, it means we can't esimate
1451 the time needed, otherwise it's the time left in seconds
1454 if self.minor is None and not self.Attach():
1455 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1456 proc_info = self._MassageProcData(self._GetProcData())
1457 if self.minor not in proc_info:
1458 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1460 line = proc_info[self.minor]
1461 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1462 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1464 sync_percent = float(match.group(1))
1465 hours = int(match.group(2))
1466 minutes = int(match.group(3))
1467 seconds = int(match.group(4))
1468 est_time = hours * 3600 + minutes * 60 + seconds
1472 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1474 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1476 client_state = match.group(1)
1477 is_degraded = client_state != "Connected"
1478 return sync_percent, est_time, is_degraded
1480 def GetStatus(self):
1481 """Compute the status of the DRBD device
1483 Note that DRBD devices don't have the STATUS_EXISTING state.
1486 if self.minor is None and not self.Attach():
1487 return self.STATUS_UNKNOWN
1489 data = self._GetProcData()
1490 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1493 mresult = match.match(line)
1497 logger.Error("Can't find myself!")
1498 return self.STATUS_UNKNOWN
1500 state = mresult.group(2)
1501 if state == "Primary":
1502 result = self.STATUS_ONLINE
1504 result = self.STATUS_STANDBY
1509 def _ZeroDevice(device):
1512 This writes until we get ENOSPC.
1515 f = open(device, "w")
1516 buf = "\0" * 1048576
1520 except IOError, err:
1521 if err.errno != errno.ENOSPC:
1525 def Create(cls, unique_id, children, size):
1526 """Create a new DRBD device.
1528 Since DRBD devices are not created per se, just assembled, this
1529 function just zeroes the meta device.
1532 if len(children) != 2:
1533 raise errors.ProgrammerError("Invalid setup for the drbd device")
1536 if not meta.Attach():
1537 raise errors.BlockDeviceError("Can't attach to meta device")
1538 if not cls._CheckMetaSize(meta.dev_path):
1539 raise errors.BlockDeviceError("Invalid meta device")
1540 logger.Info("Started zeroing device %s" % meta.dev_path)
1541 cls._ZeroDevice(meta.dev_path)
1542 logger.Info("Done zeroing device %s" % meta.dev_path)
1543 return cls(unique_id, children)
1546 """Stub remove for DRBD devices.
1549 return self.Shutdown()
1552 class DRBD8(BaseDRBD):
1553 """DRBD v8.x block device.
1555 This implements the local host part of the DRBD device, i.e. it
1556 doesn't do anything to the supposed peer. If you need a fully
1557 connected DRBD pair, you need to use this class on both hosts.
1559 The unique_id for the drbd device is the (local_ip, local_port,
1560 remote_ip, remote_port) tuple, and it must have two children: the
1561 data device and the meta_device. The meta device is checked for
1562 valid size and is zeroed on create.
1568 def __init__(self, unique_id, children):
1569 super(DRBD8, self).__init__(unique_id, children)
1570 self.major = self._DRBD_MAJOR
1571 [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1573 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1574 " requested ganeti usage: kernel is"
1575 " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1577 if len(children) not in (0, 2):
1578 raise ValueError("Invalid configuration data %s" % str(children))
1579 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1580 raise ValueError("Invalid configuration data %s" % str(unique_id))
1581 self._lhost, self._lport, self._rhost, self._rport = unique_id
1585 def _InitMeta(cls, minor, dev_path):
1586 """Initialize a meta device.
1588 This will not work if the given minor is in use.
1591 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1592 "v08", dev_path, "0", "create-md"])
1594 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1598 def _FindUnusedMinor(cls):
1599 """Find an unused DRBD device.
1601 This is specific to 8.x as the minors are allocated dynamically,
1602 so non-existing numbers up to a max minor count are actually free.
1605 data = cls._GetProcData()
1607 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1608 used_line = re.compile("^ *([0-9]+): cs:")
1611 match = unused_line.match(line)
1613 return int(match.group(1))
1614 match = used_line.match(line)
1616 minor = int(match.group(1))
1617 highest = max(highest, minor)
1618 if highest is None: # there are no minors in use at all
1620 if highest >= cls._MAX_MINORS:
1621 logger.Error("Error: no free drbd minors!")
1622 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1626 def _IsValidMeta(cls, meta_device):
1627 """Check if the given meta device looks like a valid one.
1630 minor = cls._FindUnusedMinor()
1631 minor_path = cls._DevPath(minor)
1632 result = utils.RunCmd(["drbdmeta", minor_path,
1633 "v08", meta_device, "0",
1636 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1641 def _GetShowParser(cls):
1642 """Return a parser for `drbd show` output.
1644 This will either create or return an already-create parser for the
1645 output of the command `drbd show`.
1648 if cls._PARSE_SHOW is not None:
1649 return cls._PARSE_SHOW
1652 lbrace = pyp.Literal("{").suppress()
1653 rbrace = pyp.Literal("}").suppress()
1654 semi = pyp.Literal(";").suppress()
1655 # this also converts the value to an int
1656 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1658 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1659 defa = pyp.Literal("_is_default").suppress()
1660 dbl_quote = pyp.Literal('"').suppress()
1662 keyword = pyp.Word(pyp.alphanums + '-')
1665 value = pyp.Word(pyp.alphanums + '_-/.:')
1666 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1667 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1669 # meta device, extended syntax
1670 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1671 number + pyp.Word(']').suppress())
1674 stmt = (~rbrace + keyword + ~lbrace +
1675 (addr_port ^ value ^ quoted ^ meta_value) +
1676 pyp.Optional(defa) + semi +
1677 pyp.Optional(pyp.restOfLine).suppress())
1680 section_name = pyp.Word(pyp.alphas + '_')
1681 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1683 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1686 cls._PARSE_SHOW = bnf
1691 def _GetDevInfo(cls, minor):
1692 """Get details about a given DRBD minor.
1694 This return, if available, the local backing device (as a path)
1695 and the local and remote (ip, port) information.
1699 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1701 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1707 bnf = cls._GetShowParser()
1711 results = bnf.parseString(out)
1712 except pyp.ParseException, err:
1713 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1716 # and massage the results into our desired format
1717 for section in results:
1719 if sname == "_this_host":
1720 for lst in section[1:]:
1721 if lst[0] == "disk":
1722 data["local_dev"] = lst[1]
1723 elif lst[0] == "meta-disk":
1724 data["meta_dev"] = lst[1]
1725 data["meta_index"] = lst[2]
1726 elif lst[0] == "address":
1727 data["local_addr"] = tuple(lst[1:])
1728 elif sname == "_remote_host":
1729 for lst in section[1:]:
1730 if lst[0] == "address":
1731 data["remote_addr"] = tuple(lst[1:])
1734 def _MatchesLocal(self, info):
1735 """Test if our local config matches with an existing device.
1737 The parameter should be as returned from `_GetDevInfo()`. This
1738 method tests if our local backing device is the same as the one in
1739 the info parameter, in effect testing if we look like the given
1744 backend, meta = self._children
1746 backend = meta = None
1748 if backend is not None:
1749 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1751 retval = ("local_dev" not in info)
1753 if meta is not None:
1754 retval = retval and ("meta_dev" in info and
1755 info["meta_dev"] == meta.dev_path)
1756 retval = retval and ("meta_index" in info and
1757 info["meta_index"] == 0)
1759 retval = retval and ("meta_dev" not in info and
1760 "meta_index" not in info)
1763 def _MatchesNet(self, info):
1764 """Test if our network config matches with an existing device.
1766 The parameter should be as returned from `_GetDevInfo()`. This
1767 method tests if our network configuration is the same as the one
1768 in the info parameter, in effect testing if we look like the given
1772 if (((self._lhost is None and not ("local_addr" in info)) and
1773 (self._rhost is None and not ("remote_addr" in info)))):
1776 if self._lhost is None:
1779 if not ("local_addr" in info and
1780 "remote_addr" in info):
1783 retval = (info["local_addr"] == (self._lhost, self._lport))
1784 retval = (retval and
1785 info["remote_addr"] == (self._rhost, self._rport))
1789 def _AssembleLocal(cls, minor, backend, meta):
1790 """Configure the local part of a DRBD device.
1792 This is the first thing that must be done on an unconfigured DRBD
1793 device. And it must be done only once.
1796 if not cls._IsValidMeta(meta):
1798 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1799 backend, meta, "0", "-e", "detach",
1802 logger.Error("Can't attach local disk: %s" % result.output)
1803 return not result.failed
1806 def _AssembleNet(cls, minor, net_info, protocol,
1807 dual_pri=False, hmac=None, secret=None):
1808 """Configure the network part of the device.
1811 lhost, lport, rhost, rport = net_info
1812 args = ["drbdsetup", cls._DevPath(minor), "net",
1813 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1814 "-A", "discard-zero-changes",
1820 args.extend(["-a", hmac, "-x", secret])
1821 result = utils.RunCmd(args)
1823 logger.Error("Can't setup network for dbrd device: %s" %
1827 timeout = time.time() + 10
1829 while time.time() < timeout:
1830 info = cls._GetDevInfo(minor)
1831 if not "local_addr" in info or not "remote_addr" in info:
1834 if (info["local_addr"] != (lhost, lport) or
1835 info["remote_addr"] != (rhost, rport)):
1841 logger.Error("Timeout while configuring network")
1845 def AddChildren(self, devices):
1846 """Add a disk to the DRBD device.
1849 if self.minor is None:
1850 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1852 if len(devices) != 2:
1853 raise errors.BlockDeviceError("Need two devices for AddChildren")
1855 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1856 backend, meta = devices
1857 if backend.dev_path is None or meta.dev_path is None:
1858 raise errors.BlockDeviceError("Children not ready during AddChildren")
1861 if not self._CheckMetaSize(meta.dev_path):
1862 raise errors.BlockDeviceError("Invalid meta device size")
1863 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1864 if not self._IsValidMeta(meta.dev_path):
1865 raise errors.BlockDeviceError("Cannot initalize meta device")
1867 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1868 raise errors.BlockDeviceError("Can't attach to local storage")
1869 self._children = devices
1871 def RemoveChildren(self, devices):
1872 """Detach the drbd device from local storage.
1875 if self.minor is None:
1876 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1878 if len(self._children) != 2:
1879 raise errors.BlockDeviceError("We don't have two children: %s" %
1881 if self._children.count(None) == 2: # we don't actually have children :)
1882 logger.Error("Requested detach while detached")
1884 if len(devices) != 2:
1885 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1886 for child, dev in zip(self._children, devices):
1887 if dev != child.dev_path:
1888 raise errors.BlockDeviceError("Mismatch in local storage"
1889 " (%s != %s) in RemoveChildren" %
1890 (dev, child.dev_path))
1892 if not self._ShutdownLocal(self.minor):
1893 raise errors.BlockDeviceError("Can't detach from local storage")
1896 def SetSyncSpeed(self, kbytes):
1897 """Set the speed of the DRBD syncer.
1900 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1901 if self.minor is None:
1902 logger.Info("Instance not attached to a device")
1904 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1907 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1908 return not result.failed and children_result
1910 def GetSyncStatus(self):
1911 """Returns the sync status of the device.
1914 (sync_percent, estimated_time, is_degraded)
1916 If sync_percent is None, it means all is ok
1917 If estimated_time is None, it means we can't esimate
1918 the time needed, otherwise it's the time left in seconds
1921 if self.minor is None and not self.Attach():
1922 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1923 proc_info = self._MassageProcData(self._GetProcData())
1924 if self.minor not in proc_info:
1925 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1927 line = proc_info[self.minor]
1928 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1929 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1931 sync_percent = float(match.group(1))
1932 hours = int(match.group(2))
1933 minutes = int(match.group(3))
1934 seconds = int(match.group(4))
1935 est_time = hours * 3600 + minutes * 60 + seconds
1939 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1941 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1943 client_state = match.group(1)
1944 local_disk_state = match.group(2)
1945 is_degraded = (client_state != "Connected" or
1946 local_disk_state != "UpToDate")
1947 return sync_percent, est_time, is_degraded
1949 def GetStatus(self):
1950 """Compute the status of the DRBD device
1952 Note that DRBD devices don't have the STATUS_EXISTING state.
1955 if self.minor is None and not self.Attach():
1956 return self.STATUS_UNKNOWN
1958 data = self._GetProcData()
1959 match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1962 mresult = match.match(line)
1966 logger.Error("Can't find myself!")
1967 return self.STATUS_UNKNOWN
1969 state = mresult.group(2)
1970 if state == "Primary":
1971 result = self.STATUS_ONLINE
1973 result = self.STATUS_STANDBY
1977 def Open(self, force=False):
1978 """Make the local state primary.
1980 If the 'force' parameter is given, the '--do-what-I-say' parameter
1981 is given. Since this is a pottentialy dangerous operation, the
1982 force flag should be only given after creation, when it actually
1986 if self.minor is None and not self.Attach():
1987 logger.Error("DRBD cannot attach to a device during open")
1989 cmd = ["drbdsetup", self.dev_path, "primary"]
1992 result = utils.RunCmd(cmd)
1994 logger.Error("Can't make drbd device primary: %s" % result.output)
1999 """Make the local state secondary.
2001 This will, of course, fail if the device is in use.
2004 if self.minor is None and not self.Attach():
2005 logger.Info("Instance not attached to a device")
2006 raise errors.BlockDeviceError("Can't find device")
2007 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2009 logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2010 raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2013 """Find a DRBD device which matches our config and attach to it.
2015 In case of partially attached (local device matches but no network
2016 setup), we perform the network attach. If successful, we re-test
2017 the attach if can return success.
2020 for minor in self._GetUsedDevs():
2021 info = self._GetDevInfo(minor)
2022 match_l = self._MatchesLocal(info)
2023 match_r = self._MatchesNet(info)
2024 if match_l and match_r:
2026 if match_l and not match_r and "local_addr" not in info:
2027 res_r = self._AssembleNet(minor,
2028 (self._lhost, self._lport,
2029 self._rhost, self._rport),
2031 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
2036 self._SetFromMinor(minor)
2037 return minor is not None
2040 """Assemble the drbd.
2043 - if we have a local backing device, we bind to it by:
2044 - checking the list of used drbd devices
2045 - check if the local minor use of any of them is our own device
2048 - if we have a local/remote net info:
2049 - redo the local backing device step for the remote device
2050 - check if any drbd device is using the local port,
2052 - check if any remote drbd device is using the remote
2053 port, if yes abort (for now)
2055 - bind the remote net port
2059 if self.minor is not None:
2060 logger.Info("Already assembled")
2063 result = super(DRBD8, self).Assemble()
2067 minor = self._FindUnusedMinor()
2068 need_localdev_teardown = False
2069 if self._children[0]:
2070 result = self._AssembleLocal(minor, self._children[0].dev_path,
2071 self._children[1].dev_path)
2074 need_localdev_teardown = True
2075 if self._lhost and self._lport and self._rhost and self._rport:
2076 result = self._AssembleNet(minor,
2077 (self._lhost, self._lport,
2078 self._rhost, self._rport),
2081 if need_localdev_teardown:
2082 # we will ignore failures from this
2083 logger.Error("net setup failed, tearing down local device")
2084 self._ShutdownAll(minor)
2086 self._SetFromMinor(minor)
2090 def _ShutdownLocal(cls, minor):
2091 """Detach from the local device.
2093 I/Os will continue to be served from the remote device. If we
2094 don't have a remote device, this operation will fail.
2097 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2099 logger.Error("Can't detach local device: %s" % result.output)
2100 return not result.failed
2103 def _ShutdownNet(cls, minor):
2104 """Disconnect from the remote peer.
2106 This fails if we don't have a local device.
2109 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2110 logger.Error("Can't shutdown network: %s" % result.output)
2111 return not result.failed
2114 def _ShutdownAll(cls, minor):
2115 """Deactivate the device.
2117 This will, of course, fail if the device is in use.
2120 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2122 logger.Error("Can't shutdown drbd device: %s" % result.output)
2123 return not result.failed
2126 """Shutdown the DRBD device.
2129 if self.minor is None and not self.Attach():
2130 logger.Info("DRBD device not attached to a device during Shutdown")
2132 if not self._ShutdownAll(self.minor):
2135 self.dev_path = None
2138 def Rename(self, new_uid):
2139 """Re-connect this device to another peer.
2142 if self.minor is None:
2143 raise errors.BlockDeviceError("Device not attached during rename")
2144 if self._rhost is not None:
2145 # this means we did have a host when we attached, so we are connected
2146 if not self._ShutdownNet(self.minor):
2147 raise errors.BlockDeviceError("Can't disconnect from remote peer")
2148 old_id = self.unique_id
2151 self.unique_id = new_uid
2152 if not self._AssembleNet(self.minor, self.unique_id, "C"):
2153 logger.Error("Can't attach to new peer!")
2154 if old_id is not None:
2155 self._AssembleNet(self.minor, old_id, "C")
2156 self.unique_id = old_id
2157 raise errors.BlockDeviceError("Can't attach to new peer")
2160 """Stub remove for DRBD devices.
2163 return self.Shutdown()
2166 def Create(cls, unique_id, children, size):
2167 """Create a new DRBD8 device.
2169 Since DRBD devices are not created per se, just assembled, this
2170 function only initializes the metadata.
2173 if len(children) != 2:
2174 raise errors.ProgrammerError("Invalid setup for the drbd device")
2177 if not meta.Attach():
2178 raise errors.BlockDeviceError("Can't attach to meta device")
2179 if not cls._CheckMetaSize(meta.dev_path):
2180 raise errors.BlockDeviceError("Invalid meta device size")
2181 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2182 if not cls._IsValidMeta(meta.dev_path):
2183 raise errors.BlockDeviceError("Cannot initalize meta device")
2184 return cls(unique_id, children)
2188 constants.LD_LV: LogicalVolume,
2189 constants.LD_MD_R1: MDRaid1,
2190 constants.LD_DRBD7: DRBDev,
2191 constants.LD_DRBD8: DRBD8,
2195 def FindDevice(dev_type, unique_id, children):
2196 """Search for an existing, assembled device.
2198 This will succeed only if the device exists and is assembled, but it
2199 does not do any actions in order to activate the device.
2202 if dev_type not in DEV_MAP:
2203 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2204 device = DEV_MAP[dev_type](unique_id, children)
2205 if not device.Attach():
2210 def AttachOrAssemble(dev_type, unique_id, children):
2211 """Try to attach or assemble an existing device.
2213 This will attach to an existing assembled device or will assemble
2214 the device, as needed, to bring it fully up.
2217 if dev_type not in DEV_MAP:
2218 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2219 device = DEV_MAP[dev_type](unique_id, children)
2220 if not device.Attach():
2222 if not device.Attach():
2223 raise errors.BlockDeviceError("Can't find a valid block device for"
2225 (dev_type, unique_id, children))
2229 def Create(dev_type, unique_id, children, size):
2233 if dev_type not in DEV_MAP:
2234 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2235 device = DEV_MAP[dev_type].Create(unique_id, children, size)