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
30 from ganeti import utils
31 from ganeti import logger
32 from ganeti import errors
33 from ganeti import constants
36 class BlockDev(object):
37 """Block device abstract class.
39 A block device can be in the following states:
40 - not existing on the system, and by `Create()` it goes into:
41 - existing but not setup/not active, and by `Assemble()` goes into:
42 - active read-write and by `Open()` it goes into
43 - online (=used, or ready for use)
45 A device can also be online but read-only, however we are not using
46 the readonly state (MD and LV have it, if needed in the future)
47 and we are usually looking at this like at a stack, so it's easier
48 to conceptualise the transition from not-existing to online and back
51 The many different states of the device are due to the fact that we
52 need to cover many device types:
53 - logical volumes are created, lvchange -a y $lv, and used
54 - md arrays are created or assembled and used
55 - drbd devices are attached to a local disk/remote peer and made primary
57 A block device is identified by three items:
58 - the /dev path of the device (dynamic)
59 - a unique ID of the device (static)
60 - it's major/minor pair (dynamic)
62 Not all devices implement both the first two as distinct items. LVM
63 logical volumes have their unique ID (the pair volume group, logical
64 volume name) in a 1-to-1 relation to the dev path. For MD devices,
65 the /dev path is dynamic and the unique ID is the UUID generated at
66 array creation plus the slave list. For DRBD devices, the /dev path
67 is again dynamic and the unique id is the pair (host1, dev1),
70 You can get to a device in two ways:
71 - creating the (real) device, which returns you
72 an attached instance (lvcreate, mdadm --create)
73 - attaching of a python instance to an existing (real) device
75 The second point, the attachement to a device, is different
76 depending on whether the device is assembled or not. At init() time,
77 we search for a device with the same unique_id as us. If found,
78 good. It also means that the device is already assembled. If not,
79 after assembly we'll have our correct major/minor.
82 def __init__(self, unique_id, children):
83 self._children = children
85 self.unique_id = unique_id
90 """Assemble the device from its components.
92 If this is a plain block device (e.g. LVM) than assemble does
93 nothing, as the LVM has no children and we don't put logical
96 One guarantee is that after the device has been assembled, it
97 knows its major/minor numbers. This allows other devices (usually
98 parents) to probe correctly for their children.
102 for child in self._children:
103 if not isinstance(child, BlockDev):
104 raise TypeError("Invalid child passed of type '%s'" % type(child))
107 status = status and child.Assemble()
113 except errors.BlockDeviceError:
114 for child in self._children:
119 for child in self._children:
124 """Find a device which matches our config and attach to it.
127 raise NotImplementedError
130 """Notifies that the device will no longer be used for I/O.
133 raise NotImplementedError
136 def Create(cls, unique_id, children, size):
137 """Create the device.
139 If the device cannot be created, it will return None
140 instead. Error messages go to the logging system.
142 Note that for some devices, the unique_id is used, and for other,
143 the children. The idea is that these two, taken together, are
144 enough for both creation and assembly (later).
147 raise NotImplementedError
150 """Remove this device.
152 This makes sense only for some of the device types: LV and to a
153 lesser degree, md devices. Also note that if the device can't
154 attach, the removal can't be completed.
157 raise NotImplementedError
159 def Rename(self, new_id):
160 """Rename this device.
162 This may or may not make sense for a given device type.
165 raise NotImplementedError
167 def Open(self, force=False):
168 """Make the device ready for use.
170 This makes the device ready for I/O. For now, just the DRBD
173 The force parameter signifies that if the device has any kind of
174 --force thing, it should be used, we know what we are doing.
177 raise NotImplementedError
180 """Shut down the device, freeing its children.
182 This undoes the `Assemble()` work, except for the child
183 assembling; as such, the children on the device are still
184 assembled after this call.
187 raise NotImplementedError
189 def SetSyncSpeed(self, speed):
190 """Adjust the sync speed of the mirror.
192 In case this is not a mirroring device, this is no-op.
197 for child in self._children:
198 result = result and child.SetSyncSpeed(speed)
201 def GetSyncStatus(self):
202 """Returns the sync status of the device.
204 If this device is a mirroring device, this function returns the
205 status of the mirror.
208 (sync_percent, estimated_time, is_degraded, ldisk)
210 If sync_percent is None, it means the device is not syncing.
212 If estimated_time is None, it means we can't estimate
213 the time needed, otherwise it's the time left in seconds.
215 If is_degraded is True, it means the device is missing
216 redundancy. This is usually a sign that something went wrong in
217 the device setup, if sync_percent is None.
219 The ldisk parameter represents the degradation of the local
220 data. This is only valid for some devices, the rest will always
221 return False (not degraded).
224 return None, None, False, False
227 def CombinedSyncStatus(self):
228 """Calculate the mirror status recursively for our children.
230 The return value is the same as for `GetSyncStatus()` except the
231 minimum percent and maximum time are calculated across our
235 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
237 for child in self._children:
238 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
239 if min_percent is None:
240 min_percent = c_percent
241 elif c_percent is not None:
242 min_percent = min(min_percent, c_percent)
245 elif c_time is not None:
246 max_time = max(max_time, c_time)
247 is_degraded = is_degraded or c_degraded
248 ldisk = ldisk or c_ldisk
249 return min_percent, max_time, is_degraded, ldisk
252 def SetInfo(self, text):
253 """Update metadata with info text.
255 Only supported for some device types.
258 for child in self._children:
263 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
264 (self.__class__, self.unique_id, self._children,
265 self.major, self.minor, self.dev_path))
268 class LogicalVolume(BlockDev):
269 """Logical Volume block device.
272 def __init__(self, unique_id, children):
273 """Attaches to a LV device.
275 The unique_id is a tuple (vg_name, lv_name)
278 super(LogicalVolume, self).__init__(unique_id, children)
279 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
280 raise ValueError("Invalid configuration data %s" % str(unique_id))
281 self._vg_name, self._lv_name = unique_id
282 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
359 def Rename(self, new_id):
360 """Rename this logical volume.
363 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
364 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
365 new_vg, new_name = new_id
366 if new_vg != self._vg_name:
367 raise errors.ProgrammerError("Can't move a logical volume across"
368 " volume groups (from %s to to %s)" %
369 (self._vg_name, new_vg))
370 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
372 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
374 self._lv_name = new_name
375 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
378 """Attach to an existing LV.
380 This method will try to see if an existing and active LV exists
381 which matches our name. If so, its major/minor will be
385 result = utils.RunCmd(["lvdisplay", self.dev_path])
387 logger.Error("Can't find LV %s: %s, %s" %
388 (self.dev_path, result.fail_reason, result.output))
390 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
391 for line in result.stdout.splitlines():
392 match_result = match.match(line)
394 self.major = int(match_result.group(1))
395 self.minor = int(match_result.group(2))
400 """Assemble the device.
402 We alway run `lvchange -ay` on the LV to ensure it's active before
403 use, as there were cases when xenvg was not active after boot
404 (also possibly after disk issues).
407 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
409 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
410 return not result.failed
413 """Shutdown the device.
415 This is a no-op for the LV device type, as we don't deactivate the
421 def GetSyncStatus(self):
422 """Returns the sync status of the device.
424 If this device is a mirroring device, this function returns the
425 status of the mirror.
428 (sync_percent, estimated_time, is_degraded, ldisk)
430 For logical volumes, sync_percent and estimated_time are always
431 None (no recovery in progress, as we don't handle the mirrored LV
432 case). The is_degraded parameter is the inverse of the ldisk
435 For the ldisk parameter, we check if the logical volume has the
436 'virtual' type, which means it's not backed by existing storage
437 anymore (read from it return I/O error). This happens after a
438 physical disk failure and subsequent 'vgreduce --removemissing' on
442 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
444 logger.Error("Can't display lv: %s" % result.fail_reason)
445 return None, None, True, True
446 out = result.stdout.strip()
447 # format: type/permissions/alloc/fixed_minor/state/open
449 logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
450 return None, None, True, True
451 ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
453 return None, None, ldisk, ldisk
455 def Open(self, force=False):
456 """Make the device ready for I/O.
458 This is a no-op for the LV device type.
464 """Notifies that the device will no longer be used for I/O.
466 This is a no-op for the LV device type.
471 def Snapshot(self, size):
472 """Create a snapshot copy of an lvm block device.
475 snap_name = self._lv_name + ".snap"
477 # remove existing snapshot if found
478 snap = LogicalVolume((self._vg_name, snap_name), None)
481 pvs_info = self.GetPVInfo(self._vg_name)
483 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
487 free_size, pv_name = pvs_info[0]
489 raise errors.BlockDeviceError("Not enough free space: required %s,"
490 " available %s" % (size, free_size))
492 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
493 "-n%s" % snap_name, self.dev_path])
495 raise errors.BlockDeviceError("command: %s error: %s" %
496 (result.cmd, result.fail_reason))
500 def SetInfo(self, text):
501 """Update metadata with info text.
504 BlockDev.SetInfo(self, text)
506 # Replace invalid characters
507 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
508 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
510 # Only up to 128 characters are allowed
513 result = utils.RunCmd(["lvchange", "--addtag", text,
516 raise errors.BlockDeviceError("Command: %s error: %s" %
517 (result.cmd, result.fail_reason))
520 class MDRaid1(BlockDev):
521 """raid1 device implemented via md.
524 def __init__(self, unique_id, children):
525 super(MDRaid1, self).__init__(unique_id, children)
530 """Find an array which matches our config and attach to it.
532 This tries to find a MD array which has the same UUID as our own.
535 minor = self._FindMDByUUID(self.unique_id)
536 if minor is not None:
537 self._SetFromMinor(minor)
542 return (minor is not None)
546 """Compute the list of in-use MD devices.
548 It doesn't matter if the used device have other raid level, just
549 that they are in use.
552 mdstat = open("/proc/mdstat", "r")
553 data = mdstat.readlines()
557 valid_line = re.compile("^md([0-9]+) : .*$")
559 match = valid_line.match(line)
561 md_no = int(match.group(1))
562 used_md[md_no] = line
567 def _GetDevInfo(minor):
568 """Get info about a MD device.
570 Currently only uuid is returned.
573 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
575 logger.Error("Can't display md: %s" % result.fail_reason)
578 for line in result.stdout.splitlines():
580 kv = line.split(" : ", 1)
583 retval["uuid"] = kv[1].split()[0]
584 elif kv[0] == "State":
585 retval["state"] = kv[1].split(", ")
589 def _FindUnusedMinor():
590 """Compute an unused MD minor.
592 This code assumes that there are 256 minors only.
595 used_md = MDRaid1._GetUsedDevs()
602 logger.Error("Critical: Out of md minor numbers.")
603 raise errors.BlockDeviceError("Can't find a free MD minor")
607 def _FindMDByUUID(cls, uuid):
608 """Find the minor of an MD array with a given UUID.
611 md_list = cls._GetUsedDevs()
612 for minor in md_list:
613 info = cls._GetDevInfo(minor)
614 if info and info["uuid"] == uuid:
619 def _ZeroSuperblock(dev_path):
620 """Zero the possible locations for an MD superblock.
622 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
623 fails in versions 2.x with the same error code as non-writable
626 The superblocks are located at (negative values are relative to
627 the end of the block device):
628 - -128k to end for version 0.90 superblock
629 - -8k to -12k for version 1.0 superblock (included in the above)
630 - 0k to 4k for version 1.1 superblock
631 - 4k to 8k for version 1.2 superblock
633 To cover all situations, the zero-ing will be:
637 As such, the minimum device size must be 128k, otherwise we'll get
640 Note that this function depends on the fact that one can open,
641 read and write block devices normally.
644 overwrite_size = 128 * 1024
645 empty_buf = '\0' * overwrite_size
646 fd = open(dev_path, "r+")
652 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
653 fd.seek(-overwrite_size, 2)
657 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
662 def Create(cls, unique_id, children, size):
663 """Create a new MD raid1 array.
666 if not isinstance(children, (tuple, list)):
667 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
670 if not isinstance(i, BlockDev):
671 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
674 cls._ZeroSuperblock(i.dev_path)
675 except EnvironmentError, err:
676 logger.Error("Can't zero superblock for %s: %s" %
677 (i.dev_path, str(err)))
679 minor = cls._FindUnusedMinor()
680 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
681 "--auto=yes", "--force", "-l1",
682 "-n%d" % len(children)] +
683 [dev.dev_path for dev in children])
686 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
689 info = cls._GetDevInfo(minor)
690 if not info or not "uuid" in info:
691 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
693 return MDRaid1(info["uuid"], children)
696 """Stub remove function for MD RAID 1 arrays.
698 We don't remove the superblock right now. Mark a to do.
701 #TODO: maybe zero superblock on child devices?
702 return self.Shutdown()
704 def Rename(self, new_id):
707 This is not supported for md raid1 devices.
710 raise errors.ProgrammerError("Can't rename a md raid1 device")
712 def AddChildren(self, devices):
713 """Add new member(s) to the md raid1.
716 if self.minor is None and not self.Attach():
717 raise errors.BlockDeviceError("Can't attach to device")
719 args = ["mdadm", "-a", self.dev_path]
721 if dev.dev_path is None:
722 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
724 args.append(dev.dev_path)
725 result = utils.RunCmd(args)
727 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
729 new_len = len(self._children) + len(devices)
730 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
732 raise errors.BlockDeviceError("Can't grow md array: %s" %
734 self._children.extend(devices)
736 def RemoveChildren(self, devices):
737 """Remove member(s) from the md raid1.
740 if self.minor is None and not self.Attach():
741 raise errors.BlockDeviceError("Can't attach to device")
742 new_len = len(self._children) - len(devices)
744 raise errors.BlockDeviceError("Can't reduce to less than one child")
745 args = ["mdadm", "-f", self.dev_path]
749 for c in self._children:
750 if c.dev_path == dev:
754 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
756 result = utils.RunCmd(args)
758 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
761 # it seems here we need a short delay for MD to update its
765 result = utils.RunCmd(args)
767 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
768 " %s" % result.output)
769 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
772 raise errors.BlockDeviceError("Can't shrink md array: %s" %
774 for dev in orig_devs:
775 self._children.remove(dev)
777 def _SetFromMinor(self, minor):
778 """Set our parameters based on the given minor.
780 This sets our minor variable and our dev_path.
784 self.dev_path = "/dev/md%d" % minor
787 """Assemble the MD device.
789 At this point we should have:
790 - list of children devices
794 result = super(MDRaid1, self).Assemble()
797 md_list = self._GetUsedDevs()
798 for minor in md_list:
799 info = self._GetDevInfo(minor)
800 if info and info["uuid"] == self.unique_id:
801 self._SetFromMinor(minor)
802 logger.Info("MD array %s already started" % str(self))
804 free_minor = self._FindUnusedMinor()
805 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
806 self.unique_id, "/dev/md%d" % free_minor] +
807 [bdev.dev_path for bdev in self._children])
809 logger.Error("Can't assemble MD array: %s: %s" %
810 (result.fail_reason, result.output))
813 self.minor = free_minor
814 return not result.failed
817 """Tear down the MD array.
819 This does a 'mdadm --stop' so after this command, the array is no
823 if self.minor is None and not self.Attach():
824 logger.Info("MD object not attached to a device")
827 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
829 logger.Error("Can't stop MD array: %s" % result.fail_reason)
835 def SetSyncSpeed(self, kbytes):
836 """Set the maximum sync speed for the MD array.
839 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
840 if self.minor is None:
841 logger.Error("MD array not attached to a device")
843 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
845 f.write("%d" % kbytes)
848 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
850 f.write("%d" % (kbytes/2))
855 def GetSyncStatus(self):
856 """Returns the sync status of the device.
859 (sync_percent, estimated_time, is_degraded, ldisk)
861 If sync_percent is None, it means all is ok
862 If estimated_time is None, it means we can't esimate
863 the time needed, otherwise it's the time left in seconds.
865 The ldisk parameter is always true for MD devices.
868 if self.minor is None and not self.Attach():
869 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
870 dev_info = self._GetDevInfo(self.minor)
871 is_clean = ("state" in dev_info and
872 len(dev_info["state"]) == 1 and
873 dev_info["state"][0] in ("clean", "active"))
874 sys_path = "/sys/block/md%s/md/" % self.minor
875 f = file(sys_path + "sync_action")
876 sync_status = f.readline().strip()
878 if sync_status == "idle":
879 return None, None, not is_clean, False
880 f = file(sys_path + "sync_completed")
881 sync_completed = f.readline().strip().split(" / ")
883 if len(sync_completed) != 2:
884 return 0, None, not is_clean, False
885 sync_done, sync_total = [float(i) for i in sync_completed]
886 sync_percent = 100.0*sync_done/sync_total
887 f = file(sys_path + "sync_speed")
888 sync_speed_k = int(f.readline().strip())
889 if sync_speed_k == 0:
892 time_est = (sync_total - sync_done) / 2 / sync_speed_k
893 return sync_percent, time_est, not is_clean, False
895 def Open(self, force=False):
896 """Make the device ready for I/O.
898 This is a no-op for the MDRaid1 device type, although we could use
899 the 2.6.18's new array_state thing.
905 """Notifies that the device will no longer be used for I/O.
907 This is a no-op for the MDRaid1 device type, but see comment for
914 class BaseDRBD(BlockDev):
917 This class contains a few bits of common functionality between the
918 0.7 and 8.x versions of DRBD.
921 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
922 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
925 _ST_UNCONFIGURED = "Unconfigured"
926 _ST_WFCONNECTION = "WFConnection"
927 _ST_CONNECTED = "Connected"
931 """Return data from /proc/drbd.
934 stat = open("/proc/drbd", "r")
936 data = stat.read().splitlines()
940 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
944 def _MassageProcData(data):
945 """Transform the output of _GetProdData into a nicer form.
948 a dictionary of minor: joined lines from /proc/drbd for that minor
951 lmatch = re.compile("^ *([0-9]+):.*$")
953 old_minor = old_line = None
955 lresult = lmatch.match(line)
956 if lresult is not None:
957 if old_minor is not None:
958 results[old_minor] = old_line
959 old_minor = int(lresult.group(1))
962 if old_minor is not None:
963 old_line += " " + line.strip()
965 if old_minor is not None:
966 results[old_minor] = old_line
970 def _GetVersion(cls):
971 """Return the DRBD version.
973 This will return a dict with keys:
979 proto2 (only on drbd > 8.2.X)
982 proc_data = cls._GetProcData()
983 first_line = proc_data[0].strip()
984 version = cls._VERSION_RE.match(first_line)
986 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
989 values = version.groups()
990 retval = {'k_major': int(values[0]),
991 'k_minor': int(values[1]),
992 'k_point': int(values[2]),
993 'api': int(values[3]),
994 'proto': int(values[4]),
996 if values[5] is not None:
997 retval['proto2'] = values[5]
1002 def _DevPath(minor):
1003 """Return the path to a drbd device for a given minor.
1006 return "/dev/drbd%d" % minor
1009 def _GetUsedDevs(cls):
1010 """Compute the list of used DRBD devices.
1013 data = cls._GetProcData()
1016 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1018 match = valid_line.match(line)
1021 minor = int(match.group(1))
1022 state = match.group(2)
1023 if state == cls._ST_UNCONFIGURED:
1025 used_devs[minor] = state, line
1029 def _SetFromMinor(self, minor):
1030 """Set our parameters based on the given minor.
1032 This sets our minor variable and our dev_path.
1036 self.minor = self.dev_path = None
1039 self.dev_path = self._DevPath(minor)
1042 def _CheckMetaSize(meta_device):
1043 """Check if the given meta device looks like a valid one.
1045 This currently only check the size, which must be around
1049 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1051 logger.Error("Failed to get device size: %s" % result.fail_reason)
1054 sectors = int(result.stdout)
1056 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1058 bytes = sectors * 512
1059 if bytes < 128 * 1024 * 1024: # less than 128MiB
1060 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1062 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1063 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1067 def Rename(self, new_id):
1070 This is not supported for drbd devices.
1073 raise errors.ProgrammerError("Can't rename a drbd device")
1076 class DRBDev(BaseDRBD):
1077 """DRBD block device.
1079 This implements the local host part of the DRBD device, i.e. it
1080 doesn't do anything to the supposed peer. If you need a fully
1081 connected DRBD pair, you need to use this class on both hosts.
1083 The unique_id for the drbd device is the (local_ip, local_port,
1084 remote_ip, remote_port) tuple, and it must have two children: the
1085 data device and the meta_device. The meta device is checked for
1086 valid size and is zeroed on create.
1089 def __init__(self, unique_id, children):
1090 super(DRBDev, self).__init__(unique_id, children)
1091 self.major = self._DRBD_MAJOR
1092 version = self._GetVersion()
1093 if version['k_major'] != 0 and version['k_minor'] != 7:
1094 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1095 " requested ganeti usage: kernel is"
1096 " %s.%s, ganeti wants 0.7" %
1097 (version['k_major'], version['k_minor']))
1098 if len(children) != 2:
1099 raise ValueError("Invalid configuration data %s" % str(children))
1100 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1101 raise ValueError("Invalid configuration data %s" % str(unique_id))
1102 self._lhost, self._lport, self._rhost, self._rport = unique_id
1106 def _FindUnusedMinor(cls):
1107 """Find an unused DRBD device.
1110 data = cls._GetProcData()
1112 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1114 match = valid_line.match(line)
1116 return int(match.group(1))
1117 logger.Error("Error: no free drbd minors!")
1118 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1121 def _GetDevInfo(cls, minor):
1122 """Get details about a given DRBD minor.
1124 This return, if available, the local backing device in (major,
1125 minor) formant and the local and remote (ip, port) information.
1129 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1131 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1134 if out == "Not configured\n":
1136 for line in out.splitlines():
1137 if "local_dev" not in data:
1138 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1140 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1142 if "meta_dev" not in data:
1143 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1145 if match.group(2) is not None and match.group(3) is not None:
1146 # matched on the major/minor
1147 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1149 # matched on the "internal" string
1150 data["meta_dev"] = match.group(1)
1151 # in this case, no meta_index is in the output
1152 data["meta_index"] = -1
1154 if "meta_index" not in data:
1155 match = re.match("^Meta index: ([0-9]+).*$", line)
1157 data["meta_index"] = int(match.group(1))
1159 if "local_addr" not in data:
1160 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1162 data["local_addr"] = (match.group(1), int(match.group(2)))
1164 if "remote_addr" not in data:
1165 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1167 data["remote_addr"] = (match.group(1), int(match.group(2)))
1171 def _MatchesLocal(self, info):
1172 """Test if our local config matches with an existing device.
1174 The parameter should be as returned from `_GetDevInfo()`. This
1175 method tests if our local backing device is the same as the one in
1176 the info parameter, in effect testing if we look like the given
1180 if not ("local_dev" in info and "meta_dev" in info and
1181 "meta_index" in info):
1184 backend = self._children[0]
1185 if backend is not None:
1186 retval = (info["local_dev"] == (backend.major, backend.minor))
1188 retval = (info["local_dev"] == (0, 0))
1189 meta = self._children[1]
1190 if meta is not None:
1191 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1192 retval = retval and (info["meta_index"] == 0)
1194 retval = retval and (info["meta_dev"] == "internal" and
1195 info["meta_index"] == -1)
1198 def _MatchesNet(self, info):
1199 """Test if our network config matches with an existing device.
1201 The parameter should be as returned from `_GetDevInfo()`. This
1202 method tests if our network configuration is the same as the one
1203 in the info parameter, in effect testing if we look like the given
1207 if (((self._lhost is None and not ("local_addr" in info)) and
1208 (self._rhost is None and not ("remote_addr" in info)))):
1211 if self._lhost is None:
1214 if not ("local_addr" in info and
1215 "remote_addr" in info):
1218 retval = (info["local_addr"] == (self._lhost, self._lport))
1219 retval = (retval and
1220 info["remote_addr"] == (self._rhost, self._rport))
1224 def _AssembleLocal(cls, minor, backend, meta):
1225 """Configure the local part of a DRBD device.
1227 This is the first thing that must be done on an unconfigured DRBD
1228 device. And it must be done only once.
1231 if not cls._CheckMetaSize(meta):
1233 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1234 backend, meta, "0", "-e", "detach"])
1236 logger.Error("Can't attach local disk: %s" % result.output)
1237 return not result.failed
1240 def _ShutdownLocal(cls, minor):
1241 """Detach from the local device.
1243 I/Os will continue to be served from the remote device. If we
1244 don't have a remote device, this operation will fail.
1247 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1249 logger.Error("Can't detach local device: %s" % result.output)
1250 return not result.failed
1253 def _ShutdownAll(minor):
1254 """Deactivate the device.
1256 This will, of course, fail if the device is in use.
1259 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1261 logger.Error("Can't shutdown drbd device: %s" % result.output)
1262 return not result.failed
1265 def _AssembleNet(cls, minor, net_info, protocol):
1266 """Configure the network part of the device.
1268 This operation can be, in theory, done multiple times, but there
1269 have been cases (in lab testing) in which the network part of the
1270 device had become stuck and couldn't be shut down because activity
1271 from the new peer (also stuck) triggered a timer re-init and
1272 needed remote peer interface shutdown in order to clear. So please
1273 don't change online the net config.
1276 lhost, lport, rhost, rport = net_info
1277 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1278 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1281 logger.Error("Can't setup network for dbrd device: %s" %
1285 timeout = time.time() + 10
1287 while time.time() < timeout:
1288 info = cls._GetDevInfo(minor)
1289 if not "local_addr" in info or not "remote_addr" in info:
1292 if (info["local_addr"] != (lhost, lport) or
1293 info["remote_addr"] != (rhost, rport)):
1299 logger.Error("Timeout while configuring network")
1304 def _ShutdownNet(cls, minor):
1305 """Disconnect from the remote peer.
1307 This fails if we don't have a local device.
1310 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1312 logger.Error("Can't shutdown network: %s" % result.output)
1313 return not result.failed
1316 """Assemble the drbd.
1319 - if we have a local backing device, we bind to it by:
1320 - checking the list of used drbd devices
1321 - check if the local minor use of any of them is our own device
1324 - if we have a local/remote net info:
1325 - redo the local backing device step for the remote device
1326 - check if any drbd device is using the local port,
1328 - check if any remote drbd device is using the remote
1329 port, if yes abort (for now)
1331 - bind the remote net port
1335 if self.minor is not None:
1336 logger.Info("Already assembled")
1339 result = super(DRBDev, self).Assemble()
1343 minor = self._FindUnusedMinor()
1344 need_localdev_teardown = False
1345 if self._children[0]:
1346 result = self._AssembleLocal(minor, self._children[0].dev_path,
1347 self._children[1].dev_path)
1350 need_localdev_teardown = True
1351 if self._lhost and self._lport and self._rhost and self._rport:
1352 result = self._AssembleNet(minor,
1353 (self._lhost, self._lport,
1354 self._rhost, self._rport),
1357 if need_localdev_teardown:
1358 # we will ignore failures from this
1359 logger.Error("net setup failed, tearing down local device")
1360 self._ShutdownAll(minor)
1362 self._SetFromMinor(minor)
1366 """Shutdown the DRBD device.
1369 if self.minor is None and not self.Attach():
1370 logger.Info("DRBD device not attached to a device during Shutdown")
1372 if not self._ShutdownAll(self.minor):
1375 self.dev_path = None
1379 """Find a DRBD device which matches our config and attach to it.
1381 In case of partially attached (local device matches but no network
1382 setup), we perform the network attach. If successful, we re-test
1383 the attach if can return success.
1386 for minor in self._GetUsedDevs():
1387 info = self._GetDevInfo(minor)
1388 match_l = self._MatchesLocal(info)
1389 match_r = self._MatchesNet(info)
1390 if match_l and match_r:
1392 if match_l and not match_r and "local_addr" not in info:
1393 res_r = self._AssembleNet(minor,
1394 (self._lhost, self._lport,
1395 self._rhost, self._rport),
1397 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1402 self._SetFromMinor(minor)
1403 return minor is not None
1405 def Open(self, force=False):
1406 """Make the local state primary.
1408 If the 'force' parameter is given, the '--do-what-I-say' parameter
1409 is given. Since this is a pottentialy dangerous operation, the
1410 force flag should be only given after creation, when it actually
1414 if self.minor is None and not self.Attach():
1415 logger.Error("DRBD cannot attach to a device during open")
1417 cmd = ["drbdsetup", self.dev_path, "primary"]
1419 cmd.append("--do-what-I-say")
1420 result = utils.RunCmd(cmd)
1422 msg = ("Can't make drbd device primary: %s" % result.output)
1424 raise errors.BlockDeviceError(msg)
1427 """Make the local state secondary.
1429 This will, of course, fail if the device is in use.
1432 if self.minor is None and not self.Attach():
1433 logger.Info("Instance not attached to a device")
1434 raise errors.BlockDeviceError("Can't find device")
1435 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1437 msg = ("Can't switch drbd device to"
1438 " secondary: %s" % result.output)
1440 raise errors.BlockDeviceError(msg)
1442 def SetSyncSpeed(self, kbytes):
1443 """Set the speed of the DRBD syncer.
1446 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1447 if self.minor is None:
1448 logger.Info("Instance not attached to a device")
1450 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1453 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1454 return not result.failed and children_result
1456 def GetSyncStatus(self):
1457 """Returns the sync status of the device.
1460 (sync_percent, estimated_time, is_degraded, ldisk)
1462 If sync_percent is None, it means all is ok
1463 If estimated_time is None, it means we can't esimate
1464 the time needed, otherwise it's the time left in seconds.
1466 The ldisk parameter will be returned as True, since the DRBD7
1467 devices have not been converted.
1470 if self.minor is None and not self.Attach():
1471 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1472 proc_info = self._MassageProcData(self._GetProcData())
1473 if self.minor not in proc_info:
1474 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1476 line = proc_info[self.minor]
1477 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1478 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1480 sync_percent = float(match.group(1))
1481 hours = int(match.group(2))
1482 minutes = int(match.group(3))
1483 seconds = int(match.group(4))
1484 est_time = hours * 3600 + minutes * 60 + seconds
1488 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1490 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1492 client_state = match.group(1)
1493 is_degraded = client_state != "Connected"
1494 return sync_percent, est_time, is_degraded, False
1497 def _ZeroDevice(device):
1500 This writes until we get ENOSPC.
1503 f = open(device, "w")
1504 buf = "\0" * 1048576
1508 except IOError, err:
1509 if err.errno != errno.ENOSPC:
1513 def Create(cls, unique_id, children, size):
1514 """Create a new DRBD device.
1516 Since DRBD devices are not created per se, just assembled, this
1517 function just zeroes the meta device.
1520 if len(children) != 2:
1521 raise errors.ProgrammerError("Invalid setup for the drbd device")
1524 if not meta.Attach():
1525 raise errors.BlockDeviceError("Can't attach to meta device")
1526 if not cls._CheckMetaSize(meta.dev_path):
1527 raise errors.BlockDeviceError("Invalid meta device")
1528 logger.Info("Started zeroing device %s" % meta.dev_path)
1529 cls._ZeroDevice(meta.dev_path)
1530 logger.Info("Done zeroing device %s" % meta.dev_path)
1531 return cls(unique_id, children)
1534 """Stub remove for DRBD devices.
1537 return self.Shutdown()
1540 class DRBD8(BaseDRBD):
1541 """DRBD v8.x block device.
1543 This implements the local host part of the DRBD device, i.e. it
1544 doesn't do anything to the supposed peer. If you need a fully
1545 connected DRBD pair, you need to use this class on both hosts.
1547 The unique_id for the drbd device is the (local_ip, local_port,
1548 remote_ip, remote_port) tuple, and it must have two children: the
1549 data device and the meta_device. The meta device is checked for
1550 valid size and is zeroed on create.
1556 def __init__(self, unique_id, children):
1557 if children and children.count(None) > 0:
1559 super(DRBD8, self).__init__(unique_id, children)
1560 self.major = self._DRBD_MAJOR
1561 version = self._GetVersion()
1562 if version['k_major'] != 8 :
1563 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1564 " requested ganeti usage: kernel is"
1565 " %s.%s, ganeti wants 8.x" %
1566 (version['k_major'], version['k_minor']))
1568 if len(children) not in (0, 2):
1569 raise ValueError("Invalid configuration data %s" % str(children))
1570 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1571 raise ValueError("Invalid configuration data %s" % str(unique_id))
1572 self._lhost, self._lport, self._rhost, self._rport = unique_id
1576 def _InitMeta(cls, minor, dev_path):
1577 """Initialize a meta device.
1579 This will not work if the given minor is in use.
1582 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1583 "v08", dev_path, "0", "create-md"])
1585 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1589 def _FindUnusedMinor(cls):
1590 """Find an unused DRBD device.
1592 This is specific to 8.x as the minors are allocated dynamically,
1593 so non-existing numbers up to a max minor count are actually free.
1596 data = cls._GetProcData()
1598 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1599 used_line = re.compile("^ *([0-9]+): cs:")
1602 match = unused_line.match(line)
1604 return int(match.group(1))
1605 match = used_line.match(line)
1607 minor = int(match.group(1))
1608 highest = max(highest, minor)
1609 if highest is None: # there are no minors in use at all
1611 if highest >= cls._MAX_MINORS:
1612 logger.Error("Error: no free drbd minors!")
1613 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1617 def _IsValidMeta(cls, meta_device):
1618 """Check if the given meta device looks like a valid one.
1621 minor = cls._FindUnusedMinor()
1622 minor_path = cls._DevPath(minor)
1623 result = utils.RunCmd(["drbdmeta", minor_path,
1624 "v08", meta_device, "0",
1627 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1632 def _GetShowParser(cls):
1633 """Return a parser for `drbd show` output.
1635 This will either create or return an already-create parser for the
1636 output of the command `drbd show`.
1639 if cls._PARSE_SHOW is not None:
1640 return cls._PARSE_SHOW
1643 lbrace = pyp.Literal("{").suppress()
1644 rbrace = pyp.Literal("}").suppress()
1645 semi = pyp.Literal(";").suppress()
1646 # this also converts the value to an int
1647 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1649 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1650 defa = pyp.Literal("_is_default").suppress()
1651 dbl_quote = pyp.Literal('"').suppress()
1653 keyword = pyp.Word(pyp.alphanums + '-')
1656 value = pyp.Word(pyp.alphanums + '_-/.:')
1657 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1658 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1660 # meta device, extended syntax
1661 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1662 number + pyp.Word(']').suppress())
1665 stmt = (~rbrace + keyword + ~lbrace +
1666 (addr_port ^ value ^ quoted ^ meta_value) +
1667 pyp.Optional(defa) + semi +
1668 pyp.Optional(pyp.restOfLine).suppress())
1671 section_name = pyp.Word(pyp.alphas + '_')
1672 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1674 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1677 cls._PARSE_SHOW = bnf
1682 def _GetShowData(cls, minor):
1683 """Return the `drbdsetup show` data for a minor.
1686 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1688 logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1690 return result.stdout
1693 def _GetDevInfo(cls, out):
1694 """Parse details about a given DRBD minor.
1696 This return, if available, the local backing device (as a path)
1697 and the local and remote (ip, port) information from a string
1698 containing the output of the `drbdsetup show` command as returned
1706 bnf = cls._GetShowParser()
1710 results = bnf.parseString(out)
1711 except pyp.ParseException, err:
1712 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1715 # and massage the results into our desired format
1716 for section in results:
1718 if sname == "_this_host":
1719 for lst in section[1:]:
1720 if lst[0] == "disk":
1721 data["local_dev"] = lst[1]
1722 elif lst[0] == "meta-disk":
1723 data["meta_dev"] = lst[1]
1724 data["meta_index"] = lst[2]
1725 elif lst[0] == "address":
1726 data["local_addr"] = tuple(lst[1:])
1727 elif sname == "_remote_host":
1728 for lst in section[1:]:
1729 if lst[0] == "address":
1730 data["remote_addr"] = tuple(lst[1:])
1733 def _MatchesLocal(self, info):
1734 """Test if our local config matches with an existing device.
1736 The parameter should be as returned from `_GetDevInfo()`. This
1737 method tests if our local backing device is the same as the one in
1738 the info parameter, in effect testing if we look like the given
1743 backend, meta = self._children
1745 backend = meta = None
1747 if backend is not None:
1748 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1750 retval = ("local_dev" not in info)
1752 if meta is not None:
1753 retval = retval and ("meta_dev" in info and
1754 info["meta_dev"] == meta.dev_path)
1755 retval = retval and ("meta_index" in info and
1756 info["meta_index"] == 0)
1758 retval = retval and ("meta_dev" not in info and
1759 "meta_index" not in info)
1762 def _MatchesNet(self, info):
1763 """Test if our network config matches with an existing device.
1765 The parameter should be as returned from `_GetDevInfo()`. This
1766 method tests if our network configuration is the same as the one
1767 in the info parameter, in effect testing if we look like the given
1771 if (((self._lhost is None and not ("local_addr" in info)) and
1772 (self._rhost is None and not ("remote_addr" in info)))):
1775 if self._lhost is None:
1778 if not ("local_addr" in info and
1779 "remote_addr" in info):
1782 retval = (info["local_addr"] == (self._lhost, self._lport))
1783 retval = (retval and
1784 info["remote_addr"] == (self._rhost, self._rport))
1788 def _AssembleLocal(cls, minor, backend, meta):
1789 """Configure the local part of a DRBD device.
1791 This is the first thing that must be done on an unconfigured DRBD
1792 device. And it must be done only once.
1795 if not cls._IsValidMeta(meta):
1797 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1798 backend, meta, "0", "-e", "detach",
1801 logger.Error("Can't attach local disk: %s" % result.output)
1802 return not result.failed
1805 def _AssembleNet(cls, minor, net_info, protocol,
1806 dual_pri=False, hmac=None, secret=None):
1807 """Configure the network part of the device.
1810 lhost, lport, rhost, rport = net_info
1811 if None in net_info:
1812 # we don't want network connection and actually want to make
1814 return cls._ShutdownNet(minor)
1816 args = ["drbdsetup", cls._DevPath(minor), "net",
1817 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1818 "-A", "discard-zero-changes",
1825 args.extend(["-a", hmac, "-x", secret])
1826 result = utils.RunCmd(args)
1828 logger.Error("Can't setup network for dbrd device: %s" %
1832 timeout = time.time() + 10
1834 while time.time() < timeout:
1835 info = cls._GetDevInfo(cls._GetShowData(minor))
1836 if not "local_addr" in info or not "remote_addr" in info:
1839 if (info["local_addr"] != (lhost, lport) or
1840 info["remote_addr"] != (rhost, rport)):
1846 logger.Error("Timeout while configuring network")
1850 def AddChildren(self, devices):
1851 """Add a disk to the DRBD device.
1854 if self.minor is None:
1855 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1856 if len(devices) != 2:
1857 raise errors.BlockDeviceError("Need two devices for AddChildren")
1858 info = self._GetDevInfo(self._GetShowData(self.minor))
1859 if "local_dev" in info:
1860 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1861 backend, meta = devices
1862 if backend.dev_path is None or meta.dev_path is None:
1863 raise errors.BlockDeviceError("Children not ready during AddChildren")
1866 if not self._CheckMetaSize(meta.dev_path):
1867 raise errors.BlockDeviceError("Invalid meta device size")
1868 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1869 if not self._IsValidMeta(meta.dev_path):
1870 raise errors.BlockDeviceError("Cannot initalize meta device")
1872 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1873 raise errors.BlockDeviceError("Can't attach to local storage")
1874 self._children = devices
1876 def RemoveChildren(self, devices):
1877 """Detach the drbd device from local storage.
1880 if self.minor is None:
1881 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1883 # early return if we don't actually have backing storage
1884 info = self._GetDevInfo(self._GetShowData(self.minor))
1885 if "local_dev" not in info:
1887 if len(self._children) != 2:
1888 raise errors.BlockDeviceError("We don't have two children: %s" %
1890 if self._children.count(None) == 2: # we don't actually have children :)
1891 logger.Error("Requested detach while detached")
1893 if len(devices) != 2:
1894 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1895 for child, dev in zip(self._children, devices):
1896 if dev != child.dev_path:
1897 raise errors.BlockDeviceError("Mismatch in local storage"
1898 " (%s != %s) in RemoveChildren" %
1899 (dev, child.dev_path))
1901 if not self._ShutdownLocal(self.minor):
1902 raise errors.BlockDeviceError("Can't detach from local storage")
1905 def SetSyncSpeed(self, kbytes):
1906 """Set the speed of the DRBD syncer.
1909 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1910 if self.minor is None:
1911 logger.Info("Instance not attached to a device")
1913 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1916 logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1917 return not result.failed and children_result
1919 def GetSyncStatus(self):
1920 """Returns the sync status of the device.
1923 (sync_percent, estimated_time, is_degraded)
1925 If sync_percent is None, it means all is ok
1926 If estimated_time is None, it means we can't esimate
1927 the time needed, otherwise it's the time left in seconds.
1930 We set the is_degraded parameter to True on two conditions:
1931 network not connected or local disk missing.
1933 We compute the ldisk parameter based on wheter we have a local
1937 if self.minor is None and not self.Attach():
1938 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1939 proc_info = self._MassageProcData(self._GetProcData())
1940 if self.minor not in proc_info:
1941 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1943 line = proc_info[self.minor]
1944 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1945 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1947 sync_percent = float(match.group(1))
1948 hours = int(match.group(2))
1949 minutes = int(match.group(3))
1950 seconds = int(match.group(4))
1951 est_time = hours * 3600 + minutes * 60 + seconds
1955 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1957 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1959 client_state = match.group(1)
1960 local_disk_state = match.group(2)
1961 ldisk = local_disk_state != "UpToDate"
1962 is_degraded = client_state != "Connected"
1963 return sync_percent, est_time, is_degraded or ldisk, ldisk
1965 def Open(self, force=False):
1966 """Make the local state primary.
1968 If the 'force' parameter is given, the '-o' option is passed to
1969 drbdsetup. Since this is a potentially dangerous operation, the
1970 force flag should be only given after creation, when it actually
1974 if self.minor is None and not self.Attach():
1975 logger.Error("DRBD cannot attach to a device during open")
1977 cmd = ["drbdsetup", self.dev_path, "primary"]
1980 result = utils.RunCmd(cmd)
1982 msg = ("Can't make drbd device primary: %s" % result.output)
1984 raise errors.BlockDeviceError(msg)
1987 """Make the local state secondary.
1989 This will, of course, fail if the device is in use.
1992 if self.minor is None and not self.Attach():
1993 logger.Info("Instance not attached to a device")
1994 raise errors.BlockDeviceError("Can't find device")
1995 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1997 msg = ("Can't switch drbd device to"
1998 " secondary: %s" % result.output)
2000 raise errors.BlockDeviceError(msg)
2003 """Find a DRBD device which matches our config and attach to it.
2005 In case of partially attached (local device matches but no network
2006 setup), we perform the network attach. If successful, we re-test
2007 the attach if can return success.
2010 for minor in self._GetUsedDevs():
2011 info = self._GetDevInfo(self._GetShowData(minor))
2012 match_l = self._MatchesLocal(info)
2013 match_r = self._MatchesNet(info)
2014 if match_l and match_r:
2016 if match_l and not match_r and "local_addr" not in info:
2017 res_r = self._AssembleNet(minor,
2018 (self._lhost, self._lport,
2019 self._rhost, self._rport),
2022 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2024 # the weakest case: we find something that is only net attached
2025 # even though we were passed some children at init time
2026 if match_r and "local_dev" not in info:
2029 # this case must be considered only if we actually have local
2030 # storage, i.e. not in diskless mode, because all diskless
2031 # devices are equal from the point of view of local
2033 if (match_l and "local_dev" in info and
2034 not match_r and "local_addr" in info):
2035 # strange case - the device network part points to somewhere
2036 # else, even though its local storage is ours; as we own the
2037 # drbd space, we try to disconnect from the remote peer and
2038 # reconnect to our correct one
2039 if not self._ShutdownNet(minor):
2040 raise errors.BlockDeviceError("Device has correct local storage,"
2041 " wrong remote peer and is unable to"
2042 " disconnect in order to attach to"
2043 " the correct peer")
2044 # note: _AssembleNet also handles the case when we don't want
2045 # local storage (i.e. one or more of the _[lr](host|port) is
2047 if (self._AssembleNet(minor, (self._lhost, self._lport,
2048 self._rhost, self._rport), "C") and
2049 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2055 self._SetFromMinor(minor)
2056 return minor is not None
2059 """Assemble the drbd.
2062 - if we have a local backing device, we bind to it by:
2063 - checking the list of used drbd devices
2064 - check if the local minor use of any of them is our own device
2067 - if we have a local/remote net info:
2068 - redo the local backing device step for the remote device
2069 - check if any drbd device is using the local port,
2071 - check if any remote drbd device is using the remote
2072 port, if yes abort (for now)
2074 - bind the remote net port
2078 if self.minor is not None:
2079 logger.Info("Already assembled")
2082 result = super(DRBD8, self).Assemble()
2086 minor = self._FindUnusedMinor()
2087 need_localdev_teardown = False
2088 if self._children and self._children[0] and self._children[1]:
2089 result = self._AssembleLocal(minor, self._children[0].dev_path,
2090 self._children[1].dev_path)
2093 need_localdev_teardown = True
2094 if self._lhost and self._lport and self._rhost and self._rport:
2095 result = self._AssembleNet(minor,
2096 (self._lhost, self._lport,
2097 self._rhost, self._rport),
2100 if need_localdev_teardown:
2101 # we will ignore failures from this
2102 logger.Error("net setup failed, tearing down local device")
2103 self._ShutdownAll(minor)
2105 self._SetFromMinor(minor)
2109 def _ShutdownLocal(cls, minor):
2110 """Detach from the local device.
2112 I/Os will continue to be served from the remote device. If we
2113 don't have a remote device, this operation will fail.
2116 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2118 logger.Error("Can't detach local device: %s" % result.output)
2119 return not result.failed
2122 def _ShutdownNet(cls, minor):
2123 """Disconnect from the remote peer.
2125 This fails if we don't have a local device.
2128 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2130 logger.Error("Can't shutdown network: %s" % result.output)
2131 return not result.failed
2134 def _ShutdownAll(cls, minor):
2135 """Deactivate the device.
2137 This will, of course, fail if the device is in use.
2140 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2142 logger.Error("Can't shutdown drbd device: %s" % result.output)
2143 return not result.failed
2146 """Shutdown the DRBD device.
2149 if self.minor is None and not self.Attach():
2150 logger.Info("DRBD device not attached to a device during Shutdown")
2152 if not self._ShutdownAll(self.minor):
2155 self.dev_path = None
2159 """Stub remove for DRBD devices.
2162 return self.Shutdown()
2165 def Create(cls, unique_id, children, size):
2166 """Create a new DRBD8 device.
2168 Since DRBD devices are not created per se, just assembled, this
2169 function only initializes the metadata.
2172 if len(children) != 2:
2173 raise errors.ProgrammerError("Invalid setup for the drbd device")
2176 if not meta.Attach():
2177 raise errors.BlockDeviceError("Can't attach to meta device")
2178 if not cls._CheckMetaSize(meta.dev_path):
2179 raise errors.BlockDeviceError("Invalid meta device size")
2180 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2181 if not cls._IsValidMeta(meta.dev_path):
2182 raise errors.BlockDeviceError("Cannot initalize meta device")
2183 return cls(unique_id, children)
2186 class FileStorage(BlockDev):
2189 This class represents the a file storage backend device.
2191 The unique_id for the file device is a (file_driver, file_path) tuple.
2194 def __init__(self, unique_id, children):
2195 """Initalizes a file device backend.
2199 raise errors.BlockDeviceError("Invalid setup for file device")
2200 super(FileStorage, self).__init__(unique_id, children)
2201 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2202 raise ValueError("Invalid configuration data %s" % str(unique_id))
2203 self.driver = unique_id[0]
2204 self.dev_path = unique_id[1]
2207 """Assemble the device.
2209 Checks whether the file device exists, raises BlockDeviceError otherwise.
2212 if not os.path.exists(self.dev_path):
2213 raise errors.BlockDeviceError("File device '%s' does not exist." %
2218 """Shutdown the device.
2220 This is a no-op for the file type, as we don't deacivate
2221 the file on shutdown.
2226 def Open(self, force=False):
2227 """Make the device ready for I/O.
2229 This is a no-op for the file type.
2235 """Notifies that the device will no longer be used for I/O.
2237 This is a no-op for the file type.
2243 """Remove the file backing the block device.
2246 boolean indicating wheter removal of file was successful or not.
2249 if not os.path.exists(self.dev_path):
2252 os.remove(self.dev_path)
2254 except OSError, err:
2255 logger.Error("Can't remove file '%s': %s"
2256 % (self.dev_path, err))
2260 """Attach to an existing file.
2262 Check if this file already exists.
2265 boolean indicating if file exists or not.
2268 if os.path.exists(self.dev_path):
2273 def Create(cls, unique_id, children, size):
2274 """Create a new file.
2278 size: integer size of file in MiB
2281 A ganeti.bdev.FileStorage object.
2284 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2285 raise ValueError("Invalid configuration data %s" % str(unique_id))
2286 dev_path = unique_id[1]
2288 f = open(dev_path, 'w')
2289 except IOError, err:
2290 raise errors.BlockDeviceError("Could not create '%'" % err)
2292 f.truncate(size * 1024 * 1024)
2295 return FileStorage(unique_id, children)
2299 constants.LD_LV: LogicalVolume,
2300 constants.LD_MD_R1: MDRaid1,
2301 constants.LD_DRBD7: DRBDev,
2302 constants.LD_DRBD8: DRBD8,
2303 constants.LD_FILE: FileStorage,
2307 def FindDevice(dev_type, unique_id, children):
2308 """Search for an existing, assembled device.
2310 This will succeed only if the device exists and is assembled, but it
2311 does not do any actions in order to activate the device.
2314 if dev_type not in DEV_MAP:
2315 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2316 device = DEV_MAP[dev_type](unique_id, children)
2317 if not device.Attach():
2322 def AttachOrAssemble(dev_type, unique_id, children):
2323 """Try to attach or assemble an existing device.
2325 This will attach to an existing assembled device or will assemble
2326 the device, as needed, to bring it fully up.
2329 if dev_type not in DEV_MAP:
2330 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2331 device = DEV_MAP[dev_type](unique_id, children)
2332 if not device.Attach():
2334 if not device.Attach():
2335 raise errors.BlockDeviceError("Can't find a valid block device for"
2337 (dev_type, unique_id, children))
2341 def Create(dev_type, unique_id, children, size):
2345 if dev_type not in DEV_MAP:
2346 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2347 device = DEV_MAP[dev_type].Create(unique_id, children, size)