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.
205 If sync_percent is None, it means the device is not syncing.
207 If estimated_time is None, it means we can't estimate
208 the time needed, otherwise it's the time left in seconds.
210 If is_degraded is True, it means the device is missing
211 redundancy. This is usually a sign that something went wrong in
212 the device setup, if sync_percent is None.
214 The ldisk parameter represents the degradation of the local
215 data. This is only valid for some devices, the rest will always
216 return False (not degraded).
219 @return: (sync_percent, estimated_time, is_degraded, ldisk)
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.
262 @param amount: the amount (in mebibytes) to grow with
265 raise NotImplementedError
268 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
269 (self.__class__, self.unique_id, self._children,
270 self.major, self.minor, self.dev_path))
273 class LogicalVolume(BlockDev):
274 """Logical Volume block device.
277 def __init__(self, unique_id, children):
278 """Attaches to a LV device.
280 The unique_id is a tuple (vg_name, lv_name)
283 super(LogicalVolume, self).__init__(unique_id, children)
284 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
285 raise ValueError("Invalid configuration data %s" % str(unique_id))
286 self._vg_name, self._lv_name = unique_id
287 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
288 self._degraded = True
289 self.major = self.minor = None
293 def Create(cls, unique_id, children, size):
294 """Create a new logical volume.
297 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
298 raise ValueError("Invalid configuration data %s" % str(unique_id))
299 vg_name, lv_name = unique_id
300 pvs_info = cls.GetPVInfo(vg_name)
302 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
307 pvlist = [ pv[1] for pv in pvs_info ]
308 free_size = sum([ pv[0] for pv in pvs_info ])
310 # The size constraint should have been checked from the master before
311 # calling the create function.
313 raise errors.BlockDeviceError("Not enough free space: required %s,"
314 " available %s" % (size, free_size))
315 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
318 raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
320 return LogicalVolume(unique_id, children)
323 def GetPVInfo(vg_name):
324 """Get the free space info for PVs in a volume group.
326 @param vg_name: the volume group name
329 @return: list of tuples (free_space, name) with free_space in mebibytes
332 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
333 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
335 result = utils.RunCmd(command)
337 logging.error("Can't get the PV information: %s - %s",
338 result.fail_reason, result.output)
341 for line in result.stdout.splitlines():
342 fields = line.strip().split(':')
344 logging.error("Can't parse pvs output: line '%s'", line)
346 # skip over pvs from another vg or ones which are not allocatable
347 if fields[1] != vg_name or fields[3][0] != 'a':
349 data.append((float(fields[2]), fields[0]))
354 """Remove this logical volume.
357 if not self.minor and not self.Attach():
358 # the LV does not exist
360 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
361 (self._vg_name, self._lv_name)])
363 logging.error("Can't lvremove: %s - %s",
364 result.fail_reason, result.output)
366 return not result.failed
368 def Rename(self, new_id):
369 """Rename this logical volume.
372 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
373 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
374 new_vg, new_name = new_id
375 if new_vg != self._vg_name:
376 raise errors.ProgrammerError("Can't move a logical volume across"
377 " volume groups (from %s to to %s)" %
378 (self._vg_name, new_vg))
379 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
381 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
383 self._lv_name = new_name
384 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
387 """Attach to an existing LV.
389 This method will try to see if an existing and active LV exists
390 which matches our name. If so, its major/minor will be
394 self.attached = False
395 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
396 "-olv_attr,lv_kernel_major,lv_kernel_minor",
399 logging.error("Can't find LV %s: %s, %s",
400 self.dev_path, result.fail_reason, result.output)
402 out = result.stdout.strip().rstrip(',')
405 logging.error("Can't parse LVS output, len(%s) != 3", str(out))
408 status, major, minor = out[:3]
410 logging.error("lvs lv_attr is not 6 characters (%s)", status)
416 except ValueError, err:
417 logging.error("lvs major/minor cannot be parsed: %s", str(err))
421 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
427 """Assemble the device.
429 We alway run `lvchange -ay` on the LV to ensure it's active before
430 use, as there were cases when xenvg was not active after boot
431 (also possibly after disk issues).
434 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
436 logging.error("Can't activate lv %s: %s", self.dev_path, result.output)
441 """Shutdown the device.
443 This is a no-op for the LV device type, as we don't deactivate the
449 def GetSyncStatus(self):
450 """Returns the sync status of the device.
452 If this device is a mirroring device, this function returns the
453 status of the mirror.
455 For logical volumes, sync_percent and estimated_time are always
456 None (no recovery in progress, as we don't handle the mirrored LV
457 case). The is_degraded parameter is the inverse of the ldisk
460 For the ldisk parameter, we check if the logical volume has the
461 'virtual' type, which means it's not backed by existing storage
462 anymore (read from it return I/O error). This happens after a
463 physical disk failure and subsequent 'vgreduce --removemissing' on
466 The status was already read in Attach, so we just return it.
469 @return: (sync_percent, estimated_time, is_degraded, ldisk)
472 return None, None, self._degraded, self._degraded
474 def Open(self, force=False):
475 """Make the device ready for I/O.
477 This is a no-op for the LV device type.
483 """Notifies that the device will no longer be used for I/O.
485 This is a no-op for the LV device type.
490 def Snapshot(self, size):
491 """Create a snapshot copy of an lvm block device.
494 snap_name = self._lv_name + ".snap"
496 # remove existing snapshot if found
497 snap = LogicalVolume((self._vg_name, snap_name), None)
500 pvs_info = self.GetPVInfo(self._vg_name)
502 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
506 free_size, pv_name = pvs_info[0]
508 raise errors.BlockDeviceError("Not enough free space: required %s,"
509 " available %s" % (size, free_size))
511 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
512 "-n%s" % snap_name, self.dev_path])
514 raise errors.BlockDeviceError("command: %s error: %s - %s" %
515 (result.cmd, result.fail_reason,
520 def SetInfo(self, text):
521 """Update metadata with info text.
524 BlockDev.SetInfo(self, text)
526 # Replace invalid characters
527 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
528 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
530 # Only up to 128 characters are allowed
533 result = utils.RunCmd(["lvchange", "--addtag", text,
536 raise errors.BlockDeviceError("Command: %s error: %s - %s" %
537 (result.cmd, result.fail_reason,
539 def Grow(self, amount):
540 """Grow the logical volume.
543 # we try multiple algorithms since the 'best' ones might not have
544 # space available in the right place, but later ones might (since
545 # they have less constraints); also note that only recent LVM
547 for alloc_policy in "contiguous", "cling", "normal":
548 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
549 "-L", "+%dm" % amount, self.dev_path])
550 if not result.failed:
552 raise errors.BlockDeviceError("Can't grow LV %s: %s" %
553 (self.dev_path, result.output))
556 class DRBD8Status(object):
557 """A DRBD status representation class.
559 Note that this doesn't support unconfigured devices (cs:Unconfigured).
562 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+st:([^/]+)/(\S+)"
563 "\s+ds:([^/]+)/(\S+)\s+.*$")
564 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
565 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
567 def __init__(self, procline):
568 m = self.LINE_RE.match(procline)
570 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
571 self.cstatus = m.group(1)
572 self.lrole = m.group(2)
573 self.rrole = m.group(3)
574 self.ldisk = m.group(4)
575 self.rdisk = m.group(5)
577 self.is_standalone = self.cstatus == "StandAlone"
578 self.is_wfconn = self.cstatus == "WFConnection"
579 self.is_connected = self.cstatus == "Connected"
580 self.is_primary = self.lrole == "Primary"
581 self.is_secondary = self.lrole == "Secondary"
582 self.peer_primary = self.rrole == "Primary"
583 self.peer_secondary = self.rrole == "Secondary"
584 self.both_primary = self.is_primary and self.peer_primary
585 self.both_secondary = self.is_secondary and self.peer_secondary
587 self.is_diskless = self.ldisk == "Diskless"
588 self.is_disk_uptodate = self.ldisk == "UpToDate"
590 m = self.SYNC_RE.match(procline)
592 self.sync_percent = float(m.group(1))
593 hours = int(m.group(2))
594 minutes = int(m.group(3))
595 seconds = int(m.group(4))
596 self.est_time = hours * 3600 + minutes * 60 + seconds
598 self.sync_percent = None
601 self.is_sync_target = self.peer_sync_source = self.cstatus == "SyncTarget"
602 self.peer_sync_target = self.is_sync_source = self.cstatus == "SyncSource"
603 self.is_resync = self.is_sync_target or self.is_sync_source
606 class BaseDRBD(BlockDev):
609 This class contains a few bits of common functionality between the
610 0.7 and 8.x versions of DRBD.
613 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
614 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
617 _ST_UNCONFIGURED = "Unconfigured"
618 _ST_WFCONNECTION = "WFConnection"
619 _ST_CONNECTED = "Connected"
621 _STATUS_FILE = "/proc/drbd"
624 def _GetProcData(filename=_STATUS_FILE):
625 """Return data from /proc/drbd.
628 stat = open(filename, "r")
630 data = stat.read().splitlines()
634 raise errors.BlockDeviceError("Can't read any data from %s" % filename)
638 def _MassageProcData(data):
639 """Transform the output of _GetProdData into a nicer form.
641 @return: a dictionary of minor: joined lines from /proc/drbd
645 lmatch = re.compile("^ *([0-9]+):.*$")
647 old_minor = old_line = None
649 lresult = lmatch.match(line)
650 if lresult is not None:
651 if old_minor is not None:
652 results[old_minor] = old_line
653 old_minor = int(lresult.group(1))
656 if old_minor is not None:
657 old_line += " " + line.strip()
659 if old_minor is not None:
660 results[old_minor] = old_line
664 def _GetVersion(cls):
665 """Return the DRBD version.
667 This will return a dict with keys:
673 - proto2 (only on drbd > 8.2.X)
676 proc_data = cls._GetProcData()
677 first_line = proc_data[0].strip()
678 version = cls._VERSION_RE.match(first_line)
680 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
683 values = version.groups()
684 retval = {'k_major': int(values[0]),
685 'k_minor': int(values[1]),
686 'k_point': int(values[2]),
687 'api': int(values[3]),
688 'proto': int(values[4]),
690 if values[5] is not None:
691 retval['proto2'] = values[5]
697 """Return the path to a drbd device for a given minor.
700 return "/dev/drbd%d" % minor
703 def _GetUsedDevs(cls):
704 """Compute the list of used DRBD devices.
707 data = cls._GetProcData()
710 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
712 match = valid_line.match(line)
715 minor = int(match.group(1))
716 state = match.group(2)
717 if state == cls._ST_UNCONFIGURED:
719 used_devs[minor] = state, line
723 def _SetFromMinor(self, minor):
724 """Set our parameters based on the given minor.
726 This sets our minor variable and our dev_path.
730 self.minor = self.dev_path = None
731 self.attached = False
734 self.dev_path = self._DevPath(minor)
738 def _CheckMetaSize(meta_device):
739 """Check if the given meta device looks like a valid one.
741 This currently only check the size, which must be around
745 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
747 logging.error("Failed to get device size: %s - %s",
748 result.fail_reason, result.output)
751 sectors = int(result.stdout)
753 logging.error("Invalid output from blockdev: '%s'", result.stdout)
755 bytes = sectors * 512
756 if bytes < 128 * 1024 * 1024: # less than 128MiB
757 logging.error("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
759 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
760 logging.error("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
764 def Rename(self, new_id):
767 This is not supported for drbd devices.
770 raise errors.ProgrammerError("Can't rename a drbd device")
773 class DRBD8(BaseDRBD):
774 """DRBD v8.x block device.
776 This implements the local host part of the DRBD device, i.e. it
777 doesn't do anything to the supposed peer. If you need a fully
778 connected DRBD pair, you need to use this class on both hosts.
780 The unique_id for the drbd device is the (local_ip, local_port,
781 remote_ip, remote_port) tuple, and it must have two children: the
782 data device and the meta_device. The meta device is checked for
783 valid size and is zeroed on create.
789 def __init__(self, unique_id, children):
790 if children and children.count(None) > 0:
792 super(DRBD8, self).__init__(unique_id, children)
793 self.major = self._DRBD_MAJOR
794 version = self._GetVersion()
795 if version['k_major'] != 8 :
796 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
797 " requested ganeti usage: kernel is"
798 " %s.%s, ganeti wants 8.x" %
799 (version['k_major'], version['k_minor']))
801 if len(children) not in (0, 2):
802 raise ValueError("Invalid configuration data %s" % str(children))
803 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
804 raise ValueError("Invalid configuration data %s" % str(unique_id))
805 (self._lhost, self._lport,
806 self._rhost, self._rport,
807 self._aminor, self._secret) = unique_id
808 if (self._lhost is not None and self._lhost == self._rhost and
809 self._lport == self._rport):
810 raise ValueError("Invalid configuration data, same local/remote %s" %
815 def _InitMeta(cls, minor, dev_path):
816 """Initialize a meta device.
818 This will not work if the given minor is in use.
821 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
822 "v08", dev_path, "0", "create-md"])
824 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
828 def _FindUnusedMinor(cls):
829 """Find an unused DRBD device.
831 This is specific to 8.x as the minors are allocated dynamically,
832 so non-existing numbers up to a max minor count are actually free.
835 data = cls._GetProcData()
837 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
838 used_line = re.compile("^ *([0-9]+): cs:")
841 match = unused_line.match(line)
843 return int(match.group(1))
844 match = used_line.match(line)
846 minor = int(match.group(1))
847 highest = max(highest, minor)
848 if highest is None: # there are no minors in use at all
850 if highest >= cls._MAX_MINORS:
851 logging.error("Error: no free drbd minors!")
852 raise errors.BlockDeviceError("Can't find a free DRBD minor")
856 def _IsValidMeta(cls, meta_device):
857 """Check if the given meta device looks like a valid one.
860 minor = cls._FindUnusedMinor()
861 minor_path = cls._DevPath(minor)
862 result = utils.RunCmd(["drbdmeta", minor_path,
863 "v08", meta_device, "0",
866 logging.error("Invalid meta device %s: %s", meta_device, result.output)
871 def _GetShowParser(cls):
872 """Return a parser for `drbd show` output.
874 This will either create or return an already-create parser for the
875 output of the command `drbd show`.
878 if cls._PARSE_SHOW is not None:
879 return cls._PARSE_SHOW
882 lbrace = pyp.Literal("{").suppress()
883 rbrace = pyp.Literal("}").suppress()
884 semi = pyp.Literal(";").suppress()
885 # this also converts the value to an int
886 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
888 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
889 defa = pyp.Literal("_is_default").suppress()
890 dbl_quote = pyp.Literal('"').suppress()
892 keyword = pyp.Word(pyp.alphanums + '-')
895 value = pyp.Word(pyp.alphanums + '_-/.:')
896 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
897 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
899 # meta device, extended syntax
900 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
901 number + pyp.Word(']').suppress())
904 stmt = (~rbrace + keyword + ~lbrace +
905 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
906 pyp.Optional(defa) + semi +
907 pyp.Optional(pyp.restOfLine).suppress())
910 section_name = pyp.Word(pyp.alphas + '_')
911 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
913 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
916 cls._PARSE_SHOW = bnf
921 def _GetShowData(cls, minor):
922 """Return the `drbdsetup show` data for a minor.
925 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
927 logging.error("Can't display the drbd config: %s - %s",
928 result.fail_reason, result.output)
933 def _GetDevInfo(cls, out):
934 """Parse details about a given DRBD minor.
936 This return, if available, the local backing device (as a path)
937 and the local and remote (ip, port) information from a string
938 containing the output of the `drbdsetup show` command as returned
946 bnf = cls._GetShowParser()
950 results = bnf.parseString(out)
951 except pyp.ParseException, err:
952 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
955 # and massage the results into our desired format
956 for section in results:
958 if sname == "_this_host":
959 for lst in section[1:]:
961 data["local_dev"] = lst[1]
962 elif lst[0] == "meta-disk":
963 data["meta_dev"] = lst[1]
964 data["meta_index"] = lst[2]
965 elif lst[0] == "address":
966 data["local_addr"] = tuple(lst[1:])
967 elif sname == "_remote_host":
968 for lst in section[1:]:
969 if lst[0] == "address":
970 data["remote_addr"] = tuple(lst[1:])
973 def _MatchesLocal(self, info):
974 """Test if our local config matches with an existing device.
976 The parameter should be as returned from `_GetDevInfo()`. This
977 method tests if our local backing device is the same as the one in
978 the info parameter, in effect testing if we look like the given
983 backend, meta = self._children
985 backend = meta = None
987 if backend is not None:
988 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
990 retval = ("local_dev" not in info)
993 retval = retval and ("meta_dev" in info and
994 info["meta_dev"] == meta.dev_path)
995 retval = retval and ("meta_index" in info and
996 info["meta_index"] == 0)
998 retval = retval and ("meta_dev" not in info and
999 "meta_index" not in info)
1002 def _MatchesNet(self, info):
1003 """Test if our network config matches with an existing device.
1005 The parameter should be as returned from `_GetDevInfo()`. This
1006 method tests if our network configuration is the same as the one
1007 in the info parameter, in effect testing if we look like the given
1011 if (((self._lhost is None and not ("local_addr" in info)) and
1012 (self._rhost is None and not ("remote_addr" in info)))):
1015 if self._lhost is None:
1018 if not ("local_addr" in info and
1019 "remote_addr" in info):
1022 retval = (info["local_addr"] == (self._lhost, self._lport))
1023 retval = (retval and
1024 info["remote_addr"] == (self._rhost, self._rport))
1028 def _AssembleLocal(cls, minor, backend, meta):
1029 """Configure the local part of a DRBD device.
1031 This is the first thing that must be done on an unconfigured DRBD
1032 device. And it must be done only once.
1035 if not cls._IsValidMeta(meta):
1037 args = ["drbdsetup", cls._DevPath(minor), "disk",
1038 backend, meta, "0", "-e", "detach", "--create-device"]
1039 result = utils.RunCmd(args)
1041 logging.error("Can't attach local disk: %s", result.output)
1042 return not result.failed
1045 def _AssembleNet(cls, minor, net_info, protocol,
1046 dual_pri=False, hmac=None, secret=None):
1047 """Configure the network part of the device.
1050 lhost, lport, rhost, rport = net_info
1051 if None in net_info:
1052 # we don't want network connection and actually want to make
1054 return cls._ShutdownNet(minor)
1056 args = ["drbdsetup", cls._DevPath(minor), "net",
1057 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1058 "-A", "discard-zero-changes",
1065 args.extend(["-a", hmac, "-x", secret])
1066 result = utils.RunCmd(args)
1068 logging.error("Can't setup network for dbrd device: %s - %s",
1069 result.fail_reason, result.output)
1072 timeout = time.time() + 10
1074 while time.time() < timeout:
1075 info = cls._GetDevInfo(cls._GetShowData(minor))
1076 if not "local_addr" in info or not "remote_addr" in info:
1079 if (info["local_addr"] != (lhost, lport) or
1080 info["remote_addr"] != (rhost, rport)):
1086 logging.error("Timeout while configuring network")
1090 def AddChildren(self, devices):
1091 """Add a disk to the DRBD device.
1094 if self.minor is None:
1095 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1096 if len(devices) != 2:
1097 raise errors.BlockDeviceError("Need two devices for AddChildren")
1098 info = self._GetDevInfo(self._GetShowData(self.minor))
1099 if "local_dev" in info:
1100 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1101 backend, meta = devices
1102 if backend.dev_path is None or meta.dev_path is None:
1103 raise errors.BlockDeviceError("Children not ready during AddChildren")
1106 if not self._CheckMetaSize(meta.dev_path):
1107 raise errors.BlockDeviceError("Invalid meta device size")
1108 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1109 if not self._IsValidMeta(meta.dev_path):
1110 raise errors.BlockDeviceError("Cannot initalize meta device")
1112 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1113 raise errors.BlockDeviceError("Can't attach to local storage")
1114 self._children = devices
1116 def RemoveChildren(self, devices):
1117 """Detach the drbd device from local storage.
1120 if self.minor is None:
1121 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1123 # early return if we don't actually have backing storage
1124 info = self._GetDevInfo(self._GetShowData(self.minor))
1125 if "local_dev" not in info:
1127 if len(self._children) != 2:
1128 raise errors.BlockDeviceError("We don't have two children: %s" %
1130 if self._children.count(None) == 2: # we don't actually have children :)
1131 logging.error("Requested detach while detached")
1133 if len(devices) != 2:
1134 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1135 for child, dev in zip(self._children, devices):
1136 if dev != child.dev_path:
1137 raise errors.BlockDeviceError("Mismatch in local storage"
1138 " (%s != %s) in RemoveChildren" %
1139 (dev, child.dev_path))
1141 if not self._ShutdownLocal(self.minor):
1142 raise errors.BlockDeviceError("Can't detach from local storage")
1145 def SetSyncSpeed(self, kbytes):
1146 """Set the speed of the DRBD syncer.
1149 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1150 if self.minor is None:
1151 logging.info("Instance not attached to a device")
1153 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1156 logging.error("Can't change syncer rate: %s - %s",
1157 result.fail_reason, result.output)
1158 return not result.failed and children_result
1160 def GetProcStatus(self):
1161 """Return device data from /proc.
1164 if self.minor is None:
1165 raise errors.BlockDeviceError("GetStats() called while not attached")
1166 proc_info = self._MassageProcData(self._GetProcData())
1167 if self.minor not in proc_info:
1168 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1170 return DRBD8Status(proc_info[self.minor])
1172 def GetSyncStatus(self):
1173 """Returns the sync status of the device.
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 @return: (sync_percent, estimated_time, is_degraded, ldisk)
1191 if self.minor is None and not self.Attach():
1192 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1193 stats = self.GetProcStatus()
1194 ldisk = not stats.is_disk_uptodate
1195 is_degraded = not stats.is_connected
1196 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1198 def Open(self, force=False):
1199 """Make the local state primary.
1201 If the 'force' parameter is given, the '-o' option is passed to
1202 drbdsetup. Since this is a potentially dangerous operation, the
1203 force flag should be only given after creation, when it actually
1207 if self.minor is None and not self.Attach():
1208 logging.error("DRBD cannot attach to a device during open")
1210 cmd = ["drbdsetup", self.dev_path, "primary"]
1213 result = utils.RunCmd(cmd)
1215 msg = ("Can't make drbd device primary: %s" % result.output)
1217 raise errors.BlockDeviceError(msg)
1220 """Make the local state secondary.
1222 This will, of course, fail if the device is in use.
1225 if self.minor is None and not self.Attach():
1226 logging.info("Instance not attached to a device")
1227 raise errors.BlockDeviceError("Can't find device")
1228 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1230 msg = ("Can't switch drbd device to"
1231 " secondary: %s" % result.output)
1233 raise errors.BlockDeviceError(msg)
1236 """Find a DRBD device which matches our config and attach to it.
1238 In case of partially attached (local device matches but no network
1239 setup), we perform the network attach. If successful, we re-test
1240 the attach if can return success.
1243 for minor in (self._aminor,):
1244 info = self._GetDevInfo(self._GetShowData(minor))
1245 match_l = self._MatchesLocal(info)
1246 match_r = self._MatchesNet(info)
1247 if match_l and match_r:
1249 if match_l and not match_r and "local_addr" not in info:
1250 res_r = self._AssembleNet(minor,
1251 (self._lhost, self._lport,
1252 self._rhost, self._rport),
1253 constants.DRBD_NET_PROTOCOL,
1254 hmac=constants.DRBD_HMAC_ALG,
1258 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1260 # the weakest case: we find something that is only net attached
1261 # even though we were passed some children at init time
1262 if match_r and "local_dev" not in info:
1265 # this case must be considered only if we actually have local
1266 # storage, i.e. not in diskless mode, because all diskless
1267 # devices are equal from the point of view of local
1269 if (match_l and "local_dev" in info and
1270 not match_r and "local_addr" in info):
1271 # strange case - the device network part points to somewhere
1272 # else, even though its local storage is ours; as we own the
1273 # drbd space, we try to disconnect from the remote peer and
1274 # reconnect to our correct one
1275 if not self._ShutdownNet(minor):
1276 raise errors.BlockDeviceError("Device has correct local storage,"
1277 " wrong remote peer and is unable to"
1278 " disconnect in order to attach to"
1279 " the correct peer")
1280 # note: _AssembleNet also handles the case when we don't want
1281 # local storage (i.e. one or more of the _[lr](host|port) is
1283 if (self._AssembleNet(minor, (self._lhost, self._lport,
1284 self._rhost, self._rport),
1285 constants.DRBD_NET_PROTOCOL,
1286 hmac=constants.DRBD_HMAC_ALG,
1287 secret=self._secret) and
1288 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1294 self._SetFromMinor(minor)
1295 return minor is not None
1298 """Assemble the drbd.
1301 - if we have a local backing device, we bind to it by:
1302 - checking the list of used drbd devices
1303 - check if the local minor use of any of them is our own device
1306 - if we have a local/remote net info:
1307 - redo the local backing device step for the remote device
1308 - check if any drbd device is using the local port,
1310 - check if any remote drbd device is using the remote
1311 port, if yes abort (for now)
1313 - bind the remote net port
1317 if self.minor is not None:
1318 logging.info("Already assembled")
1321 result = super(DRBD8, self).Assemble()
1325 # TODO: maybe completely tear-down the minor (drbdsetup ... down)
1326 # before attaching our own?
1327 minor = self._aminor
1328 need_localdev_teardown = False
1329 if self._children and self._children[0] and self._children[1]:
1330 result = self._AssembleLocal(minor, self._children[0].dev_path,
1331 self._children[1].dev_path)
1334 need_localdev_teardown = True
1335 if self._lhost and self._lport and self._rhost and self._rport:
1336 result = self._AssembleNet(minor,
1337 (self._lhost, self._lport,
1338 self._rhost, self._rport),
1339 constants.DRBD_NET_PROTOCOL,
1340 hmac=constants.DRBD_HMAC_ALG,
1341 secret=self._secret)
1343 if need_localdev_teardown:
1344 # we will ignore failures from this
1345 logging.error("net setup failed, tearing down local device")
1346 self._ShutdownAll(minor)
1348 self._SetFromMinor(minor)
1352 def _ShutdownLocal(cls, minor):
1353 """Detach from the local device.
1355 I/Os will continue to be served from the remote device. If we
1356 don't have a remote device, this operation will fail.
1359 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1361 logging.error("Can't detach local device: %s", result.output)
1362 return not result.failed
1365 def _ShutdownNet(cls, minor):
1366 """Disconnect from the remote peer.
1368 This fails if we don't have a local device.
1371 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1373 logging.error("Can't shutdown network: %s", result.output)
1374 return not result.failed
1377 def _ShutdownAll(cls, minor):
1378 """Deactivate the device.
1380 This will, of course, fail if the device is in use.
1383 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1385 logging.error("Can't shutdown drbd device: %s", result.output)
1386 return not result.failed
1389 """Shutdown the DRBD device.
1392 if self.minor is None and not self.Attach():
1393 logging.info("DRBD device not attached to a device during Shutdown")
1395 if not self._ShutdownAll(self.minor):
1398 self.dev_path = None
1402 """Stub remove for DRBD devices.
1405 return self.Shutdown()
1408 def Create(cls, unique_id, children, size):
1409 """Create a new DRBD8 device.
1411 Since DRBD devices are not created per se, just assembled, this
1412 function only initializes the metadata.
1415 if len(children) != 2:
1416 raise errors.ProgrammerError("Invalid setup for the drbd device")
1419 if not meta.Attach():
1420 raise errors.BlockDeviceError("Can't attach to meta device")
1421 if not cls._CheckMetaSize(meta.dev_path):
1422 raise errors.BlockDeviceError("Invalid meta device size")
1423 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1424 if not cls._IsValidMeta(meta.dev_path):
1425 raise errors.BlockDeviceError("Cannot initalize meta device")
1426 return cls(unique_id, children)
1428 def Grow(self, amount):
1429 """Resize the DRBD device and its backing storage.
1432 if self.minor is None:
1433 raise errors.ProgrammerError("drbd8: Grow called while not attached")
1434 if len(self._children) != 2 or None in self._children:
1435 raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1436 self._children[0].Grow(amount)
1437 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1439 raise errors.BlockDeviceError("resize failed for %s: %s" %
1440 (self.dev_path, result.output))
1444 class FileStorage(BlockDev):
1447 This class represents the a file storage backend device.
1449 The unique_id for the file device is a (file_driver, file_path) tuple.
1452 def __init__(self, unique_id, children):
1453 """Initalizes a file device backend.
1457 raise errors.BlockDeviceError("Invalid setup for file device")
1458 super(FileStorage, self).__init__(unique_id, children)
1459 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1460 raise ValueError("Invalid configuration data %s" % str(unique_id))
1461 self.driver = unique_id[0]
1462 self.dev_path = unique_id[1]
1466 """Assemble the device.
1468 Checks whether the file device exists, raises BlockDeviceError otherwise.
1471 if not os.path.exists(self.dev_path):
1472 raise errors.BlockDeviceError("File device '%s' does not exist." %
1477 """Shutdown the device.
1479 This is a no-op for the file type, as we don't deacivate
1480 the file on shutdown.
1485 def Open(self, force=False):
1486 """Make the device ready for I/O.
1488 This is a no-op for the file type.
1494 """Notifies that the device will no longer be used for I/O.
1496 This is a no-op for the file type.
1502 """Remove the file backing the block device.
1505 @return: True if the removal was successful
1508 if not os.path.exists(self.dev_path):
1511 os.remove(self.dev_path)
1513 except OSError, err:
1514 logging.error("Can't remove file '%s': %s", self.dev_path, err)
1518 """Attach to an existing file.
1520 Check if this file already exists.
1523 @return: True if file exists
1526 self.attached = os.path.exists(self.dev_path)
1527 return self.attached
1530 def Create(cls, unique_id, children, size):
1531 """Create a new file.
1533 @param size: the size of file in MiB
1535 @rtype: L{bdev.FileStorage}
1536 @return: an instance of FileStorage
1539 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1540 raise ValueError("Invalid configuration data %s" % str(unique_id))
1541 dev_path = unique_id[1]
1543 f = open(dev_path, 'w')
1544 except IOError, err:
1545 raise errors.BlockDeviceError("Could not create '%'" % err)
1547 f.truncate(size * 1024 * 1024)
1550 return FileStorage(unique_id, children)
1554 constants.LD_LV: LogicalVolume,
1555 constants.LD_DRBD8: DRBD8,
1556 constants.LD_FILE: FileStorage,
1560 def FindDevice(dev_type, unique_id, children):
1561 """Search for an existing, assembled device.
1563 This will succeed only if the device exists and is assembled, but it
1564 does not do any actions in order to activate the device.
1567 if dev_type not in DEV_MAP:
1568 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1569 device = DEV_MAP[dev_type](unique_id, children)
1570 if not device.attached:
1575 def AttachOrAssemble(dev_type, unique_id, children):
1576 """Try to attach or assemble an existing device.
1578 This will attach to an existing assembled device or will assemble
1579 the device, as needed, to bring it fully up.
1582 if dev_type not in DEV_MAP:
1583 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1584 device = DEV_MAP[dev_type](unique_id, children)
1585 if not device.attached:
1587 if not device.attached:
1588 raise errors.BlockDeviceError("Can't find a valid block device for"
1590 (dev_type, unique_id, children))
1594 def Create(dev_type, unique_id, children, size):
1598 if dev_type not in DEV_MAP:
1599 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1600 device = DEV_MAP[dev_type].Create(unique_id, children, size)