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
31 from ganeti import utils
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 (LV has it, if needed in the future) and we are
47 usually looking at this like at a stack, so it's easier to
48 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 - drbd devices are attached to a local disk/remote peer and made primary
56 A block device is identified by three items:
57 - the /dev path of the device (dynamic)
58 - a unique ID of the device (static)
59 - it's major/minor pair (dynamic)
61 Not all devices implement both the first two as distinct items. LVM
62 logical volumes have their unique ID (the pair volume group, logical
63 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
64 the /dev path is again dynamic and the unique id is the pair (host1,
67 You can get to a device in two ways:
68 - creating the (real) device, which returns you
69 an attached instance (lvcreate)
70 - attaching of a python instance to an existing (real) device
72 The second point, the attachement to a device, is different
73 depending on whether the device is assembled or not. At init() time,
74 we search for a device with the same unique_id as us. If found,
75 good. It also means that the device is already assembled. If not,
76 after assembly we'll have our correct major/minor.
79 def __init__(self, unique_id, children):
80 self._children = children
82 self.unique_id = unique_id
88 """Assemble the device from its components.
90 If this is a plain block device (e.g. LVM) than assemble does
91 nothing, as the LVM has no children and we don't put logical
94 One guarantee is that after the device has been assembled, it
95 knows its major/minor numbers. This allows other devices (usually
96 parents) to probe correctly for their children.
100 for child in self._children:
101 if not isinstance(child, BlockDev):
102 raise TypeError("Invalid child passed of type '%s'" % type(child))
105 status = status and child.Assemble()
111 except errors.BlockDeviceError:
112 for child in self._children:
117 for child in self._children:
122 """Find a device which matches our config and attach to it.
125 raise NotImplementedError
128 """Notifies that the device will no longer be used for I/O.
131 raise NotImplementedError
134 def Create(cls, unique_id, children, size):
135 """Create the device.
137 If the device cannot be created, it will return None
138 instead. Error messages go to the logging system.
140 Note that for some devices, the unique_id is used, and for other,
141 the children. The idea is that these two, taken together, are
142 enough for both creation and assembly (later).
145 raise NotImplementedError
148 """Remove this device.
150 This makes sense only for some of the device types: LV and file
151 storeage. Also note that if the device can't attach, the removal
155 raise NotImplementedError
157 def Rename(self, new_id):
158 """Rename this device.
160 This may or may not make sense for a given device type.
163 raise NotImplementedError
165 def Open(self, force=False):
166 """Make the device ready for use.
168 This makes the device ready for I/O. For now, just the DRBD
171 The force parameter signifies that if the device has any kind of
172 --force thing, it should be used, we know what we are doing.
175 raise NotImplementedError
178 """Shut down the device, freeing its children.
180 This undoes the `Assemble()` work, except for the child
181 assembling; as such, the children on the device are still
182 assembled after this call.
185 raise NotImplementedError
187 def SetSyncSpeed(self, speed):
188 """Adjust the sync speed of the mirror.
190 In case this is not a mirroring device, this is no-op.
195 for child in self._children:
196 result = result and child.SetSyncSpeed(speed)
199 def GetSyncStatus(self):
200 """Returns the sync status of the device.
202 If this device is a mirroring device, this function returns the
203 status of the mirror.
206 (sync_percent, estimated_time, is_degraded, ldisk)
208 If sync_percent is None, it means the device is not syncing.
210 If estimated_time is None, it means we can't estimate
211 the time needed, otherwise it's the time left in seconds.
213 If is_degraded is True, it means the device is missing
214 redundancy. This is usually a sign that something went wrong in
215 the device setup, if sync_percent is None.
217 The ldisk parameter represents the degradation of the local
218 data. This is only valid for some devices, the rest will always
219 return False (not degraded).
222 return None, None, False, False
225 def CombinedSyncStatus(self):
226 """Calculate the mirror status recursively for our children.
228 The return value is the same as for `GetSyncStatus()` except the
229 minimum percent and maximum time are calculated across our
233 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
235 for child in self._children:
236 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
237 if min_percent is None:
238 min_percent = c_percent
239 elif c_percent is not None:
240 min_percent = min(min_percent, c_percent)
243 elif c_time is not None:
244 max_time = max(max_time, c_time)
245 is_degraded = is_degraded or c_degraded
246 ldisk = ldisk or c_ldisk
247 return min_percent, max_time, is_degraded, ldisk
250 def SetInfo(self, text):
251 """Update metadata with info text.
253 Only supported for some device types.
256 for child in self._children:
259 def Grow(self, amount):
260 """Grow the block device.
263 amount: the amount (in mebibytes) to grow with
268 raise NotImplementedError
271 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
272 (self.__class__, self.unique_id, self._children,
273 self.major, self.minor, self.dev_path))
276 class LogicalVolume(BlockDev):
277 """Logical Volume block device.
280 def __init__(self, unique_id, children):
281 """Attaches to a LV device.
283 The unique_id is a tuple (vg_name, lv_name)
286 super(LogicalVolume, self).__init__(unique_id, children)
287 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
288 raise ValueError("Invalid configuration data %s" % str(unique_id))
289 self._vg_name, self._lv_name = unique_id
290 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
291 self._degraded = True
292 self.major = self.minor = None
296 def Create(cls, unique_id, children, size):
297 """Create a new logical volume.
300 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
301 raise ValueError("Invalid configuration data %s" % str(unique_id))
302 vg_name, lv_name = unique_id
303 pvs_info = cls.GetPVInfo(vg_name)
305 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
310 pvlist = [ pv[1] for pv in pvs_info ]
311 free_size = sum([ pv[0] for pv in pvs_info ])
313 # The size constraint should have been checked from the master before
314 # calling the create function.
316 raise errors.BlockDeviceError("Not enough free space: required %s,"
317 " available %s" % (size, free_size))
318 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
321 raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
323 return LogicalVolume(unique_id, children)
326 def GetPVInfo(vg_name):
327 """Get the free space info for PVs in a volume group.
330 vg_name: the volume group name
333 list of (free_space, name) with free_space in mebibytes
336 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
337 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
339 result = utils.RunCmd(command)
341 logging.error("Can't get the PV information: %s - %s",
342 result.fail_reason, result.output)
345 for line in result.stdout.splitlines():
346 fields = line.strip().split(':')
348 logging.error("Can't parse pvs output: line '%s'", line)
350 # skip over pvs from another vg or ones which are not allocatable
351 if fields[1] != vg_name or fields[3][0] != 'a':
353 data.append((float(fields[2]), fields[0]))
358 """Remove this logical volume.
361 if not self.minor and not self.Attach():
362 # the LV does not exist
364 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
365 (self._vg_name, self._lv_name)])
367 logging.error("Can't lvremove: %s - %s",
368 result.fail_reason, result.output)
370 return not result.failed
372 def Rename(self, new_id):
373 """Rename this logical volume.
376 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
377 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
378 new_vg, new_name = new_id
379 if new_vg != self._vg_name:
380 raise errors.ProgrammerError("Can't move a logical volume across"
381 " volume groups (from %s to to %s)" %
382 (self._vg_name, new_vg))
383 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
385 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
387 self._lv_name = new_name
388 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
391 """Attach to an existing LV.
393 This method will try to see if an existing and active LV exists
394 which matches our name. If so, its major/minor will be
398 self.attached = False
399 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
400 "-olv_attr,lv_kernel_major,lv_kernel_minor",
403 logging.error("Can't find LV %s: %s, %s",
404 self.dev_path, result.fail_reason, result.output)
406 out = result.stdout.strip().rstrip(',')
409 logging.error("Can't parse LVS output, len(%s) != 3", str(out))
412 status, major, minor = out[:3]
414 logging.error("lvs lv_attr is not 6 characters (%s)", status)
420 except ValueError, err:
421 logging.error("lvs major/minor cannot be parsed: %s", str(err))
425 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
431 """Assemble the device.
433 We alway run `lvchange -ay` on the LV to ensure it's active before
434 use, as there were cases when xenvg was not active after boot
435 (also possibly after disk issues).
438 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
440 logging.error("Can't activate lv %s: %s", self.dev_path, result.output)
445 """Shutdown the device.
447 This is a no-op for the LV device type, as we don't deactivate the
453 def GetSyncStatus(self):
454 """Returns the sync status of the device.
456 If this device is a mirroring device, this function returns the
457 status of the mirror.
460 (sync_percent, estimated_time, is_degraded, ldisk)
462 For logical volumes, sync_percent and estimated_time are always
463 None (no recovery in progress, as we don't handle the mirrored LV
464 case). The is_degraded parameter is the inverse of the ldisk
467 For the ldisk parameter, we check if the logical volume has the
468 'virtual' type, which means it's not backed by existing storage
469 anymore (read from it return I/O error). This happens after a
470 physical disk failure and subsequent 'vgreduce --removemissing' on
473 The status was already read in Attach, so we just return it.
476 return None, None, self._degraded, self._degraded
478 def Open(self, force=False):
479 """Make the device ready for I/O.
481 This is a no-op for the LV device type.
487 """Notifies that the device will no longer be used for I/O.
489 This is a no-op for the LV device type.
494 def Snapshot(self, size):
495 """Create a snapshot copy of an lvm block device.
498 snap_name = self._lv_name + ".snap"
500 # remove existing snapshot if found
501 snap = LogicalVolume((self._vg_name, snap_name), None)
504 pvs_info = self.GetPVInfo(self._vg_name)
506 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
510 free_size, pv_name = pvs_info[0]
512 raise errors.BlockDeviceError("Not enough free space: required %s,"
513 " available %s" % (size, free_size))
515 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
516 "-n%s" % snap_name, self.dev_path])
518 raise errors.BlockDeviceError("command: %s error: %s - %s" %
519 (result.cmd, result.fail_reason,
524 def SetInfo(self, text):
525 """Update metadata with info text.
528 BlockDev.SetInfo(self, text)
530 # Replace invalid characters
531 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
532 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
534 # Only up to 128 characters are allowed
537 result = utils.RunCmd(["lvchange", "--addtag", text,
540 raise errors.BlockDeviceError("Command: %s error: %s - %s" %
541 (result.cmd, result.fail_reason,
543 def Grow(self, amount):
544 """Grow the logical volume.
547 # we try multiple algorithms since the 'best' ones might not have
548 # space available in the right place, but later ones might (since
549 # they have less constraints); also note that only recent LVM
551 for alloc_policy in "contiguous", "cling", "normal":
552 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
553 "-L", "+%dm" % amount, self.dev_path])
554 if not result.failed:
556 raise errors.BlockDeviceError("Can't grow LV %s: %s" %
557 (self.dev_path, result.output))
560 class DRBD8Status(object):
561 """A DRBD status representation class.
563 Note that this doesn't support unconfigured devices (cs:Unconfigured).
566 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+st:([^/]+)/(\S+)"
567 "\s+ds:([^/]+)/(\S+)\s+.*$")
568 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
569 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
571 def __init__(self, procline):
572 m = self.LINE_RE.match(procline)
574 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
575 self.cstatus = m.group(1)
576 self.lrole = m.group(2)
577 self.rrole = m.group(3)
578 self.ldisk = m.group(4)
579 self.rdisk = m.group(5)
581 self.is_standalone = self.cstatus == "StandAlone"
582 self.is_wfconn = self.cstatus == "WFConnection"
583 self.is_connected = self.cstatus == "Connected"
584 self.is_primary = self.lrole == "Primary"
585 self.is_secondary = self.lrole == "Secondary"
586 self.peer_primary = self.rrole == "Primary"
587 self.peer_secondary = self.rrole == "Secondary"
588 self.both_primary = self.is_primary and self.peer_primary
589 self.both_secondary = self.is_secondary and self.peer_secondary
591 self.is_diskless = self.ldisk == "Diskless"
592 self.is_disk_uptodate = self.ldisk == "UpToDate"
594 m = self.SYNC_RE.match(procline)
596 self.sync_percent = float(m.group(1))
597 hours = int(m.group(2))
598 minutes = int(m.group(3))
599 seconds = int(m.group(4))
600 self.est_time = hours * 3600 + minutes * 60 + seconds
602 self.sync_percent = None
605 self.is_sync_target = self.peer_sync_source = self.cstatus == "SyncTarget"
606 self.peer_sync_target = self.is_sync_source = self.cstatus == "SyncSource"
607 self.is_resync = self.is_sync_target or self.is_sync_source
610 class BaseDRBD(BlockDev):
613 This class contains a few bits of common functionality between the
614 0.7 and 8.x versions of DRBD.
617 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
618 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
621 _ST_UNCONFIGURED = "Unconfigured"
622 _ST_WFCONNECTION = "WFConnection"
623 _ST_CONNECTED = "Connected"
625 _STATUS_FILE = "/proc/drbd"
628 def _GetProcData(filename=_STATUS_FILE):
629 """Return data from /proc/drbd.
632 stat = open(filename, "r")
634 data = stat.read().splitlines()
638 raise errors.BlockDeviceError("Can't read any data from %s" % filename)
642 def _MassageProcData(data):
643 """Transform the output of _GetProdData into a nicer form.
646 a dictionary of minor: joined lines from /proc/drbd for that minor
649 lmatch = re.compile("^ *([0-9]+):.*$")
651 old_minor = old_line = None
653 lresult = lmatch.match(line)
654 if lresult is not None:
655 if old_minor is not None:
656 results[old_minor] = old_line
657 old_minor = int(lresult.group(1))
660 if old_minor is not None:
661 old_line += " " + line.strip()
663 if old_minor is not None:
664 results[old_minor] = old_line
668 def _GetVersion(cls):
669 """Return the DRBD version.
671 This will return a dict with keys:
677 proto2 (only on drbd > 8.2.X)
680 proc_data = cls._GetProcData()
681 first_line = proc_data[0].strip()
682 version = cls._VERSION_RE.match(first_line)
684 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
687 values = version.groups()
688 retval = {'k_major': int(values[0]),
689 'k_minor': int(values[1]),
690 'k_point': int(values[2]),
691 'api': int(values[3]),
692 'proto': int(values[4]),
694 if values[5] is not None:
695 retval['proto2'] = values[5]
701 """Return the path to a drbd device for a given minor.
704 return "/dev/drbd%d" % minor
707 def _GetUsedDevs(cls):
708 """Compute the list of used DRBD devices.
711 data = cls._GetProcData()
714 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
716 match = valid_line.match(line)
719 minor = int(match.group(1))
720 state = match.group(2)
721 if state == cls._ST_UNCONFIGURED:
723 used_devs[minor] = state, line
727 def _SetFromMinor(self, minor):
728 """Set our parameters based on the given minor.
730 This sets our minor variable and our dev_path.
734 self.minor = self.dev_path = None
735 self.attached = False
738 self.dev_path = self._DevPath(minor)
742 def _CheckMetaSize(meta_device):
743 """Check if the given meta device looks like a valid one.
745 This currently only check the size, which must be around
749 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
751 logging.error("Failed to get device size: %s - %s",
752 result.fail_reason, result.output)
755 sectors = int(result.stdout)
757 logging.error("Invalid output from blockdev: '%s'", result.stdout)
759 bytes = sectors * 512
760 if bytes < 128 * 1024 * 1024: # less than 128MiB
761 logging.error("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
763 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
764 logging.error("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
768 def Rename(self, new_id):
771 This is not supported for drbd devices.
774 raise errors.ProgrammerError("Can't rename a drbd device")
777 class DRBD8(BaseDRBD):
778 """DRBD v8.x block device.
780 This implements the local host part of the DRBD device, i.e. it
781 doesn't do anything to the supposed peer. If you need a fully
782 connected DRBD pair, you need to use this class on both hosts.
784 The unique_id for the drbd device is the (local_ip, local_port,
785 remote_ip, remote_port) tuple, and it must have two children: the
786 data device and the meta_device. The meta device is checked for
787 valid size and is zeroed on create.
793 def __init__(self, unique_id, children):
794 if children and children.count(None) > 0:
796 super(DRBD8, self).__init__(unique_id, children)
797 self.major = self._DRBD_MAJOR
798 version = self._GetVersion()
799 if version['k_major'] != 8 :
800 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
801 " requested ganeti usage: kernel is"
802 " %s.%s, ganeti wants 8.x" %
803 (version['k_major'], version['k_minor']))
805 if len(children) not in (0, 2):
806 raise ValueError("Invalid configuration data %s" % str(children))
807 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
808 raise ValueError("Invalid configuration data %s" % str(unique_id))
809 (self._lhost, self._lport,
810 self._rhost, self._rport,
811 self._aminor, self._secret) = unique_id
812 if (self._lhost is not None and self._lhost == self._rhost and
813 self._lport == self._rport):
814 raise ValueError("Invalid configuration data, same local/remote %s" %
819 def _InitMeta(cls, minor, dev_path):
820 """Initialize a meta device.
822 This will not work if the given minor is in use.
825 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
826 "v08", dev_path, "0", "create-md"])
828 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
832 def _FindUnusedMinor(cls):
833 """Find an unused DRBD device.
835 This is specific to 8.x as the minors are allocated dynamically,
836 so non-existing numbers up to a max minor count are actually free.
839 data = cls._GetProcData()
841 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
842 used_line = re.compile("^ *([0-9]+): cs:")
845 match = unused_line.match(line)
847 return int(match.group(1))
848 match = used_line.match(line)
850 minor = int(match.group(1))
851 highest = max(highest, minor)
852 if highest is None: # there are no minors in use at all
854 if highest >= cls._MAX_MINORS:
855 logging.error("Error: no free drbd minors!")
856 raise errors.BlockDeviceError("Can't find a free DRBD minor")
860 def _IsValidMeta(cls, meta_device):
861 """Check if the given meta device looks like a valid one.
864 minor = cls._FindUnusedMinor()
865 minor_path = cls._DevPath(minor)
866 result = utils.RunCmd(["drbdmeta", minor_path,
867 "v08", meta_device, "0",
870 logging.error("Invalid meta device %s: %s", meta_device, result.output)
875 def _GetShowParser(cls):
876 """Return a parser for `drbd show` output.
878 This will either create or return an already-create parser for the
879 output of the command `drbd show`.
882 if cls._PARSE_SHOW is not None:
883 return cls._PARSE_SHOW
886 lbrace = pyp.Literal("{").suppress()
887 rbrace = pyp.Literal("}").suppress()
888 semi = pyp.Literal(";").suppress()
889 # this also converts the value to an int
890 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
892 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
893 defa = pyp.Literal("_is_default").suppress()
894 dbl_quote = pyp.Literal('"').suppress()
896 keyword = pyp.Word(pyp.alphanums + '-')
899 value = pyp.Word(pyp.alphanums + '_-/.:')
900 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
901 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
903 # meta device, extended syntax
904 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
905 number + pyp.Word(']').suppress())
908 stmt = (~rbrace + keyword + ~lbrace +
909 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
910 pyp.Optional(defa) + semi +
911 pyp.Optional(pyp.restOfLine).suppress())
914 section_name = pyp.Word(pyp.alphas + '_')
915 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
917 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
920 cls._PARSE_SHOW = bnf
925 def _GetShowData(cls, minor):
926 """Return the `drbdsetup show` data for a minor.
929 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
931 logging.error("Can't display the drbd config: %s - %s",
932 result.fail_reason, result.output)
937 def _GetDevInfo(cls, out):
938 """Parse details about a given DRBD minor.
940 This return, if available, the local backing device (as a path)
941 and the local and remote (ip, port) information from a string
942 containing the output of the `drbdsetup show` command as returned
950 bnf = cls._GetShowParser()
954 results = bnf.parseString(out)
955 except pyp.ParseException, err:
956 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
959 # and massage the results into our desired format
960 for section in results:
962 if sname == "_this_host":
963 for lst in section[1:]:
965 data["local_dev"] = lst[1]
966 elif lst[0] == "meta-disk":
967 data["meta_dev"] = lst[1]
968 data["meta_index"] = lst[2]
969 elif lst[0] == "address":
970 data["local_addr"] = tuple(lst[1:])
971 elif sname == "_remote_host":
972 for lst in section[1:]:
973 if lst[0] == "address":
974 data["remote_addr"] = tuple(lst[1:])
977 def _MatchesLocal(self, info):
978 """Test if our local config matches with an existing device.
980 The parameter should be as returned from `_GetDevInfo()`. This
981 method tests if our local backing device is the same as the one in
982 the info parameter, in effect testing if we look like the given
987 backend, meta = self._children
989 backend = meta = None
991 if backend is not None:
992 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
994 retval = ("local_dev" not in info)
997 retval = retval and ("meta_dev" in info and
998 info["meta_dev"] == meta.dev_path)
999 retval = retval and ("meta_index" in info and
1000 info["meta_index"] == 0)
1002 retval = retval and ("meta_dev" not in info and
1003 "meta_index" not in info)
1006 def _MatchesNet(self, info):
1007 """Test if our network config matches with an existing device.
1009 The parameter should be as returned from `_GetDevInfo()`. This
1010 method tests if our network configuration is the same as the one
1011 in the info parameter, in effect testing if we look like the given
1015 if (((self._lhost is None and not ("local_addr" in info)) and
1016 (self._rhost is None and not ("remote_addr" in info)))):
1019 if self._lhost is None:
1022 if not ("local_addr" in info and
1023 "remote_addr" in info):
1026 retval = (info["local_addr"] == (self._lhost, self._lport))
1027 retval = (retval and
1028 info["remote_addr"] == (self._rhost, self._rport))
1032 def _AssembleLocal(cls, minor, backend, meta):
1033 """Configure the local part of a DRBD device.
1035 This is the first thing that must be done on an unconfigured DRBD
1036 device. And it must be done only once.
1039 if not cls._IsValidMeta(meta):
1041 args = ["drbdsetup", cls._DevPath(minor), "disk",
1042 backend, meta, "0", "-e", "detach", "--create-device"]
1043 result = utils.RunCmd(args)
1045 logging.error("Can't attach local disk: %s", result.output)
1046 return not result.failed
1049 def _AssembleNet(cls, minor, net_info, protocol,
1050 dual_pri=False, hmac=None, secret=None):
1051 """Configure the network part of the device.
1054 lhost, lport, rhost, rport = net_info
1055 if None in net_info:
1056 # we don't want network connection and actually want to make
1058 return cls._ShutdownNet(minor)
1060 args = ["drbdsetup", cls._DevPath(minor), "net",
1061 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1062 "-A", "discard-zero-changes",
1069 args.extend(["-a", hmac, "-x", secret])
1070 result = utils.RunCmd(args)
1072 logging.error("Can't setup network for dbrd device: %s - %s",
1073 result.fail_reason, result.output)
1076 timeout = time.time() + 10
1078 while time.time() < timeout:
1079 info = cls._GetDevInfo(cls._GetShowData(minor))
1080 if not "local_addr" in info or not "remote_addr" in info:
1083 if (info["local_addr"] != (lhost, lport) or
1084 info["remote_addr"] != (rhost, rport)):
1090 logging.error("Timeout while configuring network")
1094 def AddChildren(self, devices):
1095 """Add a disk to the DRBD device.
1098 if self.minor is None:
1099 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1100 if len(devices) != 2:
1101 raise errors.BlockDeviceError("Need two devices for AddChildren")
1102 info = self._GetDevInfo(self._GetShowData(self.minor))
1103 if "local_dev" in info:
1104 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1105 backend, meta = devices
1106 if backend.dev_path is None or meta.dev_path is None:
1107 raise errors.BlockDeviceError("Children not ready during AddChildren")
1110 if not self._CheckMetaSize(meta.dev_path):
1111 raise errors.BlockDeviceError("Invalid meta device size")
1112 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1113 if not self._IsValidMeta(meta.dev_path):
1114 raise errors.BlockDeviceError("Cannot initalize meta device")
1116 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1117 raise errors.BlockDeviceError("Can't attach to local storage")
1118 self._children = devices
1120 def RemoveChildren(self, devices):
1121 """Detach the drbd device from local storage.
1124 if self.minor is None:
1125 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1127 # early return if we don't actually have backing storage
1128 info = self._GetDevInfo(self._GetShowData(self.minor))
1129 if "local_dev" not in info:
1131 if len(self._children) != 2:
1132 raise errors.BlockDeviceError("We don't have two children: %s" %
1134 if self._children.count(None) == 2: # we don't actually have children :)
1135 logging.error("Requested detach while detached")
1137 if len(devices) != 2:
1138 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1139 for child, dev in zip(self._children, devices):
1140 if dev != child.dev_path:
1141 raise errors.BlockDeviceError("Mismatch in local storage"
1142 " (%s != %s) in RemoveChildren" %
1143 (dev, child.dev_path))
1145 if not self._ShutdownLocal(self.minor):
1146 raise errors.BlockDeviceError("Can't detach from local storage")
1149 def SetSyncSpeed(self, kbytes):
1150 """Set the speed of the DRBD syncer.
1153 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1154 if self.minor is None:
1155 logging.info("Instance not attached to a device")
1157 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1160 logging.error("Can't change syncer rate: %s - %s",
1161 result.fail_reason, result.output)
1162 return not result.failed and children_result
1164 def GetProcStatus(self):
1165 """Return device data from /proc.
1168 if self.minor is None:
1169 raise errors.BlockDeviceError("GetStats() called while not attached")
1170 proc_info = self._MassageProcData(self._GetProcData())
1171 if self.minor not in proc_info:
1172 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1174 return DRBD8Status(proc_info[self.minor])
1176 def GetSyncStatus(self):
1177 """Returns the sync status of the device.
1180 (sync_percent, estimated_time, is_degraded)
1182 If sync_percent is None, it means all is ok
1183 If estimated_time is None, it means we can't esimate
1184 the time needed, otherwise it's the time left in seconds.
1187 We set the is_degraded parameter to True on two conditions:
1188 network not connected or local disk missing.
1190 We compute the ldisk parameter based on wheter we have a local
1194 if self.minor is None and not self.Attach():
1195 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1196 stats = self.GetProcStatus()
1197 ldisk = not stats.is_disk_uptodate
1198 is_degraded = not stats.is_connected
1199 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1201 def Open(self, force=False):
1202 """Make the local state primary.
1204 If the 'force' parameter is given, the '-o' option is passed to
1205 drbdsetup. Since this is a potentially dangerous operation, the
1206 force flag should be only given after creation, when it actually
1210 if self.minor is None and not self.Attach():
1211 logging.error("DRBD cannot attach to a device during open")
1213 cmd = ["drbdsetup", self.dev_path, "primary"]
1216 result = utils.RunCmd(cmd)
1218 msg = ("Can't make drbd device primary: %s" % result.output)
1220 raise errors.BlockDeviceError(msg)
1223 """Make the local state secondary.
1225 This will, of course, fail if the device is in use.
1228 if self.minor is None and not self.Attach():
1229 logging.info("Instance not attached to a device")
1230 raise errors.BlockDeviceError("Can't find device")
1231 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1233 msg = ("Can't switch drbd device to"
1234 " secondary: %s" % result.output)
1236 raise errors.BlockDeviceError(msg)
1239 """Find a DRBD device which matches our config and attach to it.
1241 In case of partially attached (local device matches but no network
1242 setup), we perform the network attach. If successful, we re-test
1243 the attach if can return success.
1246 for minor in (self._aminor,):
1247 info = self._GetDevInfo(self._GetShowData(minor))
1248 match_l = self._MatchesLocal(info)
1249 match_r = self._MatchesNet(info)
1250 if match_l and match_r:
1252 if match_l and not match_r and "local_addr" not in info:
1253 res_r = self._AssembleNet(minor,
1254 (self._lhost, self._lport,
1255 self._rhost, self._rport),
1256 constants.DRBD_NET_PROTOCOL,
1257 hmac=constants.DRBD_HMAC_ALG,
1261 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1263 # the weakest case: we find something that is only net attached
1264 # even though we were passed some children at init time
1265 if match_r and "local_dev" not in info:
1268 # this case must be considered only if we actually have local
1269 # storage, i.e. not in diskless mode, because all diskless
1270 # devices are equal from the point of view of local
1272 if (match_l and "local_dev" in info and
1273 not match_r and "local_addr" in info):
1274 # strange case - the device network part points to somewhere
1275 # else, even though its local storage is ours; as we own the
1276 # drbd space, we try to disconnect from the remote peer and
1277 # reconnect to our correct one
1278 if not self._ShutdownNet(minor):
1279 raise errors.BlockDeviceError("Device has correct local storage,"
1280 " wrong remote peer and is unable to"
1281 " disconnect in order to attach to"
1282 " the correct peer")
1283 # note: _AssembleNet also handles the case when we don't want
1284 # local storage (i.e. one or more of the _[lr](host|port) is
1286 if (self._AssembleNet(minor, (self._lhost, self._lport,
1287 self._rhost, self._rport),
1288 constants.DRBD_NET_PROTOCOL,
1289 hmac=constants.DRBD_HMAC_ALG,
1290 secret=self._secret) and
1291 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1297 self._SetFromMinor(minor)
1298 return minor is not None
1301 """Assemble the drbd.
1304 - if we have a local backing device, we bind to it by:
1305 - checking the list of used drbd devices
1306 - check if the local minor use of any of them is our own device
1309 - if we have a local/remote net info:
1310 - redo the local backing device step for the remote device
1311 - check if any drbd device is using the local port,
1313 - check if any remote drbd device is using the remote
1314 port, if yes abort (for now)
1316 - bind the remote net port
1320 if self.minor is not None:
1321 logging.info("Already assembled")
1324 result = super(DRBD8, self).Assemble()
1328 # TODO: maybe completely tear-down the minor (drbdsetup ... down)
1329 # before attaching our own?
1330 minor = self._aminor
1331 need_localdev_teardown = False
1332 if self._children and self._children[0] and self._children[1]:
1333 result = self._AssembleLocal(minor, self._children[0].dev_path,
1334 self._children[1].dev_path)
1337 need_localdev_teardown = True
1338 if self._lhost and self._lport and self._rhost and self._rport:
1339 result = self._AssembleNet(minor,
1340 (self._lhost, self._lport,
1341 self._rhost, self._rport),
1342 constants.DRBD_NET_PROTOCOL,
1343 hmac=constants.DRBD_HMAC_ALG,
1344 secret=self._secret)
1346 if need_localdev_teardown:
1347 # we will ignore failures from this
1348 logging.error("net setup failed, tearing down local device")
1349 self._ShutdownAll(minor)
1351 self._SetFromMinor(minor)
1355 def _ShutdownLocal(cls, minor):
1356 """Detach from the local device.
1358 I/Os will continue to be served from the remote device. If we
1359 don't have a remote device, this operation will fail.
1362 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1364 logging.error("Can't detach local device: %s", result.output)
1365 return not result.failed
1368 def _ShutdownNet(cls, minor):
1369 """Disconnect from the remote peer.
1371 This fails if we don't have a local device.
1374 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1376 logging.error("Can't shutdown network: %s", result.output)
1377 return not result.failed
1380 def _ShutdownAll(cls, minor):
1381 """Deactivate the device.
1383 This will, of course, fail if the device is in use.
1386 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1388 logging.error("Can't shutdown drbd device: %s", result.output)
1389 return not result.failed
1392 """Shutdown the DRBD device.
1395 if self.minor is None and not self.Attach():
1396 logging.info("DRBD device not attached to a device during Shutdown")
1398 if not self._ShutdownAll(self.minor):
1401 self.dev_path = None
1405 """Stub remove for DRBD devices.
1408 return self.Shutdown()
1411 def Create(cls, unique_id, children, size):
1412 """Create a new DRBD8 device.
1414 Since DRBD devices are not created per se, just assembled, this
1415 function only initializes the metadata.
1418 if len(children) != 2:
1419 raise errors.ProgrammerError("Invalid setup for the drbd device")
1422 if not meta.Attach():
1423 raise errors.BlockDeviceError("Can't attach to meta device")
1424 if not cls._CheckMetaSize(meta.dev_path):
1425 raise errors.BlockDeviceError("Invalid meta device size")
1426 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1427 if not cls._IsValidMeta(meta.dev_path):
1428 raise errors.BlockDeviceError("Cannot initalize meta device")
1429 return cls(unique_id, children)
1431 def Grow(self, amount):
1432 """Resize the DRBD device and its backing storage.
1435 if self.minor is None:
1436 raise errors.ProgrammerError("drbd8: Grow called while not attached")
1437 if len(self._children) != 2 or None in self._children:
1438 raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1439 self._children[0].Grow(amount)
1440 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1442 raise errors.BlockDeviceError("resize failed for %s: %s" %
1443 (self.dev_path, result.output))
1447 class FileStorage(BlockDev):
1450 This class represents the a file storage backend device.
1452 The unique_id for the file device is a (file_driver, file_path) tuple.
1455 def __init__(self, unique_id, children):
1456 """Initalizes a file device backend.
1460 raise errors.BlockDeviceError("Invalid setup for file device")
1461 super(FileStorage, self).__init__(unique_id, children)
1462 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1463 raise ValueError("Invalid configuration data %s" % str(unique_id))
1464 self.driver = unique_id[0]
1465 self.dev_path = unique_id[1]
1468 """Assemble the device.
1470 Checks whether the file device exists, raises BlockDeviceError otherwise.
1473 if not os.path.exists(self.dev_path):
1474 raise errors.BlockDeviceError("File device '%s' does not exist." %
1479 """Shutdown the device.
1481 This is a no-op for the file type, as we don't deacivate
1482 the file on shutdown.
1487 def Open(self, force=False):
1488 """Make the device ready for I/O.
1490 This is a no-op for the file type.
1496 """Notifies that the device will no longer be used for I/O.
1498 This is a no-op for the file type.
1504 """Remove the file backing the block device.
1507 boolean indicating wheter removal of file was successful or not.
1510 if not os.path.exists(self.dev_path):
1513 os.remove(self.dev_path)
1515 except OSError, err:
1516 logging.error("Can't remove file '%s': %s", self.dev_path, err)
1520 """Attach to an existing file.
1522 Check if this file already exists.
1525 boolean indicating if file exists or not.
1528 if os.path.exists(self.dev_path):
1533 def Create(cls, unique_id, children, size):
1534 """Create a new file.
1538 size: integer size of file in MiB
1541 A ganeti.bdev.FileStorage object.
1544 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1545 raise ValueError("Invalid configuration data %s" % str(unique_id))
1546 dev_path = unique_id[1]
1548 f = open(dev_path, 'w')
1549 except IOError, err:
1550 raise errors.BlockDeviceError("Could not create '%'" % err)
1552 f.truncate(size * 1024 * 1024)
1555 return FileStorage(unique_id, children)
1559 constants.LD_LV: LogicalVolume,
1560 constants.LD_DRBD8: DRBD8,
1561 constants.LD_FILE: FileStorage,
1565 def FindDevice(dev_type, unique_id, children):
1566 """Search for an existing, assembled device.
1568 This will succeed only if the device exists and is assembled, but it
1569 does not do any actions in order to activate the device.
1572 if dev_type not in DEV_MAP:
1573 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1574 device = DEV_MAP[dev_type](unique_id, children)
1575 if not device.attached:
1580 def AttachOrAssemble(dev_type, unique_id, children):
1581 """Try to attach or assemble an existing device.
1583 This will attach to an existing assembled device or will assemble
1584 the device, as needed, to bring it fully up.
1587 if dev_type not in DEV_MAP:
1588 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1589 device = DEV_MAP[dev_type](unique_id, children)
1590 if not device.attached:
1592 if not device.attached:
1593 raise errors.BlockDeviceError("Can't find a valid block device for"
1595 (dev_type, unique_id, children))
1599 def Create(dev_type, unique_id, children, size):
1603 if dev_type not in DEV_MAP:
1604 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1605 device = DEV_MAP[dev_type].Create(unique_id, children, size)