4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Block device abstraction"""
27 import pyparsing as pyp
30 from ganeti import utils
31 from ganeti import logger
32 from ganeti import errors
33 from ganeti import constants
36 class BlockDev(object):
37 """Block device abstract class.
39 A block device can be in the following states:
40 - not existing on the system, and by `Create()` it goes into:
41 - existing but not setup/not active, and by `Assemble()` goes into:
42 - active read-write and by `Open()` it goes into
43 - online (=used, or ready for use)
45 A device can also be online but read-only, however we are not using
46 the readonly state (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
87 """Assemble the device from its components.
89 If this is a plain block device (e.g. LVM) than assemble does
90 nothing, as the LVM has no children and we don't put logical
93 One guarantee is that after the device has been assembled, it
94 knows its major/minor numbers. This allows other devices (usually
95 parents) to probe correctly for their children.
99 for child in self._children:
100 if not isinstance(child, BlockDev):
101 raise TypeError("Invalid child passed of type '%s'" % type(child))
104 status = status and child.Assemble()
110 except errors.BlockDeviceError:
111 for child in self._children:
116 for child in self._children:
121 """Find a device which matches our config and attach to it.
124 raise NotImplementedError
127 """Notifies that the device will no longer be used for I/O.
130 raise NotImplementedError
133 def Create(cls, unique_id, children, size):
134 """Create the device.
136 If the device cannot be created, it will return None
137 instead. Error messages go to the logging system.
139 Note that for some devices, the unique_id is used, and for other,
140 the children. The idea is that these two, taken together, are
141 enough for both creation and assembly (later).
144 raise NotImplementedError
147 """Remove this device.
149 This makes sense only for some of the device types: LV and file
150 storeage. Also note that if the device can't attach, the removal
154 raise NotImplementedError
156 def Rename(self, new_id):
157 """Rename this device.
159 This may or may not make sense for a given device type.
162 raise NotImplementedError
164 def Open(self, force=False):
165 """Make the device ready for use.
167 This makes the device ready for I/O. For now, just the DRBD
170 The force parameter signifies that if the device has any kind of
171 --force thing, it should be used, we know what we are doing.
174 raise NotImplementedError
177 """Shut down the device, freeing its children.
179 This undoes the `Assemble()` work, except for the child
180 assembling; as such, the children on the device are still
181 assembled after this call.
184 raise NotImplementedError
186 def SetSyncSpeed(self, speed):
187 """Adjust the sync speed of the mirror.
189 In case this is not a mirroring device, this is no-op.
194 for child in self._children:
195 result = result and child.SetSyncSpeed(speed)
198 def GetSyncStatus(self):
199 """Returns the sync status of the device.
201 If this device is a mirroring device, this function returns the
202 status of the mirror.
205 (sync_percent, estimated_time, is_degraded, ldisk)
207 If sync_percent is None, it means the device is not syncing.
209 If estimated_time is None, it means we can't estimate
210 the time needed, otherwise it's the time left in seconds.
212 If is_degraded is True, it means the device is missing
213 redundancy. This is usually a sign that something went wrong in
214 the device setup, if sync_percent is None.
216 The ldisk parameter represents the degradation of the local
217 data. This is only valid for some devices, the rest will always
218 return False (not degraded).
221 return None, None, False, False
224 def CombinedSyncStatus(self):
225 """Calculate the mirror status recursively for our children.
227 The return value is the same as for `GetSyncStatus()` except the
228 minimum percent and maximum time are calculated across our
232 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
234 for child in self._children:
235 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
236 if min_percent is None:
237 min_percent = c_percent
238 elif c_percent is not None:
239 min_percent = min(min_percent, c_percent)
242 elif c_time is not None:
243 max_time = max(max_time, c_time)
244 is_degraded = is_degraded or c_degraded
245 ldisk = ldisk or c_ldisk
246 return min_percent, max_time, is_degraded, ldisk
249 def SetInfo(self, text):
250 """Update metadata with info text.
252 Only supported for some device types.
255 for child in self._children:
258 def Grow(self, amount):
259 """Grow the block device.
262 amount: the amount (in mebibytes) to grow with
267 raise NotImplementedError
270 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
271 (self.__class__, self.unique_id, self._children,
272 self.major, self.minor, self.dev_path))
275 class LogicalVolume(BlockDev):
276 """Logical Volume block device.
279 def __init__(self, unique_id, children):
280 """Attaches to a LV device.
282 The unique_id is a tuple (vg_name, lv_name)
285 super(LogicalVolume, self).__init__(unique_id, children)
286 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
287 raise ValueError("Invalid configuration data %s" % str(unique_id))
288 self._vg_name, self._lv_name = unique_id
289 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
290 self._degraded = True
291 self.major = self.minor = None
295 def Create(cls, unique_id, children, size):
296 """Create a new logical volume.
299 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
300 raise ValueError("Invalid configuration data %s" % str(unique_id))
301 vg_name, lv_name = unique_id
302 pvs_info = cls.GetPVInfo(vg_name)
304 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
309 pvlist = [ pv[1] for pv in pvs_info ]
310 free_size = sum([ pv[0] for pv in pvs_info ])
312 # The size constraint should have been checked from the master before
313 # calling the create function.
315 raise errors.BlockDeviceError("Not enough free space: required %s,"
316 " available %s" % (size, free_size))
317 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
320 raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
322 return LogicalVolume(unique_id, children)
325 def GetPVInfo(vg_name):
326 """Get the free space info for PVs in a volume group.
329 vg_name: the volume group name
332 list of (free_space, name) with free_space in mebibytes
335 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
336 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
338 result = utils.RunCmd(command)
340 logger.Error("Can't get the PV information: %s - %s" %
341 (result.fail_reason, result.output))
344 for line in result.stdout.splitlines():
345 fields = line.strip().split(':')
347 logger.Error("Can't parse pvs output: line '%s'" % line)
349 # skip over pvs from another vg or ones which are not allocatable
350 if fields[1] != vg_name or fields[3][0] != 'a':
352 data.append((float(fields[2]), fields[0]))
357 """Remove this logical volume.
360 if not self.minor and not self.Attach():
361 # the LV does not exist
363 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
364 (self._vg_name, self._lv_name)])
366 logger.Error("Can't lvremove: %s - %s" %
367 (result.fail_reason, result.output))
369 return not result.failed
371 def Rename(self, new_id):
372 """Rename this logical volume.
375 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
376 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
377 new_vg, new_name = new_id
378 if new_vg != self._vg_name:
379 raise errors.ProgrammerError("Can't move a logical volume across"
380 " volume groups (from %s to to %s)" %
381 (self._vg_name, new_vg))
382 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
384 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
386 self._lv_name = new_name
387 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
390 """Attach to an existing LV.
392 This method will try to see if an existing and active LV exists
393 which matches our name. If so, its major/minor will be
397 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
398 "-olv_attr,lv_kernel_major,lv_kernel_minor",
401 logger.Error("Can't find LV %s: %s, %s" %
402 (self.dev_path, result.fail_reason, result.output))
404 out = result.stdout.strip().rstrip(',')
407 logger.Error("Can't parse LVS output, len(%s) != 3" % str(out))
410 status, major, minor = out[:3]
412 logger.Error("lvs lv_attr is not 6 characters (%s)" % status)
418 except ValueError, err:
419 logger.Error("lvs major/minor cannot be parsed: %s" % str(err))
423 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
428 """Assemble the device.
430 We alway run `lvchange -ay` on the LV to ensure it's active before
431 use, as there were cases when xenvg was not active after boot
432 (also possibly after disk issues).
435 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
437 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
438 return not result.failed
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.
456 (sync_percent, estimated_time, is_degraded, ldisk)
458 For logical volumes, sync_percent and estimated_time are always
459 None (no recovery in progress, as we don't handle the mirrored LV
460 case). The is_degraded parameter is the inverse of the ldisk
463 For the ldisk parameter, we check if the logical volume has the
464 'virtual' type, which means it's not backed by existing storage
465 anymore (read from it return I/O error). This happens after a
466 physical disk failure and subsequent 'vgreduce --removemissing' on
469 The status was already read in Attach, so we just return it.
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.
642 a dictionary of minor: joined lines from /proc/drbd for that minor
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
733 self.dev_path = self._DevPath(minor)
736 def _CheckMetaSize(meta_device):
737 """Check if the given meta device looks like a valid one.
739 This currently only check the size, which must be around
743 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
745 logger.Error("Failed to get device size: %s - %s" %
746 (result.fail_reason, result.output))
749 sectors = int(result.stdout)
751 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
753 bytes = sectors * 512
754 if bytes < 128 * 1024 * 1024: # less than 128MiB
755 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
757 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
758 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
762 def Rename(self, new_id):
765 This is not supported for drbd devices.
768 raise errors.ProgrammerError("Can't rename a drbd device")
771 class DRBD8(BaseDRBD):
772 """DRBD v8.x block device.
774 This implements the local host part of the DRBD device, i.e. it
775 doesn't do anything to the supposed peer. If you need a fully
776 connected DRBD pair, you need to use this class on both hosts.
778 The unique_id for the drbd device is the (local_ip, local_port,
779 remote_ip, remote_port) tuple, and it must have two children: the
780 data device and the meta_device. The meta device is checked for
781 valid size and is zeroed on create.
787 def __init__(self, unique_id, children):
788 if children and children.count(None) > 0:
790 super(DRBD8, self).__init__(unique_id, children)
791 self.major = self._DRBD_MAJOR
792 version = self._GetVersion()
793 if version['k_major'] != 8 :
794 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
795 " requested ganeti usage: kernel is"
796 " %s.%s, ganeti wants 8.x" %
797 (version['k_major'], version['k_minor']))
799 if len(children) not in (0, 2):
800 raise ValueError("Invalid configuration data %s" % str(children))
801 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
802 raise ValueError("Invalid configuration data %s" % str(unique_id))
803 self._lhost, self._lport, self._rhost, self._rport = unique_id
807 def _InitMeta(cls, minor, dev_path):
808 """Initialize a meta device.
810 This will not work if the given minor is in use.
813 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
814 "v08", dev_path, "0", "create-md"])
816 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
820 def _FindUnusedMinor(cls):
821 """Find an unused DRBD device.
823 This is specific to 8.x as the minors are allocated dynamically,
824 so non-existing numbers up to a max minor count are actually free.
827 data = cls._GetProcData()
829 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
830 used_line = re.compile("^ *([0-9]+): cs:")
833 match = unused_line.match(line)
835 return int(match.group(1))
836 match = used_line.match(line)
838 minor = int(match.group(1))
839 highest = max(highest, minor)
840 if highest is None: # there are no minors in use at all
842 if highest >= cls._MAX_MINORS:
843 logger.Error("Error: no free drbd minors!")
844 raise errors.BlockDeviceError("Can't find a free DRBD minor")
848 def _IsValidMeta(cls, meta_device):
849 """Check if the given meta device looks like a valid one.
852 minor = cls._FindUnusedMinor()
853 minor_path = cls._DevPath(minor)
854 result = utils.RunCmd(["drbdmeta", minor_path,
855 "v08", meta_device, "0",
858 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
863 def _GetShowParser(cls):
864 """Return a parser for `drbd show` output.
866 This will either create or return an already-create parser for the
867 output of the command `drbd show`.
870 if cls._PARSE_SHOW is not None:
871 return cls._PARSE_SHOW
874 lbrace = pyp.Literal("{").suppress()
875 rbrace = pyp.Literal("}").suppress()
876 semi = pyp.Literal(";").suppress()
877 # this also converts the value to an int
878 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
880 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
881 defa = pyp.Literal("_is_default").suppress()
882 dbl_quote = pyp.Literal('"').suppress()
884 keyword = pyp.Word(pyp.alphanums + '-')
887 value = pyp.Word(pyp.alphanums + '_-/.:')
888 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
889 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
891 # meta device, extended syntax
892 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
893 number + pyp.Word(']').suppress())
896 stmt = (~rbrace + keyword + ~lbrace +
897 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
898 pyp.Optional(defa) + semi +
899 pyp.Optional(pyp.restOfLine).suppress())
902 section_name = pyp.Word(pyp.alphas + '_')
903 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
905 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
908 cls._PARSE_SHOW = bnf
913 def _GetShowData(cls, minor):
914 """Return the `drbdsetup show` data for a minor.
917 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
919 logger.Error("Can't display the drbd config: %s - %s" %
920 (result.fail_reason, result.output))
925 def _GetDevInfo(cls, out):
926 """Parse details about a given DRBD minor.
928 This return, if available, the local backing device (as a path)
929 and the local and remote (ip, port) information from a string
930 containing the output of the `drbdsetup show` command as returned
938 bnf = cls._GetShowParser()
942 results = bnf.parseString(out)
943 except pyp.ParseException, err:
944 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
947 # and massage the results into our desired format
948 for section in results:
950 if sname == "_this_host":
951 for lst in section[1:]:
953 data["local_dev"] = lst[1]
954 elif lst[0] == "meta-disk":
955 data["meta_dev"] = lst[1]
956 data["meta_index"] = lst[2]
957 elif lst[0] == "address":
958 data["local_addr"] = tuple(lst[1:])
959 elif sname == "_remote_host":
960 for lst in section[1:]:
961 if lst[0] == "address":
962 data["remote_addr"] = tuple(lst[1:])
965 def _MatchesLocal(self, info):
966 """Test if our local config matches with an existing device.
968 The parameter should be as returned from `_GetDevInfo()`. This
969 method tests if our local backing device is the same as the one in
970 the info parameter, in effect testing if we look like the given
975 backend, meta = self._children
977 backend = meta = None
979 if backend is not None:
980 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
982 retval = ("local_dev" not in info)
985 retval = retval and ("meta_dev" in info and
986 info["meta_dev"] == meta.dev_path)
987 retval = retval and ("meta_index" in info and
988 info["meta_index"] == 0)
990 retval = retval and ("meta_dev" not in info and
991 "meta_index" not in info)
994 def _MatchesNet(self, info):
995 """Test if our network config matches with an existing device.
997 The parameter should be as returned from `_GetDevInfo()`. This
998 method tests if our network configuration is the same as the one
999 in the info parameter, in effect testing if we look like the given
1003 if (((self._lhost is None and not ("local_addr" in info)) and
1004 (self._rhost is None and not ("remote_addr" in info)))):
1007 if self._lhost is None:
1010 if not ("local_addr" in info and
1011 "remote_addr" in info):
1014 retval = (info["local_addr"] == (self._lhost, self._lport))
1015 retval = (retval and
1016 info["remote_addr"] == (self._rhost, self._rport))
1020 def _AssembleLocal(cls, minor, backend, meta):
1021 """Configure the local part of a DRBD device.
1023 This is the first thing that must be done on an unconfigured DRBD
1024 device. And it must be done only once.
1027 if not cls._IsValidMeta(meta):
1029 args = ["drbdsetup", cls._DevPath(minor), "disk",
1030 backend, meta, "0", "-e", "detach", "--create-device"]
1031 result = utils.RunCmd(args)
1033 logger.Error("Can't attach local disk: %s" % result.output)
1034 return not result.failed
1037 def _AssembleNet(cls, minor, net_info, protocol,
1038 dual_pri=False, hmac=None, secret=None):
1039 """Configure the network part of the device.
1042 lhost, lport, rhost, rport = net_info
1043 if None in net_info:
1044 # we don't want network connection and actually want to make
1046 return cls._ShutdownNet(minor)
1048 args = ["drbdsetup", cls._DevPath(minor), "net",
1049 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1050 "-A", "discard-zero-changes",
1057 args.extend(["-a", hmac, "-x", secret])
1058 result = utils.RunCmd(args)
1060 logger.Error("Can't setup network for dbrd device: %s - %s" %
1061 (result.fail_reason, result.output))
1064 timeout = time.time() + 10
1066 while time.time() < timeout:
1067 info = cls._GetDevInfo(cls._GetShowData(minor))
1068 if not "local_addr" in info or not "remote_addr" in info:
1071 if (info["local_addr"] != (lhost, lport) or
1072 info["remote_addr"] != (rhost, rport)):
1078 logger.Error("Timeout while configuring network")
1082 def AddChildren(self, devices):
1083 """Add a disk to the DRBD device.
1086 if self.minor is None:
1087 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1088 if len(devices) != 2:
1089 raise errors.BlockDeviceError("Need two devices for AddChildren")
1090 info = self._GetDevInfo(self._GetShowData(self.minor))
1091 if "local_dev" in info:
1092 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1093 backend, meta = devices
1094 if backend.dev_path is None or meta.dev_path is None:
1095 raise errors.BlockDeviceError("Children not ready during AddChildren")
1098 if not self._CheckMetaSize(meta.dev_path):
1099 raise errors.BlockDeviceError("Invalid meta device size")
1100 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1101 if not self._IsValidMeta(meta.dev_path):
1102 raise errors.BlockDeviceError("Cannot initalize meta device")
1104 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1105 raise errors.BlockDeviceError("Can't attach to local storage")
1106 self._children = devices
1108 def RemoveChildren(self, devices):
1109 """Detach the drbd device from local storage.
1112 if self.minor is None:
1113 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1115 # early return if we don't actually have backing storage
1116 info = self._GetDevInfo(self._GetShowData(self.minor))
1117 if "local_dev" not in info:
1119 if len(self._children) != 2:
1120 raise errors.BlockDeviceError("We don't have two children: %s" %
1122 if self._children.count(None) == 2: # we don't actually have children :)
1123 logger.Error("Requested detach while detached")
1125 if len(devices) != 2:
1126 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1127 for child, dev in zip(self._children, devices):
1128 if dev != child.dev_path:
1129 raise errors.BlockDeviceError("Mismatch in local storage"
1130 " (%s != %s) in RemoveChildren" %
1131 (dev, child.dev_path))
1133 if not self._ShutdownLocal(self.minor):
1134 raise errors.BlockDeviceError("Can't detach from local storage")
1137 def SetSyncSpeed(self, kbytes):
1138 """Set the speed of the DRBD syncer.
1141 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1142 if self.minor is None:
1143 logger.Info("Instance not attached to a device")
1145 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1148 logger.Error("Can't change syncer rate: %s - %s" %
1149 (result.fail_reason, result.output))
1150 return not result.failed and children_result
1152 def GetProcStatus(self):
1153 """Return device data from /proc.
1156 if self.minor is None:
1157 raise errors.BlockDeviceError("GetStats() called while not attached")
1158 proc_info = self._MassageProcData(self._GetProcData())
1159 if self.minor not in proc_info:
1160 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1162 return DRBD8Status(proc_info[self.minor])
1164 def GetSyncStatus(self):
1165 """Returns the sync status of the device.
1168 (sync_percent, estimated_time, is_degraded)
1170 If sync_percent is None, it means all is ok
1171 If estimated_time is None, it means we can't esimate
1172 the time needed, otherwise it's the time left in seconds.
1175 We set the is_degraded parameter to True on two conditions:
1176 network not connected or local disk missing.
1178 We compute the ldisk parameter based on wheter we have a local
1182 if self.minor is None and not self.Attach():
1183 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1184 stats = self.GetProcStatus()
1185 ldisk = not stats.is_disk_uptodate
1186 is_degraded = not stats.is_connected
1187 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1189 def Open(self, force=False):
1190 """Make the local state primary.
1192 If the 'force' parameter is given, the '-o' option is passed to
1193 drbdsetup. Since this is a potentially dangerous operation, the
1194 force flag should be only given after creation, when it actually
1198 if self.minor is None and not self.Attach():
1199 logger.Error("DRBD cannot attach to a device during open")
1201 cmd = ["drbdsetup", self.dev_path, "primary"]
1204 result = utils.RunCmd(cmd)
1206 msg = ("Can't make drbd device primary: %s" % result.output)
1208 raise errors.BlockDeviceError(msg)
1211 """Make the local state secondary.
1213 This will, of course, fail if the device is in use.
1216 if self.minor is None and not self.Attach():
1217 logger.Info("Instance not attached to a device")
1218 raise errors.BlockDeviceError("Can't find device")
1219 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1221 msg = ("Can't switch drbd device to"
1222 " secondary: %s" % result.output)
1224 raise errors.BlockDeviceError(msg)
1227 """Find a DRBD device which matches our config and attach to it.
1229 In case of partially attached (local device matches but no network
1230 setup), we perform the network attach. If successful, we re-test
1231 the attach if can return success.
1234 for minor in self._GetUsedDevs():
1235 info = self._GetDevInfo(self._GetShowData(minor))
1236 match_l = self._MatchesLocal(info)
1237 match_r = self._MatchesNet(info)
1238 if match_l and match_r:
1240 if match_l and not match_r and "local_addr" not in info:
1241 res_r = self._AssembleNet(minor,
1242 (self._lhost, self._lport,
1243 self._rhost, self._rport),
1246 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1248 # the weakest case: we find something that is only net attached
1249 # even though we were passed some children at init time
1250 if match_r and "local_dev" not in info:
1253 # this case must be considered only if we actually have local
1254 # storage, i.e. not in diskless mode, because all diskless
1255 # devices are equal from the point of view of local
1257 if (match_l and "local_dev" in info and
1258 not match_r and "local_addr" in info):
1259 # strange case - the device network part points to somewhere
1260 # else, even though its local storage is ours; as we own the
1261 # drbd space, we try to disconnect from the remote peer and
1262 # reconnect to our correct one
1263 if not self._ShutdownNet(minor):
1264 raise errors.BlockDeviceError("Device has correct local storage,"
1265 " wrong remote peer and is unable to"
1266 " disconnect in order to attach to"
1267 " the correct peer")
1268 # note: _AssembleNet also handles the case when we don't want
1269 # local storage (i.e. one or more of the _[lr](host|port) is
1271 if (self._AssembleNet(minor, (self._lhost, self._lport,
1272 self._rhost, self._rport), "C") and
1273 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1279 self._SetFromMinor(minor)
1280 return minor is not None
1283 """Assemble the drbd.
1286 - if we have a local backing device, we bind to it by:
1287 - checking the list of used drbd devices
1288 - check if the local minor use of any of them is our own device
1291 - if we have a local/remote net info:
1292 - redo the local backing device step for the remote device
1293 - check if any drbd device is using the local port,
1295 - check if any remote drbd device is using the remote
1296 port, if yes abort (for now)
1298 - bind the remote net port
1302 if self.minor is not None:
1303 logger.Info("Already assembled")
1306 result = super(DRBD8, self).Assemble()
1310 minor = self._FindUnusedMinor()
1311 need_localdev_teardown = False
1312 if self._children and self._children[0] and self._children[1]:
1313 result = self._AssembleLocal(minor, self._children[0].dev_path,
1314 self._children[1].dev_path)
1317 need_localdev_teardown = True
1318 if self._lhost and self._lport and self._rhost and self._rport:
1319 result = self._AssembleNet(minor,
1320 (self._lhost, self._lport,
1321 self._rhost, self._rport),
1324 if need_localdev_teardown:
1325 # we will ignore failures from this
1326 logger.Error("net setup failed, tearing down local device")
1327 self._ShutdownAll(minor)
1329 self._SetFromMinor(minor)
1333 def _ShutdownLocal(cls, minor):
1334 """Detach from the local device.
1336 I/Os will continue to be served from the remote device. If we
1337 don't have a remote device, this operation will fail.
1340 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1342 logger.Error("Can't detach local device: %s" % result.output)
1343 return not result.failed
1346 def _ShutdownNet(cls, minor):
1347 """Disconnect from the remote peer.
1349 This fails if we don't have a local device.
1352 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1354 logger.Error("Can't shutdown network: %s" % result.output)
1355 return not result.failed
1358 def _ShutdownAll(cls, minor):
1359 """Deactivate the device.
1361 This will, of course, fail if the device is in use.
1364 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1366 logger.Error("Can't shutdown drbd device: %s" % result.output)
1367 return not result.failed
1370 """Shutdown the DRBD device.
1373 if self.minor is None and not self.Attach():
1374 logger.Info("DRBD device not attached to a device during Shutdown")
1376 if not self._ShutdownAll(self.minor):
1379 self.dev_path = None
1383 """Stub remove for DRBD devices.
1386 return self.Shutdown()
1389 def Create(cls, unique_id, children, size):
1390 """Create a new DRBD8 device.
1392 Since DRBD devices are not created per se, just assembled, this
1393 function only initializes the metadata.
1396 if len(children) != 2:
1397 raise errors.ProgrammerError("Invalid setup for the drbd device")
1400 if not meta.Attach():
1401 raise errors.BlockDeviceError("Can't attach to meta device")
1402 if not cls._CheckMetaSize(meta.dev_path):
1403 raise errors.BlockDeviceError("Invalid meta device size")
1404 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1405 if not cls._IsValidMeta(meta.dev_path):
1406 raise errors.BlockDeviceError("Cannot initalize meta device")
1407 return cls(unique_id, children)
1409 def Grow(self, amount):
1410 """Resize the DRBD device and its backing storage.
1413 if self.minor is None:
1414 raise errors.ProgrammerError("drbd8: Grow called while not attached")
1415 if len(self._children) != 2 or None in self._children:
1416 raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1417 self._children[0].Grow(amount)
1418 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1420 raise errors.BlockDeviceError("resize failed for %s: %s" %
1421 (self.dev_path, result.output))
1425 class FileStorage(BlockDev):
1428 This class represents the a file storage backend device.
1430 The unique_id for the file device is a (file_driver, file_path) tuple.
1433 def __init__(self, unique_id, children):
1434 """Initalizes a file device backend.
1438 raise errors.BlockDeviceError("Invalid setup for file device")
1439 super(FileStorage, self).__init__(unique_id, children)
1440 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1441 raise ValueError("Invalid configuration data %s" % str(unique_id))
1442 self.driver = unique_id[0]
1443 self.dev_path = unique_id[1]
1446 """Assemble the device.
1448 Checks whether the file device exists, raises BlockDeviceError otherwise.
1451 if not os.path.exists(self.dev_path):
1452 raise errors.BlockDeviceError("File device '%s' does not exist." %
1457 """Shutdown the device.
1459 This is a no-op for the file type, as we don't deacivate
1460 the file on shutdown.
1465 def Open(self, force=False):
1466 """Make the device ready for I/O.
1468 This is a no-op for the file type.
1474 """Notifies that the device will no longer be used for I/O.
1476 This is a no-op for the file type.
1482 """Remove the file backing the block device.
1485 boolean indicating wheter removal of file was successful or not.
1488 if not os.path.exists(self.dev_path):
1491 os.remove(self.dev_path)
1493 except OSError, err:
1494 logger.Error("Can't remove file '%s': %s"
1495 % (self.dev_path, err))
1499 """Attach to an existing file.
1501 Check if this file already exists.
1504 boolean indicating if file exists or not.
1507 if os.path.exists(self.dev_path):
1512 def Create(cls, unique_id, children, size):
1513 """Create a new file.
1517 size: integer size of file in MiB
1520 A ganeti.bdev.FileStorage object.
1523 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1524 raise ValueError("Invalid configuration data %s" % str(unique_id))
1525 dev_path = unique_id[1]
1527 f = open(dev_path, 'w')
1528 except IOError, err:
1529 raise errors.BlockDeviceError("Could not create '%'" % err)
1531 f.truncate(size * 1024 * 1024)
1534 return FileStorage(unique_id, children)
1538 constants.LD_LV: LogicalVolume,
1539 constants.LD_DRBD8: DRBD8,
1540 constants.LD_FILE: FileStorage,
1544 def FindDevice(dev_type, unique_id, children):
1545 """Search for an existing, assembled device.
1547 This will succeed only if the device exists and is assembled, but it
1548 does not do any actions in order to activate the device.
1551 if dev_type not in DEV_MAP:
1552 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1553 device = DEV_MAP[dev_type](unique_id, children)
1554 if not device.Attach():
1559 def AttachOrAssemble(dev_type, unique_id, children):
1560 """Try to attach or assemble an existing device.
1562 This will attach to an existing assembled device or will assemble
1563 the device, as needed, to bring it fully up.
1566 if dev_type not in DEV_MAP:
1567 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1568 device = DEV_MAP[dev_type](unique_id, children)
1569 if not device.Attach():
1571 if not device.Attach():
1572 raise errors.BlockDeviceError("Can't find a valid block device for"
1574 (dev_type, unique_id, children))
1578 def Create(dev_type, unique_id, children, size):
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].Create(unique_id, children, size)