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) != 4:
808 raise ValueError("Invalid configuration data %s" % str(unique_id))
809 self._lhost, self._lport, self._rhost, self._rport = unique_id
813 def _InitMeta(cls, minor, dev_path):
814 """Initialize a meta device.
816 This will not work if the given minor is in use.
819 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
820 "v08", dev_path, "0", "create-md"])
822 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
826 def _FindUnusedMinor(cls):
827 """Find an unused DRBD device.
829 This is specific to 8.x as the minors are allocated dynamically,
830 so non-existing numbers up to a max minor count are actually free.
833 data = cls._GetProcData()
835 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
836 used_line = re.compile("^ *([0-9]+): cs:")
839 match = unused_line.match(line)
841 return int(match.group(1))
842 match = used_line.match(line)
844 minor = int(match.group(1))
845 highest = max(highest, minor)
846 if highest is None: # there are no minors in use at all
848 if highest >= cls._MAX_MINORS:
849 logging.error("Error: no free drbd minors!")
850 raise errors.BlockDeviceError("Can't find a free DRBD minor")
854 def _IsValidMeta(cls, meta_device):
855 """Check if the given meta device looks like a valid one.
858 minor = cls._FindUnusedMinor()
859 minor_path = cls._DevPath(minor)
860 result = utils.RunCmd(["drbdmeta", minor_path,
861 "v08", meta_device, "0",
864 logging.error("Invalid meta device %s: %s", meta_device, result.output)
869 def _GetShowParser(cls):
870 """Return a parser for `drbd show` output.
872 This will either create or return an already-create parser for the
873 output of the command `drbd show`.
876 if cls._PARSE_SHOW is not None:
877 return cls._PARSE_SHOW
880 lbrace = pyp.Literal("{").suppress()
881 rbrace = pyp.Literal("}").suppress()
882 semi = pyp.Literal(";").suppress()
883 # this also converts the value to an int
884 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
886 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
887 defa = pyp.Literal("_is_default").suppress()
888 dbl_quote = pyp.Literal('"').suppress()
890 keyword = pyp.Word(pyp.alphanums + '-')
893 value = pyp.Word(pyp.alphanums + '_-/.:')
894 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
895 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
897 # meta device, extended syntax
898 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
899 number + pyp.Word(']').suppress())
902 stmt = (~rbrace + keyword + ~lbrace +
903 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
904 pyp.Optional(defa) + semi +
905 pyp.Optional(pyp.restOfLine).suppress())
908 section_name = pyp.Word(pyp.alphas + '_')
909 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
911 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
914 cls._PARSE_SHOW = bnf
919 def _GetShowData(cls, minor):
920 """Return the `drbdsetup show` data for a minor.
923 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
925 logging.error("Can't display the drbd config: %s - %s",
926 result.fail_reason, result.output)
931 def _GetDevInfo(cls, out):
932 """Parse details about a given DRBD minor.
934 This return, if available, the local backing device (as a path)
935 and the local and remote (ip, port) information from a string
936 containing the output of the `drbdsetup show` command as returned
944 bnf = cls._GetShowParser()
948 results = bnf.parseString(out)
949 except pyp.ParseException, err:
950 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
953 # and massage the results into our desired format
954 for section in results:
956 if sname == "_this_host":
957 for lst in section[1:]:
959 data["local_dev"] = lst[1]
960 elif lst[0] == "meta-disk":
961 data["meta_dev"] = lst[1]
962 data["meta_index"] = lst[2]
963 elif lst[0] == "address":
964 data["local_addr"] = tuple(lst[1:])
965 elif sname == "_remote_host":
966 for lst in section[1:]:
967 if lst[0] == "address":
968 data["remote_addr"] = tuple(lst[1:])
971 def _MatchesLocal(self, info):
972 """Test if our local config matches with an existing device.
974 The parameter should be as returned from `_GetDevInfo()`. This
975 method tests if our local backing device is the same as the one in
976 the info parameter, in effect testing if we look like the given
981 backend, meta = self._children
983 backend = meta = None
985 if backend is not None:
986 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
988 retval = ("local_dev" not in info)
991 retval = retval and ("meta_dev" in info and
992 info["meta_dev"] == meta.dev_path)
993 retval = retval and ("meta_index" in info and
994 info["meta_index"] == 0)
996 retval = retval and ("meta_dev" not in info and
997 "meta_index" not in info)
1000 def _MatchesNet(self, info):
1001 """Test if our network config matches with an existing device.
1003 The parameter should be as returned from `_GetDevInfo()`. This
1004 method tests if our network configuration is the same as the one
1005 in the info parameter, in effect testing if we look like the given
1009 if (((self._lhost is None and not ("local_addr" in info)) and
1010 (self._rhost is None and not ("remote_addr" in info)))):
1013 if self._lhost is None:
1016 if not ("local_addr" in info and
1017 "remote_addr" in info):
1020 retval = (info["local_addr"] == (self._lhost, self._lport))
1021 retval = (retval and
1022 info["remote_addr"] == (self._rhost, self._rport))
1026 def _AssembleLocal(cls, minor, backend, meta):
1027 """Configure the local part of a DRBD device.
1029 This is the first thing that must be done on an unconfigured DRBD
1030 device. And it must be done only once.
1033 if not cls._IsValidMeta(meta):
1035 args = ["drbdsetup", cls._DevPath(minor), "disk",
1036 backend, meta, "0", "-e", "detach", "--create-device"]
1037 result = utils.RunCmd(args)
1039 logging.error("Can't attach local disk: %s", result.output)
1040 return not result.failed
1043 def _AssembleNet(cls, minor, net_info, protocol,
1044 dual_pri=False, hmac=None, secret=None):
1045 """Configure the network part of the device.
1048 lhost, lport, rhost, rport = net_info
1049 if None in net_info:
1050 # we don't want network connection and actually want to make
1052 return cls._ShutdownNet(minor)
1054 args = ["drbdsetup", cls._DevPath(minor), "net",
1055 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1056 "-A", "discard-zero-changes",
1063 args.extend(["-a", hmac, "-x", secret])
1064 result = utils.RunCmd(args)
1066 logging.error("Can't setup network for dbrd device: %s - %s",
1067 result.fail_reason, result.output)
1070 timeout = time.time() + 10
1072 while time.time() < timeout:
1073 info = cls._GetDevInfo(cls._GetShowData(minor))
1074 if not "local_addr" in info or not "remote_addr" in info:
1077 if (info["local_addr"] != (lhost, lport) or
1078 info["remote_addr"] != (rhost, rport)):
1084 logging.error("Timeout while configuring network")
1088 def AddChildren(self, devices):
1089 """Add a disk to the DRBD device.
1092 if self.minor is None:
1093 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1094 if len(devices) != 2:
1095 raise errors.BlockDeviceError("Need two devices for AddChildren")
1096 info = self._GetDevInfo(self._GetShowData(self.minor))
1097 if "local_dev" in info:
1098 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1099 backend, meta = devices
1100 if backend.dev_path is None or meta.dev_path is None:
1101 raise errors.BlockDeviceError("Children not ready during AddChildren")
1104 if not self._CheckMetaSize(meta.dev_path):
1105 raise errors.BlockDeviceError("Invalid meta device size")
1106 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1107 if not self._IsValidMeta(meta.dev_path):
1108 raise errors.BlockDeviceError("Cannot initalize meta device")
1110 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1111 raise errors.BlockDeviceError("Can't attach to local storage")
1112 self._children = devices
1114 def RemoveChildren(self, devices):
1115 """Detach the drbd device from local storage.
1118 if self.minor is None:
1119 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1121 # early return if we don't actually have backing storage
1122 info = self._GetDevInfo(self._GetShowData(self.minor))
1123 if "local_dev" not in info:
1125 if len(self._children) != 2:
1126 raise errors.BlockDeviceError("We don't have two children: %s" %
1128 if self._children.count(None) == 2: # we don't actually have children :)
1129 logging.error("Requested detach while detached")
1131 if len(devices) != 2:
1132 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1133 for child, dev in zip(self._children, devices):
1134 if dev != child.dev_path:
1135 raise errors.BlockDeviceError("Mismatch in local storage"
1136 " (%s != %s) in RemoveChildren" %
1137 (dev, child.dev_path))
1139 if not self._ShutdownLocal(self.minor):
1140 raise errors.BlockDeviceError("Can't detach from local storage")
1143 def SetSyncSpeed(self, kbytes):
1144 """Set the speed of the DRBD syncer.
1147 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1148 if self.minor is None:
1149 logging.info("Instance not attached to a device")
1151 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1154 logging.error("Can't change syncer rate: %s - %s",
1155 result.fail_reason, result.output)
1156 return not result.failed and children_result
1158 def GetProcStatus(self):
1159 """Return device data from /proc.
1162 if self.minor is None:
1163 raise errors.BlockDeviceError("GetStats() called while not attached")
1164 proc_info = self._MassageProcData(self._GetProcData())
1165 if self.minor not in proc_info:
1166 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1168 return DRBD8Status(proc_info[self.minor])
1170 def GetSyncStatus(self):
1171 """Returns the sync status of the device.
1174 (sync_percent, estimated_time, is_degraded)
1176 If sync_percent is None, it means all is ok
1177 If estimated_time is None, it means we can't esimate
1178 the time needed, otherwise it's the time left in seconds.
1181 We set the is_degraded parameter to True on two conditions:
1182 network not connected or local disk missing.
1184 We compute the ldisk parameter based on wheter we have a local
1188 if self.minor is None and not self.Attach():
1189 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1190 stats = self.GetProcStatus()
1191 ldisk = not stats.is_disk_uptodate
1192 is_degraded = not stats.is_connected
1193 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1195 def Open(self, force=False):
1196 """Make the local state primary.
1198 If the 'force' parameter is given, the '-o' option is passed to
1199 drbdsetup. Since this is a potentially dangerous operation, the
1200 force flag should be only given after creation, when it actually
1204 if self.minor is None and not self.Attach():
1205 logging.error("DRBD cannot attach to a device during open")
1207 cmd = ["drbdsetup", self.dev_path, "primary"]
1210 result = utils.RunCmd(cmd)
1212 msg = ("Can't make drbd device primary: %s" % result.output)
1214 raise errors.BlockDeviceError(msg)
1217 """Make the local state secondary.
1219 This will, of course, fail if the device is in use.
1222 if self.minor is None and not self.Attach():
1223 logging.info("Instance not attached to a device")
1224 raise errors.BlockDeviceError("Can't find device")
1225 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1227 msg = ("Can't switch drbd device to"
1228 " secondary: %s" % result.output)
1230 raise errors.BlockDeviceError(msg)
1233 """Find a DRBD device which matches our config and attach to it.
1235 In case of partially attached (local device matches but no network
1236 setup), we perform the network attach. If successful, we re-test
1237 the attach if can return success.
1240 for minor in self._GetUsedDevs():
1241 info = self._GetDevInfo(self._GetShowData(minor))
1242 match_l = self._MatchesLocal(info)
1243 match_r = self._MatchesNet(info)
1244 if match_l and match_r:
1246 if match_l and not match_r and "local_addr" not in info:
1247 res_r = self._AssembleNet(minor,
1248 (self._lhost, self._lport,
1249 self._rhost, self._rport),
1252 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1254 # the weakest case: we find something that is only net attached
1255 # even though we were passed some children at init time
1256 if match_r and "local_dev" not in info:
1259 # this case must be considered only if we actually have local
1260 # storage, i.e. not in diskless mode, because all diskless
1261 # devices are equal from the point of view of local
1263 if (match_l and "local_dev" in info and
1264 not match_r and "local_addr" in info):
1265 # strange case - the device network part points to somewhere
1266 # else, even though its local storage is ours; as we own the
1267 # drbd space, we try to disconnect from the remote peer and
1268 # reconnect to our correct one
1269 if not self._ShutdownNet(minor):
1270 raise errors.BlockDeviceError("Device has correct local storage,"
1271 " wrong remote peer and is unable to"
1272 " disconnect in order to attach to"
1273 " the correct peer")
1274 # note: _AssembleNet also handles the case when we don't want
1275 # local storage (i.e. one or more of the _[lr](host|port) is
1277 if (self._AssembleNet(minor, (self._lhost, self._lport,
1278 self._rhost, self._rport), "C") and
1279 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1285 self._SetFromMinor(minor)
1286 return minor is not None
1289 """Assemble the drbd.
1292 - if we have a local backing device, we bind to it by:
1293 - checking the list of used drbd devices
1294 - check if the local minor use of any of them is our own device
1297 - if we have a local/remote net info:
1298 - redo the local backing device step for the remote device
1299 - check if any drbd device is using the local port,
1301 - check if any remote drbd device is using the remote
1302 port, if yes abort (for now)
1304 - bind the remote net port
1308 if self.minor is not None:
1309 logging.info("Already assembled")
1312 result = super(DRBD8, self).Assemble()
1316 minor = self._FindUnusedMinor()
1317 need_localdev_teardown = False
1318 if self._children and self._children[0] and self._children[1]:
1319 result = self._AssembleLocal(minor, self._children[0].dev_path,
1320 self._children[1].dev_path)
1323 need_localdev_teardown = True
1324 if self._lhost and self._lport and self._rhost and self._rport:
1325 result = self._AssembleNet(minor,
1326 (self._lhost, self._lport,
1327 self._rhost, self._rport),
1330 if need_localdev_teardown:
1331 # we will ignore failures from this
1332 logging.error("net setup failed, tearing down local device")
1333 self._ShutdownAll(minor)
1335 self._SetFromMinor(minor)
1339 def _ShutdownLocal(cls, minor):
1340 """Detach from the local device.
1342 I/Os will continue to be served from the remote device. If we
1343 don't have a remote device, this operation will fail.
1346 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1348 logging.error("Can't detach local device: %s", result.output)
1349 return not result.failed
1352 def _ShutdownNet(cls, minor):
1353 """Disconnect from the remote peer.
1355 This fails if we don't have a local device.
1358 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1360 logging.error("Can't shutdown network: %s", result.output)
1361 return not result.failed
1364 def _ShutdownAll(cls, minor):
1365 """Deactivate the device.
1367 This will, of course, fail if the device is in use.
1370 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1372 logging.error("Can't shutdown drbd device: %s", result.output)
1373 return not result.failed
1376 """Shutdown the DRBD device.
1379 if self.minor is None and not self.Attach():
1380 logging.info("DRBD device not attached to a device during Shutdown")
1382 if not self._ShutdownAll(self.minor):
1385 self.dev_path = None
1389 """Stub remove for DRBD devices.
1392 return self.Shutdown()
1395 def Create(cls, unique_id, children, size):
1396 """Create a new DRBD8 device.
1398 Since DRBD devices are not created per se, just assembled, this
1399 function only initializes the metadata.
1402 if len(children) != 2:
1403 raise errors.ProgrammerError("Invalid setup for the drbd device")
1406 if not meta.Attach():
1407 raise errors.BlockDeviceError("Can't attach to meta device")
1408 if not cls._CheckMetaSize(meta.dev_path):
1409 raise errors.BlockDeviceError("Invalid meta device size")
1410 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1411 if not cls._IsValidMeta(meta.dev_path):
1412 raise errors.BlockDeviceError("Cannot initalize meta device")
1413 return cls(unique_id, children)
1415 def Grow(self, amount):
1416 """Resize the DRBD device and its backing storage.
1419 if self.minor is None:
1420 raise errors.ProgrammerError("drbd8: Grow called while not attached")
1421 if len(self._children) != 2 or None in self._children:
1422 raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1423 self._children[0].Grow(amount)
1424 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1426 raise errors.BlockDeviceError("resize failed for %s: %s" %
1427 (self.dev_path, result.output))
1431 class FileStorage(BlockDev):
1434 This class represents the a file storage backend device.
1436 The unique_id for the file device is a (file_driver, file_path) tuple.
1439 def __init__(self, unique_id, children):
1440 """Initalizes a file device backend.
1444 raise errors.BlockDeviceError("Invalid setup for file device")
1445 super(FileStorage, self).__init__(unique_id, children)
1446 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1447 raise ValueError("Invalid configuration data %s" % str(unique_id))
1448 self.driver = unique_id[0]
1449 self.dev_path = unique_id[1]
1452 """Assemble the device.
1454 Checks whether the file device exists, raises BlockDeviceError otherwise.
1457 if not os.path.exists(self.dev_path):
1458 raise errors.BlockDeviceError("File device '%s' does not exist." %
1463 """Shutdown the device.
1465 This is a no-op for the file type, as we don't deacivate
1466 the file on shutdown.
1471 def Open(self, force=False):
1472 """Make the device ready for I/O.
1474 This is a no-op for the file type.
1480 """Notifies that the device will no longer be used for I/O.
1482 This is a no-op for the file type.
1488 """Remove the file backing the block device.
1491 boolean indicating wheter removal of file was successful or not.
1494 if not os.path.exists(self.dev_path):
1497 os.remove(self.dev_path)
1499 except OSError, err:
1500 logging.error("Can't remove file '%s': %s", self.dev_path, err)
1504 """Attach to an existing file.
1506 Check if this file already exists.
1509 boolean indicating if file exists or not.
1512 if os.path.exists(self.dev_path):
1517 def Create(cls, unique_id, children, size):
1518 """Create a new file.
1522 size: integer size of file in MiB
1525 A ganeti.bdev.FileStorage object.
1528 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1529 raise ValueError("Invalid configuration data %s" % str(unique_id))
1530 dev_path = unique_id[1]
1532 f = open(dev_path, 'w')
1533 except IOError, err:
1534 raise errors.BlockDeviceError("Could not create '%'" % err)
1536 f.truncate(size * 1024 * 1024)
1539 return FileStorage(unique_id, children)
1543 constants.LD_LV: LogicalVolume,
1544 constants.LD_DRBD8: DRBD8,
1545 constants.LD_FILE: FileStorage,
1549 def FindDevice(dev_type, unique_id, children):
1550 """Search for an existing, assembled device.
1552 This will succeed only if the device exists and is assembled, but it
1553 does not do any actions in order to activate the device.
1556 if dev_type not in DEV_MAP:
1557 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1558 device = DEV_MAP[dev_type](unique_id, children)
1559 if not device.attached:
1564 def AttachOrAssemble(dev_type, unique_id, children):
1565 """Try to attach or assemble an existing device.
1567 This will attach to an existing assembled device or will assemble
1568 the device, as needed, to bring it fully up.
1571 if dev_type not in DEV_MAP:
1572 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1573 device = DEV_MAP[dev_type](unique_id, children)
1574 if not device.attached:
1576 if not device.attached:
1577 raise errors.BlockDeviceError("Can't find a valid block device for"
1579 (dev_type, unique_id, children))
1583 def Create(dev_type, unique_id, children, size):
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].Create(unique_id, children, size)