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("%s - %s" % (result.fail_reason,
313 return LogicalVolume(unique_id, children)
316 def GetPVInfo(vg_name):
317 """Get the free space info for PVs in a volume group.
320 vg_name: the volume group name
323 list of (free_space, name) with free_space in mebibytes
326 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
327 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
329 result = utils.RunCmd(command)
331 logger.Error("Can't get the PV information: %s - %s" %
332 (result.fail_reason, result.output))
335 for line in result.stdout.splitlines():
336 fields = line.strip().split(':')
338 logger.Error("Can't parse pvs output: line '%s'" % line)
340 # skip over pvs from another vg or ones which are not allocatable
341 if fields[1] != vg_name or fields[3][0] != 'a':
343 data.append((float(fields[2]), fields[0]))
348 """Remove this logical volume.
351 if not self.minor and not self.Attach():
352 # the LV does not exist
354 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
355 (self._vg_name, self._lv_name)])
357 logger.Error("Can't lvremove: %s - %s" %
358 (result.fail_reason, result.output))
360 return not result.failed
362 def Rename(self, new_id):
363 """Rename this logical volume.
366 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
367 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
368 new_vg, new_name = new_id
369 if new_vg != self._vg_name:
370 raise errors.ProgrammerError("Can't move a logical volume across"
371 " volume groups (from %s to to %s)" %
372 (self._vg_name, new_vg))
373 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
375 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
377 self._lv_name = new_name
378 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
381 """Attach to an existing LV.
383 This method will try to see if an existing and active LV exists
384 which matches our name. If so, its major/minor will be
388 result = utils.RunCmd(["lvdisplay", self.dev_path])
390 logger.Error("Can't find LV %s: %s, %s" %
391 (self.dev_path, result.fail_reason, result.output))
393 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
394 for line in result.stdout.splitlines():
395 match_result = match.match(line)
397 self.major = int(match_result.group(1))
398 self.minor = int(match_result.group(2))
403 """Assemble the device.
405 We alway run `lvchange -ay` on the LV to ensure it's active before
406 use, as there were cases when xenvg was not active after boot
407 (also possibly after disk issues).
410 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
412 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
413 return not result.failed
416 """Shutdown the device.
418 This is a no-op for the LV device type, as we don't deactivate the
424 def GetSyncStatus(self):
425 """Returns the sync status of the device.
427 If this device is a mirroring device, this function returns the
428 status of the mirror.
431 (sync_percent, estimated_time, is_degraded, ldisk)
433 For logical volumes, sync_percent and estimated_time are always
434 None (no recovery in progress, as we don't handle the mirrored LV
435 case). The is_degraded parameter is the inverse of the ldisk
438 For the ldisk parameter, we check if the logical volume has the
439 'virtual' type, which means it's not backed by existing storage
440 anymore (read from it return I/O error). This happens after a
441 physical disk failure and subsequent 'vgreduce --removemissing' on
445 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
447 logger.Error("Can't display lv: %s - %s" %
448 (result.fail_reason, result.output))
449 return None, None, True, True
450 out = result.stdout.strip()
451 # format: type/permissions/alloc/fixed_minor/state/open
453 logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
454 return None, None, True, True
455 ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
457 return None, None, ldisk, ldisk
459 def Open(self, force=False):
460 """Make the device ready for I/O.
462 This is a no-op for the LV device type.
468 """Notifies that the device will no longer be used for I/O.
470 This is a no-op for the LV device type.
475 def Snapshot(self, size):
476 """Create a snapshot copy of an lvm block device.
479 snap_name = self._lv_name + ".snap"
481 # remove existing snapshot if found
482 snap = LogicalVolume((self._vg_name, snap_name), None)
485 pvs_info = self.GetPVInfo(self._vg_name)
487 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
491 free_size, pv_name = pvs_info[0]
493 raise errors.BlockDeviceError("Not enough free space: required %s,"
494 " available %s" % (size, free_size))
496 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
497 "-n%s" % snap_name, self.dev_path])
499 raise errors.BlockDeviceError("command: %s error: %s - %s" %
500 (result.cmd, result.fail_reason,
505 def SetInfo(self, text):
506 """Update metadata with info text.
509 BlockDev.SetInfo(self, text)
511 # Replace invalid characters
512 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
513 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
515 # Only up to 128 characters are allowed
518 result = utils.RunCmd(["lvchange", "--addtag", text,
521 raise errors.BlockDeviceError("Command: %s error: %s - %s" %
522 (result.cmd, result.fail_reason,
526 class MDRaid1(BlockDev):
527 """raid1 device implemented via md.
530 def __init__(self, unique_id, children):
531 super(MDRaid1, self).__init__(unique_id, children)
536 """Find an array which matches our config and attach to it.
538 This tries to find a MD array which has the same UUID as our own.
541 minor = self._FindMDByUUID(self.unique_id)
542 if minor is not None:
543 self._SetFromMinor(minor)
548 return (minor is not None)
552 """Compute the list of in-use MD devices.
554 It doesn't matter if the used device have other raid level, just
555 that they are in use.
558 mdstat = open("/proc/mdstat", "r")
559 data = mdstat.readlines()
563 valid_line = re.compile("^md([0-9]+) : .*$")
565 match = valid_line.match(line)
567 md_no = int(match.group(1))
568 used_md[md_no] = line
573 def _GetDevInfo(minor):
574 """Get info about a MD device.
576 Currently only uuid is returned.
579 result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
581 logger.Error("Can't display md: %s - %s" %
582 (result.fail_reason, result.output))
585 for line in result.stdout.splitlines():
587 kv = line.split(" : ", 1)
590 retval["uuid"] = kv[1].split()[0]
591 elif kv[0] == "State":
592 retval["state"] = kv[1].split(", ")
596 def _FindUnusedMinor():
597 """Compute an unused MD minor.
599 This code assumes that there are 256 minors only.
602 used_md = MDRaid1._GetUsedDevs()
609 logger.Error("Critical: Out of md minor numbers.")
610 raise errors.BlockDeviceError("Can't find a free MD minor")
614 def _FindMDByUUID(cls, uuid):
615 """Find the minor of an MD array with a given UUID.
618 md_list = cls._GetUsedDevs()
619 for minor in md_list:
620 info = cls._GetDevInfo(minor)
621 if info and info["uuid"] == uuid:
626 def _ZeroSuperblock(dev_path):
627 """Zero the possible locations for an MD superblock.
629 The zero-ing can't be done via ``mdadm --zero-superblock`` as that
630 fails in versions 2.x with the same error code as non-writable
633 The superblocks are located at (negative values are relative to
634 the end of the block device):
635 - -128k to end for version 0.90 superblock
636 - -8k to -12k for version 1.0 superblock (included in the above)
637 - 0k to 4k for version 1.1 superblock
638 - 4k to 8k for version 1.2 superblock
640 To cover all situations, the zero-ing will be:
644 As such, the minimum device size must be 128k, otherwise we'll get
647 Note that this function depends on the fact that one can open,
648 read and write block devices normally.
651 overwrite_size = 128 * 1024
652 empty_buf = '\0' * overwrite_size
653 fd = open(dev_path, "r+")
659 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
660 fd.seek(-overwrite_size, 2)
664 logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
669 def Create(cls, unique_id, children, size):
670 """Create a new MD raid1 array.
673 if not isinstance(children, (tuple, list)):
674 raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
677 if not isinstance(i, BlockDev):
678 raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
681 cls._ZeroSuperblock(i.dev_path)
682 except EnvironmentError, err:
683 logger.Error("Can't zero superblock for %s: %s" %
684 (i.dev_path, str(err)))
686 minor = cls._FindUnusedMinor()
687 result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
688 "--auto=yes", "--force", "-l1",
689 "-n%d" % len(children)] +
690 [dev.dev_path for dev in children])
693 logger.Error("Can't create md: %s: %s" % (result.fail_reason,
696 info = cls._GetDevInfo(minor)
697 if not info or not "uuid" in info:
698 logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
700 return MDRaid1(info["uuid"], children)
703 """Stub remove function for MD RAID 1 arrays.
705 We don't remove the superblock right now. Mark a to do.
708 #TODO: maybe zero superblock on child devices?
709 return self.Shutdown()
711 def Rename(self, new_id):
714 This is not supported for md raid1 devices.
717 raise errors.ProgrammerError("Can't rename a md raid1 device")
719 def AddChildren(self, devices):
720 """Add new member(s) to the md raid1.
723 if self.minor is None and not self.Attach():
724 raise errors.BlockDeviceError("Can't attach to device")
726 args = ["mdadm", "-a", self.dev_path]
728 if dev.dev_path is None:
729 raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
731 args.append(dev.dev_path)
732 result = utils.RunCmd(args)
734 raise errors.BlockDeviceError("Failed to add new device to array: %s" %
736 new_len = len(self._children) + len(devices)
737 result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
739 raise errors.BlockDeviceError("Can't grow md array: %s" %
741 self._children.extend(devices)
743 def RemoveChildren(self, devices):
744 """Remove member(s) from the md raid1.
747 if self.minor is None and not self.Attach():
748 raise errors.BlockDeviceError("Can't attach to device")
749 new_len = len(self._children) - len(devices)
751 raise errors.BlockDeviceError("Can't reduce to less than one child")
752 args = ["mdadm", "-f", self.dev_path]
756 for c in self._children:
757 if c.dev_path == dev:
761 raise errors.BlockDeviceError("Can't find device '%s' for removal" %
763 result = utils.RunCmd(args)
765 raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
768 # it seems here we need a short delay for MD to update its
772 result = utils.RunCmd(args)
774 raise errors.BlockDeviceError("Failed to remove device(s) from array:"
775 " %s" % result.output)
776 result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
779 raise errors.BlockDeviceError("Can't shrink md array: %s" %
781 for dev in orig_devs:
782 self._children.remove(dev)
784 def _SetFromMinor(self, minor):
785 """Set our parameters based on the given minor.
787 This sets our minor variable and our dev_path.
791 self.dev_path = "/dev/md%d" % minor
794 """Assemble the MD device.
796 At this point we should have:
797 - list of children devices
801 result = super(MDRaid1, self).Assemble()
804 md_list = self._GetUsedDevs()
805 for minor in md_list:
806 info = self._GetDevInfo(minor)
807 if info and info["uuid"] == self.unique_id:
808 self._SetFromMinor(minor)
809 logger.Info("MD array %s already started" % str(self))
811 free_minor = self._FindUnusedMinor()
812 result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
813 self.unique_id, "/dev/md%d" % free_minor] +
814 [bdev.dev_path for bdev in self._children])
816 logger.Error("Can't assemble MD array: %s: %s" %
817 (result.fail_reason, result.output))
820 self.minor = free_minor
821 return not result.failed
824 """Tear down the MD array.
826 This does a 'mdadm --stop' so after this command, the array is no
830 if self.minor is None and not self.Attach():
831 logger.Info("MD object not attached to a device")
834 result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
836 logger.Error("Can't stop MD array: %s - %s" %
837 (result.fail_reason, result.output))
843 def SetSyncSpeed(self, kbytes):
844 """Set the maximum sync speed for the MD array.
847 result = super(MDRaid1, self).SetSyncSpeed(kbytes)
848 if self.minor is None:
849 logger.Error("MD array not attached to a device")
851 f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
853 f.write("%d" % kbytes)
856 f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
858 f.write("%d" % (kbytes/2))
863 def GetSyncStatus(self):
864 """Returns the sync status of the device.
867 (sync_percent, estimated_time, is_degraded, ldisk)
869 If sync_percent is None, it means all is ok
870 If estimated_time is None, it means we can't esimate
871 the time needed, otherwise it's the time left in seconds.
873 The ldisk parameter is always true for MD devices.
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, False
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, False
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, False
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+)(?:-(\d+))?\)")
933 _ST_UNCONFIGURED = "Unconfigured"
934 _ST_WFCONNECTION = "WFConnection"
935 _ST_CONNECTED = "Connected"
939 """Return data from /proc/drbd.
942 stat = open("/proc/drbd", "r")
944 data = stat.read().splitlines()
948 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
952 def _MassageProcData(data):
953 """Transform the output of _GetProdData into a nicer form.
956 a dictionary of minor: joined lines from /proc/drbd for that minor
959 lmatch = re.compile("^ *([0-9]+):.*$")
961 old_minor = old_line = None
963 lresult = lmatch.match(line)
964 if lresult is not None:
965 if old_minor is not None:
966 results[old_minor] = old_line
967 old_minor = int(lresult.group(1))
970 if old_minor is not None:
971 old_line += " " + line.strip()
973 if old_minor is not None:
974 results[old_minor] = old_line
978 def _GetVersion(cls):
979 """Return the DRBD version.
981 This will return a dict with keys:
987 proto2 (only on drbd > 8.2.X)
990 proc_data = cls._GetProcData()
991 first_line = proc_data[0].strip()
992 version = cls._VERSION_RE.match(first_line)
994 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
997 values = version.groups()
998 retval = {'k_major': int(values[0]),
999 'k_minor': int(values[1]),
1000 'k_point': int(values[2]),
1001 'api': int(values[3]),
1002 'proto': int(values[4]),
1004 if values[5] is not None:
1005 retval['proto2'] = values[5]
1010 def _DevPath(minor):
1011 """Return the path to a drbd device for a given minor.
1014 return "/dev/drbd%d" % minor
1017 def _GetUsedDevs(cls):
1018 """Compute the list of used DRBD devices.
1021 data = cls._GetProcData()
1024 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1026 match = valid_line.match(line)
1029 minor = int(match.group(1))
1030 state = match.group(2)
1031 if state == cls._ST_UNCONFIGURED:
1033 used_devs[minor] = state, line
1037 def _SetFromMinor(self, minor):
1038 """Set our parameters based on the given minor.
1040 This sets our minor variable and our dev_path.
1044 self.minor = self.dev_path = None
1047 self.dev_path = self._DevPath(minor)
1050 def _CheckMetaSize(meta_device):
1051 """Check if the given meta device looks like a valid one.
1053 This currently only check the size, which must be around
1057 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1059 logger.Error("Failed to get device size: %s - %s" %
1060 (result.fail_reason, result.output))
1063 sectors = int(result.stdout)
1065 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1067 bytes = sectors * 512
1068 if bytes < 128 * 1024 * 1024: # less than 128MiB
1069 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1071 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1072 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1076 def Rename(self, new_id):
1079 This is not supported for drbd devices.
1082 raise errors.ProgrammerError("Can't rename a drbd device")
1085 class DRBDev(BaseDRBD):
1086 """DRBD block device.
1088 This implements the local host part of the DRBD device, i.e. it
1089 doesn't do anything to the supposed peer. If you need a fully
1090 connected DRBD pair, you need to use this class on both hosts.
1092 The unique_id for the drbd device is the (local_ip, local_port,
1093 remote_ip, remote_port) tuple, and it must have two children: the
1094 data device and the meta_device. The meta device is checked for
1095 valid size and is zeroed on create.
1098 def __init__(self, unique_id, children):
1099 super(DRBDev, self).__init__(unique_id, children)
1100 self.major = self._DRBD_MAJOR
1101 version = self._GetVersion()
1102 if version['k_major'] != 0 and version['k_minor'] != 7:
1103 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1104 " requested ganeti usage: kernel is"
1105 " %s.%s, ganeti wants 0.7" %
1106 (version['k_major'], version['k_minor']))
1107 if len(children) != 2:
1108 raise ValueError("Invalid configuration data %s" % str(children))
1109 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1110 raise ValueError("Invalid configuration data %s" % str(unique_id))
1111 self._lhost, self._lport, self._rhost, self._rport = unique_id
1115 def _FindUnusedMinor(cls):
1116 """Find an unused DRBD device.
1119 data = cls._GetProcData()
1121 valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1123 match = valid_line.match(line)
1125 return int(match.group(1))
1126 logger.Error("Error: no free drbd minors!")
1127 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1130 def _GetDevInfo(cls, minor):
1131 """Get details about a given DRBD minor.
1133 This return, if available, the local backing device in (major,
1134 minor) formant and the local and remote (ip, port) information.
1138 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1140 logger.Error("Can't display the drbd config: %s - %s" %
1141 (result.fail_reason, result.output))
1144 if out == "Not configured\n":
1146 for line in out.splitlines():
1147 if "local_dev" not in data:
1148 match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1150 data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1152 if "meta_dev" not in data:
1153 match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1155 if match.group(2) is not None and match.group(3) is not None:
1156 # matched on the major/minor
1157 data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1159 # matched on the "internal" string
1160 data["meta_dev"] = match.group(1)
1161 # in this case, no meta_index is in the output
1162 data["meta_index"] = -1
1164 if "meta_index" not in data:
1165 match = re.match("^Meta index: ([0-9]+).*$", line)
1167 data["meta_index"] = int(match.group(1))
1169 if "local_addr" not in data:
1170 match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1172 data["local_addr"] = (match.group(1), int(match.group(2)))
1174 if "remote_addr" not in data:
1175 match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1177 data["remote_addr"] = (match.group(1), int(match.group(2)))
1181 def _MatchesLocal(self, info):
1182 """Test if our local config matches with an existing device.
1184 The parameter should be as returned from `_GetDevInfo()`. This
1185 method tests if our local backing device is the same as the one in
1186 the info parameter, in effect testing if we look like the given
1190 if not ("local_dev" in info and "meta_dev" in info and
1191 "meta_index" in info):
1194 backend = self._children[0]
1195 if backend is not None:
1196 retval = (info["local_dev"] == (backend.major, backend.minor))
1198 retval = (info["local_dev"] == (0, 0))
1199 meta = self._children[1]
1200 if meta is not None:
1201 retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1202 retval = retval and (info["meta_index"] == 0)
1204 retval = retval and (info["meta_dev"] == "internal" and
1205 info["meta_index"] == -1)
1208 def _MatchesNet(self, info):
1209 """Test if our network config matches with an existing device.
1211 The parameter should be as returned from `_GetDevInfo()`. This
1212 method tests if our network configuration is the same as the one
1213 in the info parameter, in effect testing if we look like the given
1217 if (((self._lhost is None and not ("local_addr" in info)) and
1218 (self._rhost is None and not ("remote_addr" in info)))):
1221 if self._lhost is None:
1224 if not ("local_addr" in info and
1225 "remote_addr" in info):
1228 retval = (info["local_addr"] == (self._lhost, self._lport))
1229 retval = (retval and
1230 info["remote_addr"] == (self._rhost, self._rport))
1234 def _AssembleLocal(cls, minor, backend, meta):
1235 """Configure the local part of a DRBD device.
1237 This is the first thing that must be done on an unconfigured DRBD
1238 device. And it must be done only once.
1241 if not cls._CheckMetaSize(meta):
1243 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1244 backend, meta, "0", "-e", "detach"])
1246 logger.Error("Can't attach local disk: %s" % result.output)
1247 return not result.failed
1250 def _ShutdownLocal(cls, minor):
1251 """Detach from the local device.
1253 I/Os will continue to be served from the remote device. If we
1254 don't have a remote device, this operation will fail.
1257 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1259 logger.Error("Can't detach local device: %s" % result.output)
1260 return not result.failed
1263 def _ShutdownAll(minor):
1264 """Deactivate the device.
1266 This will, of course, fail if the device is in use.
1269 result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1271 logger.Error("Can't shutdown drbd device: %s" % result.output)
1272 return not result.failed
1275 def _AssembleNet(cls, minor, net_info, protocol):
1276 """Configure the network part of the device.
1278 This operation can be, in theory, done multiple times, but there
1279 have been cases (in lab testing) in which the network part of the
1280 device had become stuck and couldn't be shut down because activity
1281 from the new peer (also stuck) triggered a timer re-init and
1282 needed remote peer interface shutdown in order to clear. So please
1283 don't change online the net config.
1286 lhost, lport, rhost, rport = net_info
1287 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1288 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1291 logger.Error("Can't setup network for dbrd device: %s - %s" %
1292 (result.fail_reason, result.output))
1295 timeout = time.time() + 10
1297 while time.time() < timeout:
1298 info = cls._GetDevInfo(minor)
1299 if not "local_addr" in info or not "remote_addr" in info:
1302 if (info["local_addr"] != (lhost, lport) or
1303 info["remote_addr"] != (rhost, rport)):
1309 logger.Error("Timeout while configuring network")
1314 def _ShutdownNet(cls, minor):
1315 """Disconnect from the remote peer.
1317 This fails if we don't have a local device.
1320 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1322 logger.Error("Can't shutdown network: %s" % result.output)
1323 return not result.failed
1326 """Assemble the drbd.
1329 - if we have a local backing device, we bind to it by:
1330 - checking the list of used drbd devices
1331 - check if the local minor use of any of them is our own device
1334 - if we have a local/remote net info:
1335 - redo the local backing device step for the remote device
1336 - check if any drbd device is using the local port,
1338 - check if any remote drbd device is using the remote
1339 port, if yes abort (for now)
1341 - bind the remote net port
1345 if self.minor is not None:
1346 logger.Info("Already assembled")
1349 result = super(DRBDev, self).Assemble()
1353 minor = self._FindUnusedMinor()
1354 need_localdev_teardown = False
1355 if self._children[0]:
1356 result = self._AssembleLocal(minor, self._children[0].dev_path,
1357 self._children[1].dev_path)
1360 need_localdev_teardown = True
1361 if self._lhost and self._lport and self._rhost and self._rport:
1362 result = self._AssembleNet(minor,
1363 (self._lhost, self._lport,
1364 self._rhost, self._rport),
1367 if need_localdev_teardown:
1368 # we will ignore failures from this
1369 logger.Error("net setup failed, tearing down local device")
1370 self._ShutdownAll(minor)
1372 self._SetFromMinor(minor)
1376 """Shutdown the DRBD device.
1379 if self.minor is None and not self.Attach():
1380 logger.Info("DRBD device not attached to a device during Shutdown")
1382 if not self._ShutdownAll(self.minor):
1385 self.dev_path = None
1389 """Find a DRBD device which matches our config and attach to it.
1391 In case of partially attached (local device matches but no network
1392 setup), we perform the network attach. If successful, we re-test
1393 the attach if can return success.
1396 for minor in self._GetUsedDevs():
1397 info = self._GetDevInfo(minor)
1398 match_l = self._MatchesLocal(info)
1399 match_r = self._MatchesNet(info)
1400 if match_l and match_r:
1402 if match_l and not match_r and "local_addr" not in info:
1403 res_r = self._AssembleNet(minor,
1404 (self._lhost, self._lport,
1405 self._rhost, self._rport),
1407 if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1412 self._SetFromMinor(minor)
1413 return minor is not None
1415 def Open(self, force=False):
1416 """Make the local state primary.
1418 If the 'force' parameter is given, the '--do-what-I-say' parameter
1419 is given. Since this is a pottentialy dangerous operation, the
1420 force flag should be only given after creation, when it actually
1424 if self.minor is None and not self.Attach():
1425 logger.Error("DRBD cannot attach to a device during open")
1427 cmd = ["drbdsetup", self.dev_path, "primary"]
1429 cmd.append("--do-what-I-say")
1430 result = utils.RunCmd(cmd)
1432 msg = ("Can't make drbd device primary: %s" % result.output)
1434 raise errors.BlockDeviceError(msg)
1437 """Make the local state secondary.
1439 This will, of course, fail if the device is in use.
1442 if self.minor is None and not self.Attach():
1443 logger.Info("Instance not attached to a device")
1444 raise errors.BlockDeviceError("Can't find device")
1445 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1447 msg = ("Can't switch drbd device to"
1448 " secondary: %s" % result.output)
1450 raise errors.BlockDeviceError(msg)
1452 def SetSyncSpeed(self, kbytes):
1453 """Set the speed of the DRBD syncer.
1456 children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1457 if self.minor is None:
1458 logger.Info("Instance not attached to a device")
1460 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1463 logger.Error("Can't change syncer rate: %s - %s" %
1464 (result.fail_reason, result.output))
1465 return not result.failed and children_result
1467 def GetSyncStatus(self):
1468 """Returns the sync status of the device.
1471 (sync_percent, estimated_time, is_degraded, ldisk)
1473 If sync_percent is None, it means all is ok
1474 If estimated_time is None, it means we can't esimate
1475 the time needed, otherwise it's the time left in seconds.
1477 The ldisk parameter will be returned as True, since the DRBD7
1478 devices have not been converted.
1481 if self.minor is None and not self.Attach():
1482 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1483 proc_info = self._MassageProcData(self._GetProcData())
1484 if self.minor not in proc_info:
1485 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1487 line = proc_info[self.minor]
1488 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1489 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1491 sync_percent = float(match.group(1))
1492 hours = int(match.group(2))
1493 minutes = int(match.group(3))
1494 seconds = int(match.group(4))
1495 est_time = hours * 3600 + minutes * 60 + seconds
1499 match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1501 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1503 client_state = match.group(1)
1504 is_degraded = client_state != "Connected"
1505 return sync_percent, est_time, is_degraded, False
1508 def _ZeroDevice(device):
1511 This writes until we get ENOSPC.
1514 f = open(device, "w")
1515 buf = "\0" * 1048576
1519 except IOError, err:
1520 if err.errno != errno.ENOSPC:
1524 def Create(cls, unique_id, children, size):
1525 """Create a new DRBD device.
1527 Since DRBD devices are not created per se, just assembled, this
1528 function just zeroes the meta device.
1531 if len(children) != 2:
1532 raise errors.ProgrammerError("Invalid setup for the drbd device")
1535 if not meta.Attach():
1536 raise errors.BlockDeviceError("Can't attach to meta device")
1537 if not cls._CheckMetaSize(meta.dev_path):
1538 raise errors.BlockDeviceError("Invalid meta device")
1539 logger.Info("Started zeroing device %s" % meta.dev_path)
1540 cls._ZeroDevice(meta.dev_path)
1541 logger.Info("Done zeroing device %s" % meta.dev_path)
1542 return cls(unique_id, children)
1545 """Stub remove for DRBD devices.
1548 return self.Shutdown()
1551 class DRBD8(BaseDRBD):
1552 """DRBD v8.x block device.
1554 This implements the local host part of the DRBD device, i.e. it
1555 doesn't do anything to the supposed peer. If you need a fully
1556 connected DRBD pair, you need to use this class on both hosts.
1558 The unique_id for the drbd device is the (local_ip, local_port,
1559 remote_ip, remote_port) tuple, and it must have two children: the
1560 data device and the meta_device. The meta device is checked for
1561 valid size and is zeroed on create.
1567 def __init__(self, unique_id, children):
1568 if children and children.count(None) > 0:
1570 super(DRBD8, self).__init__(unique_id, children)
1571 self.major = self._DRBD_MAJOR
1572 version = self._GetVersion()
1573 if version['k_major'] != 8 :
1574 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1575 " requested ganeti usage: kernel is"
1576 " %s.%s, ganeti wants 8.x" %
1577 (version['k_major'], version['k_minor']))
1579 if len(children) not in (0, 2):
1580 raise ValueError("Invalid configuration data %s" % str(children))
1581 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1582 raise ValueError("Invalid configuration data %s" % str(unique_id))
1583 self._lhost, self._lport, self._rhost, self._rport = unique_id
1587 def _InitMeta(cls, minor, dev_path):
1588 """Initialize a meta device.
1590 This will not work if the given minor is in use.
1593 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1594 "v08", dev_path, "0", "create-md"])
1596 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1600 def _FindUnusedMinor(cls):
1601 """Find an unused DRBD device.
1603 This is specific to 8.x as the minors are allocated dynamically,
1604 so non-existing numbers up to a max minor count are actually free.
1607 data = cls._GetProcData()
1609 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1610 used_line = re.compile("^ *([0-9]+): cs:")
1613 match = unused_line.match(line)
1615 return int(match.group(1))
1616 match = used_line.match(line)
1618 minor = int(match.group(1))
1619 highest = max(highest, minor)
1620 if highest is None: # there are no minors in use at all
1622 if highest >= cls._MAX_MINORS:
1623 logger.Error("Error: no free drbd minors!")
1624 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1628 def _IsValidMeta(cls, meta_device):
1629 """Check if the given meta device looks like a valid one.
1632 minor = cls._FindUnusedMinor()
1633 minor_path = cls._DevPath(minor)
1634 result = utils.RunCmd(["drbdmeta", minor_path,
1635 "v08", meta_device, "0",
1638 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1643 def _GetShowParser(cls):
1644 """Return a parser for `drbd show` output.
1646 This will either create or return an already-create parser for the
1647 output of the command `drbd show`.
1650 if cls._PARSE_SHOW is not None:
1651 return cls._PARSE_SHOW
1654 lbrace = pyp.Literal("{").suppress()
1655 rbrace = pyp.Literal("}").suppress()
1656 semi = pyp.Literal(";").suppress()
1657 # this also converts the value to an int
1658 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1660 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1661 defa = pyp.Literal("_is_default").suppress()
1662 dbl_quote = pyp.Literal('"').suppress()
1664 keyword = pyp.Word(pyp.alphanums + '-')
1667 value = pyp.Word(pyp.alphanums + '_-/.:')
1668 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1669 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1671 # meta device, extended syntax
1672 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1673 number + pyp.Word(']').suppress())
1676 stmt = (~rbrace + keyword + ~lbrace +
1677 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
1678 pyp.Optional(defa) + semi +
1679 pyp.Optional(pyp.restOfLine).suppress())
1682 section_name = pyp.Word(pyp.alphas + '_')
1683 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1685 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1688 cls._PARSE_SHOW = bnf
1693 def _GetShowData(cls, minor):
1694 """Return the `drbdsetup show` data for a minor.
1697 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1699 logger.Error("Can't display the drbd config: %s - %s" %
1700 (result.fail_reason, result.output))
1702 return result.stdout
1705 def _GetDevInfo(cls, out):
1706 """Parse details about a given DRBD minor.
1708 This return, if available, the local backing device (as a path)
1709 and the local and remote (ip, port) information from a string
1710 containing the output of the `drbdsetup show` command as returned
1718 bnf = cls._GetShowParser()
1722 results = bnf.parseString(out)
1723 except pyp.ParseException, err:
1724 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1727 # and massage the results into our desired format
1728 for section in results:
1730 if sname == "_this_host":
1731 for lst in section[1:]:
1732 if lst[0] == "disk":
1733 data["local_dev"] = lst[1]
1734 elif lst[0] == "meta-disk":
1735 data["meta_dev"] = lst[1]
1736 data["meta_index"] = lst[2]
1737 elif lst[0] == "address":
1738 data["local_addr"] = tuple(lst[1:])
1739 elif sname == "_remote_host":
1740 for lst in section[1:]:
1741 if lst[0] == "address":
1742 data["remote_addr"] = tuple(lst[1:])
1745 def _MatchesLocal(self, info):
1746 """Test if our local config matches with an existing device.
1748 The parameter should be as returned from `_GetDevInfo()`. This
1749 method tests if our local backing device is the same as the one in
1750 the info parameter, in effect testing if we look like the given
1755 backend, meta = self._children
1757 backend = meta = None
1759 if backend is not None:
1760 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1762 retval = ("local_dev" not in info)
1764 if meta is not None:
1765 retval = retval and ("meta_dev" in info and
1766 info["meta_dev"] == meta.dev_path)
1767 retval = retval and ("meta_index" in info and
1768 info["meta_index"] == 0)
1770 retval = retval and ("meta_dev" not in info and
1771 "meta_index" not in info)
1774 def _MatchesNet(self, info):
1775 """Test if our network config matches with an existing device.
1777 The parameter should be as returned from `_GetDevInfo()`. This
1778 method tests if our network configuration is the same as the one
1779 in the info parameter, in effect testing if we look like the given
1783 if (((self._lhost is None and not ("local_addr" in info)) and
1784 (self._rhost is None and not ("remote_addr" in info)))):
1787 if self._lhost is None:
1790 if not ("local_addr" in info and
1791 "remote_addr" in info):
1794 retval = (info["local_addr"] == (self._lhost, self._lport))
1795 retval = (retval and
1796 info["remote_addr"] == (self._rhost, self._rport))
1800 def _AssembleLocal(cls, minor, backend, meta):
1801 """Configure the local part of a DRBD device.
1803 This is the first thing that must be done on an unconfigured DRBD
1804 device. And it must be done only once.
1807 if not cls._IsValidMeta(meta):
1809 args = ["drbdsetup", cls._DevPath(minor), "disk",
1810 backend, meta, "0", "-e", "detach", "--create-device"]
1811 result = utils.RunCmd(args)
1813 logger.Error("Can't attach local disk: %s" % result.output)
1814 return not result.failed
1817 def _AssembleNet(cls, minor, net_info, protocol,
1818 dual_pri=False, hmac=None, secret=None):
1819 """Configure the network part of the device.
1822 lhost, lport, rhost, rport = net_info
1823 if None in net_info:
1824 # we don't want network connection and actually want to make
1826 return cls._ShutdownNet(minor)
1828 args = ["drbdsetup", cls._DevPath(minor), "net",
1829 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1830 "-A", "discard-zero-changes",
1837 args.extend(["-a", hmac, "-x", secret])
1838 result = utils.RunCmd(args)
1840 logger.Error("Can't setup network for dbrd device: %s - %s" %
1841 (result.fail_reason, result.output))
1844 timeout = time.time() + 10
1846 while time.time() < timeout:
1847 info = cls._GetDevInfo(cls._GetShowData(minor))
1848 if not "local_addr" in info or not "remote_addr" in info:
1851 if (info["local_addr"] != (lhost, lport) or
1852 info["remote_addr"] != (rhost, rport)):
1858 logger.Error("Timeout while configuring network")
1862 def AddChildren(self, devices):
1863 """Add a disk to the DRBD device.
1866 if self.minor is None:
1867 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1868 if len(devices) != 2:
1869 raise errors.BlockDeviceError("Need two devices for AddChildren")
1870 info = self._GetDevInfo(self._GetShowData(self.minor))
1871 if "local_dev" in info:
1872 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1873 backend, meta = devices
1874 if backend.dev_path is None or meta.dev_path is None:
1875 raise errors.BlockDeviceError("Children not ready during AddChildren")
1878 if not self._CheckMetaSize(meta.dev_path):
1879 raise errors.BlockDeviceError("Invalid meta device size")
1880 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1881 if not self._IsValidMeta(meta.dev_path):
1882 raise errors.BlockDeviceError("Cannot initalize meta device")
1884 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1885 raise errors.BlockDeviceError("Can't attach to local storage")
1886 self._children = devices
1888 def RemoveChildren(self, devices):
1889 """Detach the drbd device from local storage.
1892 if self.minor is None:
1893 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1895 # early return if we don't actually have backing storage
1896 info = self._GetDevInfo(self._GetShowData(self.minor))
1897 if "local_dev" not in info:
1899 if len(self._children) != 2:
1900 raise errors.BlockDeviceError("We don't have two children: %s" %
1902 if self._children.count(None) == 2: # we don't actually have children :)
1903 logger.Error("Requested detach while detached")
1905 if len(devices) != 2:
1906 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1907 for child, dev in zip(self._children, devices):
1908 if dev != child.dev_path:
1909 raise errors.BlockDeviceError("Mismatch in local storage"
1910 " (%s != %s) in RemoveChildren" %
1911 (dev, child.dev_path))
1913 if not self._ShutdownLocal(self.minor):
1914 raise errors.BlockDeviceError("Can't detach from local storage")
1917 def SetSyncSpeed(self, kbytes):
1918 """Set the speed of the DRBD syncer.
1921 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1922 if self.minor is None:
1923 logger.Info("Instance not attached to a device")
1925 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1928 logger.Error("Can't change syncer rate: %s - %s" %
1929 (result.fail_reason, result.output))
1930 return not result.failed and children_result
1932 def GetSyncStatus(self):
1933 """Returns the sync status of the device.
1936 (sync_percent, estimated_time, is_degraded)
1938 If sync_percent is None, it means all is ok
1939 If estimated_time is None, it means we can't esimate
1940 the time needed, otherwise it's the time left in seconds.
1943 We set the is_degraded parameter to True on two conditions:
1944 network not connected or local disk missing.
1946 We compute the ldisk parameter based on wheter we have a local
1950 if self.minor is None and not self.Attach():
1951 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1952 proc_info = self._MassageProcData(self._GetProcData())
1953 if self.minor not in proc_info:
1954 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1956 line = proc_info[self.minor]
1957 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1958 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1960 sync_percent = float(match.group(1))
1961 hours = int(match.group(2))
1962 minutes = int(match.group(3))
1963 seconds = int(match.group(4))
1964 est_time = hours * 3600 + minutes * 60 + seconds
1968 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1970 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1972 client_state = match.group(1)
1973 local_disk_state = match.group(2)
1974 ldisk = local_disk_state != "UpToDate"
1975 is_degraded = client_state != "Connected"
1976 return sync_percent, est_time, is_degraded or ldisk, ldisk
1978 def Open(self, force=False):
1979 """Make the local state primary.
1981 If the 'force' parameter is given, the '-o' option is passed to
1982 drbdsetup. Since this is a potentially dangerous operation, the
1983 force flag should be only given after creation, when it actually
1987 if self.minor is None and not self.Attach():
1988 logger.Error("DRBD cannot attach to a device during open")
1990 cmd = ["drbdsetup", self.dev_path, "primary"]
1993 result = utils.RunCmd(cmd)
1995 msg = ("Can't make drbd device primary: %s" % result.output)
1997 raise errors.BlockDeviceError(msg)
2000 """Make the local state secondary.
2002 This will, of course, fail if the device is in use.
2005 if self.minor is None and not self.Attach():
2006 logger.Info("Instance not attached to a device")
2007 raise errors.BlockDeviceError("Can't find device")
2008 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2010 msg = ("Can't switch drbd device to"
2011 " secondary: %s" % result.output)
2013 raise errors.BlockDeviceError(msg)
2016 """Find a DRBD device which matches our config and attach to it.
2018 In case of partially attached (local device matches but no network
2019 setup), we perform the network attach. If successful, we re-test
2020 the attach if can return success.
2023 for minor in self._GetUsedDevs():
2024 info = self._GetDevInfo(self._GetShowData(minor))
2025 match_l = self._MatchesLocal(info)
2026 match_r = self._MatchesNet(info)
2027 if match_l and match_r:
2029 if match_l and not match_r and "local_addr" not in info:
2030 res_r = self._AssembleNet(minor,
2031 (self._lhost, self._lport,
2032 self._rhost, self._rport),
2035 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2037 # the weakest case: we find something that is only net attached
2038 # even though we were passed some children at init time
2039 if match_r and "local_dev" not in info:
2042 # this case must be considered only if we actually have local
2043 # storage, i.e. not in diskless mode, because all diskless
2044 # devices are equal from the point of view of local
2046 if (match_l and "local_dev" in info and
2047 not match_r and "local_addr" in info):
2048 # strange case - the device network part points to somewhere
2049 # else, even though its local storage is ours; as we own the
2050 # drbd space, we try to disconnect from the remote peer and
2051 # reconnect to our correct one
2052 if not self._ShutdownNet(minor):
2053 raise errors.BlockDeviceError("Device has correct local storage,"
2054 " wrong remote peer and is unable to"
2055 " disconnect in order to attach to"
2056 " the correct peer")
2057 # note: _AssembleNet also handles the case when we don't want
2058 # local storage (i.e. one or more of the _[lr](host|port) is
2060 if (self._AssembleNet(minor, (self._lhost, self._lport,
2061 self._rhost, self._rport), "C") and
2062 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2068 self._SetFromMinor(minor)
2069 return minor is not None
2072 """Assemble the drbd.
2075 - if we have a local backing device, we bind to it by:
2076 - checking the list of used drbd devices
2077 - check if the local minor use of any of them is our own device
2080 - if we have a local/remote net info:
2081 - redo the local backing device step for the remote device
2082 - check if any drbd device is using the local port,
2084 - check if any remote drbd device is using the remote
2085 port, if yes abort (for now)
2087 - bind the remote net port
2091 if self.minor is not None:
2092 logger.Info("Already assembled")
2095 result = super(DRBD8, self).Assemble()
2099 minor = self._FindUnusedMinor()
2100 need_localdev_teardown = False
2101 if self._children and self._children[0] and self._children[1]:
2102 result = self._AssembleLocal(minor, self._children[0].dev_path,
2103 self._children[1].dev_path)
2106 need_localdev_teardown = True
2107 if self._lhost and self._lport and self._rhost and self._rport:
2108 result = self._AssembleNet(minor,
2109 (self._lhost, self._lport,
2110 self._rhost, self._rport),
2113 if need_localdev_teardown:
2114 # we will ignore failures from this
2115 logger.Error("net setup failed, tearing down local device")
2116 self._ShutdownAll(minor)
2118 self._SetFromMinor(minor)
2122 def _ShutdownLocal(cls, minor):
2123 """Detach from the local device.
2125 I/Os will continue to be served from the remote device. If we
2126 don't have a remote device, this operation will fail.
2129 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2131 logger.Error("Can't detach local device: %s" % result.output)
2132 return not result.failed
2135 def _ShutdownNet(cls, minor):
2136 """Disconnect from the remote peer.
2138 This fails if we don't have a local device.
2141 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2143 logger.Error("Can't shutdown network: %s" % result.output)
2144 return not result.failed
2147 def _ShutdownAll(cls, minor):
2148 """Deactivate the device.
2150 This will, of course, fail if the device is in use.
2153 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2155 logger.Error("Can't shutdown drbd device: %s" % result.output)
2156 return not result.failed
2159 """Shutdown the DRBD device.
2162 if self.minor is None and not self.Attach():
2163 logger.Info("DRBD device not attached to a device during Shutdown")
2165 if not self._ShutdownAll(self.minor):
2168 self.dev_path = None
2172 """Stub remove for DRBD devices.
2175 return self.Shutdown()
2178 def Create(cls, unique_id, children, size):
2179 """Create a new DRBD8 device.
2181 Since DRBD devices are not created per se, just assembled, this
2182 function only initializes the metadata.
2185 if len(children) != 2:
2186 raise errors.ProgrammerError("Invalid setup for the drbd device")
2189 if not meta.Attach():
2190 raise errors.BlockDeviceError("Can't attach to meta device")
2191 if not cls._CheckMetaSize(meta.dev_path):
2192 raise errors.BlockDeviceError("Invalid meta device size")
2193 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2194 if not cls._IsValidMeta(meta.dev_path):
2195 raise errors.BlockDeviceError("Cannot initalize meta device")
2196 return cls(unique_id, children)
2199 class FileStorage(BlockDev):
2202 This class represents the a file storage backend device.
2204 The unique_id for the file device is a (file_driver, file_path) tuple.
2207 def __init__(self, unique_id, children):
2208 """Initalizes a file device backend.
2212 raise errors.BlockDeviceError("Invalid setup for file device")
2213 super(FileStorage, self).__init__(unique_id, children)
2214 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2215 raise ValueError("Invalid configuration data %s" % str(unique_id))
2216 self.driver = unique_id[0]
2217 self.dev_path = unique_id[1]
2220 """Assemble the device.
2222 Checks whether the file device exists, raises BlockDeviceError otherwise.
2225 if not os.path.exists(self.dev_path):
2226 raise errors.BlockDeviceError("File device '%s' does not exist." %
2231 """Shutdown the device.
2233 This is a no-op for the file type, as we don't deacivate
2234 the file on shutdown.
2239 def Open(self, force=False):
2240 """Make the device ready for I/O.
2242 This is a no-op for the file type.
2248 """Notifies that the device will no longer be used for I/O.
2250 This is a no-op for the file type.
2256 """Remove the file backing the block device.
2259 boolean indicating wheter removal of file was successful or not.
2262 if not os.path.exists(self.dev_path):
2265 os.remove(self.dev_path)
2267 except OSError, err:
2268 logger.Error("Can't remove file '%s': %s"
2269 % (self.dev_path, err))
2273 """Attach to an existing file.
2275 Check if this file already exists.
2278 boolean indicating if file exists or not.
2281 if os.path.exists(self.dev_path):
2286 def Create(cls, unique_id, children, size):
2287 """Create a new file.
2291 size: integer size of file in MiB
2294 A ganeti.bdev.FileStorage object.
2297 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2298 raise ValueError("Invalid configuration data %s" % str(unique_id))
2299 dev_path = unique_id[1]
2301 f = open(dev_path, 'w')
2302 except IOError, err:
2303 raise errors.BlockDeviceError("Could not create '%'" % err)
2305 f.truncate(size * 1024 * 1024)
2308 return FileStorage(unique_id, children)
2312 constants.LD_LV: LogicalVolume,
2313 constants.LD_MD_R1: MDRaid1,
2314 constants.LD_DRBD7: DRBDev,
2315 constants.LD_DRBD8: DRBD8,
2316 constants.LD_FILE: FileStorage,
2320 def FindDevice(dev_type, unique_id, children):
2321 """Search for an existing, assembled device.
2323 This will succeed only if the device exists and is assembled, but it
2324 does not do any actions in order to activate the device.
2327 if dev_type not in DEV_MAP:
2328 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2329 device = DEV_MAP[dev_type](unique_id, children)
2330 if not device.Attach():
2335 def AttachOrAssemble(dev_type, unique_id, children):
2336 """Try to attach or assemble an existing device.
2338 This will attach to an existing assembled device or will assemble
2339 the device, as needed, to bring it fully up.
2342 if dev_type not in DEV_MAP:
2343 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2344 device = DEV_MAP[dev_type](unique_id, children)
2345 if not device.Attach():
2347 if not device.Attach():
2348 raise errors.BlockDeviceError("Can't find a valid block device for"
2350 (dev_type, unique_id, children))
2354 def Create(dev_type, unique_id, children, size):
2358 if dev_type not in DEV_MAP:
2359 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2360 device = DEV_MAP[dev_type].Create(unique_id, children, size)