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)
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.
327 vg_name: the volume group name
330 list of (free_space, name) with free_space in mebibytes
333 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
334 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
336 result = utils.RunCmd(command)
338 logger.Error("Can't get the PV information: %s - %s" %
339 (result.fail_reason, result.output))
342 for line in result.stdout.splitlines():
343 fields = line.strip().split(':')
345 logger.Error("Can't parse pvs output: line '%s'" % line)
347 # skip over pvs from another vg or ones which are not allocatable
348 if fields[1] != vg_name or fields[3][0] != 'a':
350 data.append((float(fields[2]), fields[0]))
355 """Remove this logical volume.
358 if not self.minor and not self.Attach():
359 # the LV does not exist
361 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
362 (self._vg_name, self._lv_name)])
364 logger.Error("Can't lvremove: %s - %s" %
365 (result.fail_reason, result.output))
367 return not result.failed
369 def Rename(self, new_id):
370 """Rename this logical volume.
373 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
374 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
375 new_vg, new_name = new_id
376 if new_vg != self._vg_name:
377 raise errors.ProgrammerError("Can't move a logical volume across"
378 " volume groups (from %s to to %s)" %
379 (self._vg_name, new_vg))
380 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
382 raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
384 self._lv_name = new_name
385 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
388 """Attach to an existing LV.
390 This method will try to see if an existing and active LV exists
391 which matches our name. If so, its major/minor will be
395 result = utils.RunCmd(["lvdisplay", self.dev_path])
397 logger.Error("Can't find LV %s: %s, %s" %
398 (self.dev_path, result.fail_reason, result.output))
400 match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
401 for line in result.stdout.splitlines():
402 match_result = match.match(line)
404 self.major = int(match_result.group(1))
405 self.minor = int(match_result.group(2))
410 """Assemble the device.
412 We alway run `lvchange -ay` on the LV to ensure it's active before
413 use, as there were cases when xenvg was not active after boot
414 (also possibly after disk issues).
417 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
419 logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
420 return not result.failed
423 """Shutdown the device.
425 This is a no-op for the LV device type, as we don't deactivate the
431 def GetSyncStatus(self):
432 """Returns the sync status of the device.
434 If this device is a mirroring device, this function returns the
435 status of the mirror.
438 (sync_percent, estimated_time, is_degraded, ldisk)
440 For logical volumes, sync_percent and estimated_time are always
441 None (no recovery in progress, as we don't handle the mirrored LV
442 case). The is_degraded parameter is the inverse of the ldisk
445 For the ldisk parameter, we check if the logical volume has the
446 'virtual' type, which means it's not backed by existing storage
447 anymore (read from it return I/O error). This happens after a
448 physical disk failure and subsequent 'vgreduce --removemissing' on
452 result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
454 logger.Error("Can't display lv: %s - %s" %
455 (result.fail_reason, result.output))
456 return None, None, True, True
457 out = result.stdout.strip()
458 # format: type/permissions/alloc/fixed_minor/state/open
460 logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
461 return None, None, True, True
462 ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
464 return None, None, ldisk, ldisk
466 def Open(self, force=False):
467 """Make the device ready for I/O.
469 This is a no-op for the LV device type.
475 """Notifies that the device will no longer be used for I/O.
477 This is a no-op for the LV device type.
482 def Snapshot(self, size):
483 """Create a snapshot copy of an lvm block device.
486 snap_name = self._lv_name + ".snap"
488 # remove existing snapshot if found
489 snap = LogicalVolume((self._vg_name, snap_name), None)
492 pvs_info = self.GetPVInfo(self._vg_name)
494 raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
498 free_size, pv_name = pvs_info[0]
500 raise errors.BlockDeviceError("Not enough free space: required %s,"
501 " available %s" % (size, free_size))
503 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
504 "-n%s" % snap_name, self.dev_path])
506 raise errors.BlockDeviceError("command: %s error: %s - %s" %
507 (result.cmd, result.fail_reason,
512 def SetInfo(self, text):
513 """Update metadata with info text.
516 BlockDev.SetInfo(self, text)
518 # Replace invalid characters
519 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
520 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
522 # Only up to 128 characters are allowed
525 result = utils.RunCmd(["lvchange", "--addtag", text,
528 raise errors.BlockDeviceError("Command: %s error: %s - %s" %
529 (result.cmd, result.fail_reason,
531 def Grow(self, amount):
532 """Grow the logical volume.
535 # we try multiple algorithms since the 'best' ones might not have
536 # space available in the right place, but later ones might (since
537 # they have less constraints); also note that only recent LVM
539 for alloc_policy in "contiguous", "cling", "normal":
540 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
541 "-L", "+%dm" % amount, self.dev_path])
542 if not result.failed:
544 raise errors.BlockDeviceError("Can't grow LV %s: %s" %
545 (self.dev_path, result.output))
548 class BaseDRBD(BlockDev):
551 This class contains a few bits of common functionality between the
552 0.7 and 8.x versions of DRBD.
555 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
556 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
559 _ST_UNCONFIGURED = "Unconfigured"
560 _ST_WFCONNECTION = "WFConnection"
561 _ST_CONNECTED = "Connected"
565 """Return data from /proc/drbd.
568 stat = open("/proc/drbd", "r")
570 data = stat.read().splitlines()
574 raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
578 def _MassageProcData(data):
579 """Transform the output of _GetProdData into a nicer form.
582 a dictionary of minor: joined lines from /proc/drbd for that minor
585 lmatch = re.compile("^ *([0-9]+):.*$")
587 old_minor = old_line = None
589 lresult = lmatch.match(line)
590 if lresult is not None:
591 if old_minor is not None:
592 results[old_minor] = old_line
593 old_minor = int(lresult.group(1))
596 if old_minor is not None:
597 old_line += " " + line.strip()
599 if old_minor is not None:
600 results[old_minor] = old_line
604 def _GetVersion(cls):
605 """Return the DRBD version.
607 This will return a dict with keys:
613 proto2 (only on drbd > 8.2.X)
616 proc_data = cls._GetProcData()
617 first_line = proc_data[0].strip()
618 version = cls._VERSION_RE.match(first_line)
620 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
623 values = version.groups()
624 retval = {'k_major': int(values[0]),
625 'k_minor': int(values[1]),
626 'k_point': int(values[2]),
627 'api': int(values[3]),
628 'proto': int(values[4]),
630 if values[5] is not None:
631 retval['proto2'] = values[5]
637 """Return the path to a drbd device for a given minor.
640 return "/dev/drbd%d" % minor
643 def _GetUsedDevs(cls):
644 """Compute the list of used DRBD devices.
647 data = cls._GetProcData()
650 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
652 match = valid_line.match(line)
655 minor = int(match.group(1))
656 state = match.group(2)
657 if state == cls._ST_UNCONFIGURED:
659 used_devs[minor] = state, line
663 def _SetFromMinor(self, minor):
664 """Set our parameters based on the given minor.
666 This sets our minor variable and our dev_path.
670 self.minor = self.dev_path = None
673 self.dev_path = self._DevPath(minor)
676 def _CheckMetaSize(meta_device):
677 """Check if the given meta device looks like a valid one.
679 This currently only check the size, which must be around
683 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
685 logger.Error("Failed to get device size: %s - %s" %
686 (result.fail_reason, result.output))
689 sectors = int(result.stdout)
691 logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
693 bytes = sectors * 512
694 if bytes < 128 * 1024 * 1024: # less than 128MiB
695 logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
697 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
698 logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
702 def Rename(self, new_id):
705 This is not supported for drbd devices.
708 raise errors.ProgrammerError("Can't rename a drbd device")
711 class DRBD8(BaseDRBD):
712 """DRBD v8.x block device.
714 This implements the local host part of the DRBD device, i.e. it
715 doesn't do anything to the supposed peer. If you need a fully
716 connected DRBD pair, you need to use this class on both hosts.
718 The unique_id for the drbd device is the (local_ip, local_port,
719 remote_ip, remote_port) tuple, and it must have two children: the
720 data device and the meta_device. The meta device is checked for
721 valid size and is zeroed on create.
727 def __init__(self, unique_id, children):
728 if children and children.count(None) > 0:
730 super(DRBD8, self).__init__(unique_id, children)
731 self.major = self._DRBD_MAJOR
732 version = self._GetVersion()
733 if version['k_major'] != 8 :
734 raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
735 " requested ganeti usage: kernel is"
736 " %s.%s, ganeti wants 8.x" %
737 (version['k_major'], version['k_minor']))
739 if len(children) not in (0, 2):
740 raise ValueError("Invalid configuration data %s" % str(children))
741 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
742 raise ValueError("Invalid configuration data %s" % str(unique_id))
743 self._lhost, self._lport, self._rhost, self._rport = unique_id
747 def _InitMeta(cls, minor, dev_path):
748 """Initialize a meta device.
750 This will not work if the given minor is in use.
753 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
754 "v08", dev_path, "0", "create-md"])
756 raise errors.BlockDeviceError("Can't initialize meta device: %s" %
760 def _FindUnusedMinor(cls):
761 """Find an unused DRBD device.
763 This is specific to 8.x as the minors are allocated dynamically,
764 so non-existing numbers up to a max minor count are actually free.
767 data = cls._GetProcData()
769 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
770 used_line = re.compile("^ *([0-9]+): cs:")
773 match = unused_line.match(line)
775 return int(match.group(1))
776 match = used_line.match(line)
778 minor = int(match.group(1))
779 highest = max(highest, minor)
780 if highest is None: # there are no minors in use at all
782 if highest >= cls._MAX_MINORS:
783 logger.Error("Error: no free drbd minors!")
784 raise errors.BlockDeviceError("Can't find a free DRBD minor")
788 def _IsValidMeta(cls, meta_device):
789 """Check if the given meta device looks like a valid one.
792 minor = cls._FindUnusedMinor()
793 minor_path = cls._DevPath(minor)
794 result = utils.RunCmd(["drbdmeta", minor_path,
795 "v08", meta_device, "0",
798 logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
803 def _GetShowParser(cls):
804 """Return a parser for `drbd show` output.
806 This will either create or return an already-create parser for the
807 output of the command `drbd show`.
810 if cls._PARSE_SHOW is not None:
811 return cls._PARSE_SHOW
814 lbrace = pyp.Literal("{").suppress()
815 rbrace = pyp.Literal("}").suppress()
816 semi = pyp.Literal(";").suppress()
817 # this also converts the value to an int
818 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
820 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
821 defa = pyp.Literal("_is_default").suppress()
822 dbl_quote = pyp.Literal('"').suppress()
824 keyword = pyp.Word(pyp.alphanums + '-')
827 value = pyp.Word(pyp.alphanums + '_-/.:')
828 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
829 addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
831 # meta device, extended syntax
832 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
833 number + pyp.Word(']').suppress())
836 stmt = (~rbrace + keyword + ~lbrace +
837 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
838 pyp.Optional(defa) + semi +
839 pyp.Optional(pyp.restOfLine).suppress())
842 section_name = pyp.Word(pyp.alphas + '_')
843 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
845 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
848 cls._PARSE_SHOW = bnf
853 def _GetShowData(cls, minor):
854 """Return the `drbdsetup show` data for a minor.
857 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
859 logger.Error("Can't display the drbd config: %s - %s" %
860 (result.fail_reason, result.output))
865 def _GetDevInfo(cls, out):
866 """Parse details about a given DRBD minor.
868 This return, if available, the local backing device (as a path)
869 and the local and remote (ip, port) information from a string
870 containing the output of the `drbdsetup show` command as returned
878 bnf = cls._GetShowParser()
882 results = bnf.parseString(out)
883 except pyp.ParseException, err:
884 raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
887 # and massage the results into our desired format
888 for section in results:
890 if sname == "_this_host":
891 for lst in section[1:]:
893 data["local_dev"] = lst[1]
894 elif lst[0] == "meta-disk":
895 data["meta_dev"] = lst[1]
896 data["meta_index"] = lst[2]
897 elif lst[0] == "address":
898 data["local_addr"] = tuple(lst[1:])
899 elif sname == "_remote_host":
900 for lst in section[1:]:
901 if lst[0] == "address":
902 data["remote_addr"] = tuple(lst[1:])
905 def _MatchesLocal(self, info):
906 """Test if our local config matches with an existing device.
908 The parameter should be as returned from `_GetDevInfo()`. This
909 method tests if our local backing device is the same as the one in
910 the info parameter, in effect testing if we look like the given
915 backend, meta = self._children
917 backend = meta = None
919 if backend is not None:
920 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
922 retval = ("local_dev" not in info)
925 retval = retval and ("meta_dev" in info and
926 info["meta_dev"] == meta.dev_path)
927 retval = retval and ("meta_index" in info and
928 info["meta_index"] == 0)
930 retval = retval and ("meta_dev" not in info and
931 "meta_index" not in info)
934 def _MatchesNet(self, info):
935 """Test if our network config matches with an existing device.
937 The parameter should be as returned from `_GetDevInfo()`. This
938 method tests if our network configuration is the same as the one
939 in the info parameter, in effect testing if we look like the given
943 if (((self._lhost is None and not ("local_addr" in info)) and
944 (self._rhost is None and not ("remote_addr" in info)))):
947 if self._lhost is None:
950 if not ("local_addr" in info and
951 "remote_addr" in info):
954 retval = (info["local_addr"] == (self._lhost, self._lport))
956 info["remote_addr"] == (self._rhost, self._rport))
960 def _AssembleLocal(cls, minor, backend, meta):
961 """Configure the local part of a DRBD device.
963 This is the first thing that must be done on an unconfigured DRBD
964 device. And it must be done only once.
967 if not cls._IsValidMeta(meta):
969 args = ["drbdsetup", cls._DevPath(minor), "disk",
970 backend, meta, "0", "-e", "detach", "--create-device"]
971 result = utils.RunCmd(args)
973 logger.Error("Can't attach local disk: %s" % result.output)
974 return not result.failed
977 def _AssembleNet(cls, minor, net_info, protocol,
978 dual_pri=False, hmac=None, secret=None):
979 """Configure the network part of the device.
982 lhost, lport, rhost, rport = net_info
984 # we don't want network connection and actually want to make
986 return cls._ShutdownNet(minor)
988 args = ["drbdsetup", cls._DevPath(minor), "net",
989 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
990 "-A", "discard-zero-changes",
997 args.extend(["-a", hmac, "-x", secret])
998 result = utils.RunCmd(args)
1000 logger.Error("Can't setup network for dbrd device: %s - %s" %
1001 (result.fail_reason, result.output))
1004 timeout = time.time() + 10
1006 while time.time() < timeout:
1007 info = cls._GetDevInfo(cls._GetShowData(minor))
1008 if not "local_addr" in info or not "remote_addr" in info:
1011 if (info["local_addr"] != (lhost, lport) or
1012 info["remote_addr"] != (rhost, rport)):
1018 logger.Error("Timeout while configuring network")
1022 def AddChildren(self, devices):
1023 """Add a disk to the DRBD device.
1026 if self.minor is None:
1027 raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1028 if len(devices) != 2:
1029 raise errors.BlockDeviceError("Need two devices for AddChildren")
1030 info = self._GetDevInfo(self._GetShowData(self.minor))
1031 if "local_dev" in info:
1032 raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1033 backend, meta = devices
1034 if backend.dev_path is None or meta.dev_path is None:
1035 raise errors.BlockDeviceError("Children not ready during AddChildren")
1038 if not self._CheckMetaSize(meta.dev_path):
1039 raise errors.BlockDeviceError("Invalid meta device size")
1040 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1041 if not self._IsValidMeta(meta.dev_path):
1042 raise errors.BlockDeviceError("Cannot initalize meta device")
1044 if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1045 raise errors.BlockDeviceError("Can't attach to local storage")
1046 self._children = devices
1048 def RemoveChildren(self, devices):
1049 """Detach the drbd device from local storage.
1052 if self.minor is None:
1053 raise errors.BlockDeviceError("Can't attach to drbd8 during"
1055 # early return if we don't actually have backing storage
1056 info = self._GetDevInfo(self._GetShowData(self.minor))
1057 if "local_dev" not in info:
1059 if len(self._children) != 2:
1060 raise errors.BlockDeviceError("We don't have two children: %s" %
1062 if self._children.count(None) == 2: # we don't actually have children :)
1063 logger.Error("Requested detach while detached")
1065 if len(devices) != 2:
1066 raise errors.BlockDeviceError("We need two children in RemoveChildren")
1067 for child, dev in zip(self._children, devices):
1068 if dev != child.dev_path:
1069 raise errors.BlockDeviceError("Mismatch in local storage"
1070 " (%s != %s) in RemoveChildren" %
1071 (dev, child.dev_path))
1073 if not self._ShutdownLocal(self.minor):
1074 raise errors.BlockDeviceError("Can't detach from local storage")
1077 def SetSyncSpeed(self, kbytes):
1078 """Set the speed of the DRBD syncer.
1081 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1082 if self.minor is None:
1083 logger.Info("Instance not attached to a device")
1085 result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1088 logger.Error("Can't change syncer rate: %s - %s" %
1089 (result.fail_reason, result.output))
1090 return not result.failed and children_result
1092 def GetSyncStatus(self):
1093 """Returns the sync status of the device.
1096 (sync_percent, estimated_time, is_degraded)
1098 If sync_percent is None, it means all is ok
1099 If estimated_time is None, it means we can't esimate
1100 the time needed, otherwise it's the time left in seconds.
1103 We set the is_degraded parameter to True on two conditions:
1104 network not connected or local disk missing.
1106 We compute the ldisk parameter based on wheter we have a local
1110 if self.minor is None and not self.Attach():
1111 raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1112 proc_info = self._MassageProcData(self._GetProcData())
1113 if self.minor not in proc_info:
1114 raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1116 line = proc_info[self.minor]
1117 match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1118 " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1120 sync_percent = float(match.group(1))
1121 hours = int(match.group(2))
1122 minutes = int(match.group(3))
1123 seconds = int(match.group(4))
1124 est_time = hours * 3600 + minutes * 60 + seconds
1128 match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1130 raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1132 client_state = match.group(1)
1133 local_disk_state = match.group(2)
1134 ldisk = local_disk_state != "UpToDate"
1135 is_degraded = client_state != "Connected"
1136 return sync_percent, est_time, is_degraded or ldisk, ldisk
1138 def Open(self, force=False):
1139 """Make the local state primary.
1141 If the 'force' parameter is given, the '-o' option is passed to
1142 drbdsetup. Since this is a potentially dangerous operation, the
1143 force flag should be only given after creation, when it actually
1147 if self.minor is None and not self.Attach():
1148 logger.Error("DRBD cannot attach to a device during open")
1150 cmd = ["drbdsetup", self.dev_path, "primary"]
1153 result = utils.RunCmd(cmd)
1155 msg = ("Can't make drbd device primary: %s" % result.output)
1157 raise errors.BlockDeviceError(msg)
1160 """Make the local state secondary.
1162 This will, of course, fail if the device is in use.
1165 if self.minor is None and not self.Attach():
1166 logger.Info("Instance not attached to a device")
1167 raise errors.BlockDeviceError("Can't find device")
1168 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1170 msg = ("Can't switch drbd device to"
1171 " secondary: %s" % result.output)
1173 raise errors.BlockDeviceError(msg)
1176 """Find a DRBD device which matches our config and attach to it.
1178 In case of partially attached (local device matches but no network
1179 setup), we perform the network attach. If successful, we re-test
1180 the attach if can return success.
1183 for minor in self._GetUsedDevs():
1184 info = self._GetDevInfo(self._GetShowData(minor))
1185 match_l = self._MatchesLocal(info)
1186 match_r = self._MatchesNet(info)
1187 if match_l and match_r:
1189 if match_l and not match_r and "local_addr" not in info:
1190 res_r = self._AssembleNet(minor,
1191 (self._lhost, self._lport,
1192 self._rhost, self._rport),
1195 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1197 # the weakest case: we find something that is only net attached
1198 # even though we were passed some children at init time
1199 if match_r and "local_dev" not in info:
1202 # this case must be considered only if we actually have local
1203 # storage, i.e. not in diskless mode, because all diskless
1204 # devices are equal from the point of view of local
1206 if (match_l and "local_dev" in info and
1207 not match_r and "local_addr" in info):
1208 # strange case - the device network part points to somewhere
1209 # else, even though its local storage is ours; as we own the
1210 # drbd space, we try to disconnect from the remote peer and
1211 # reconnect to our correct one
1212 if not self._ShutdownNet(minor):
1213 raise errors.BlockDeviceError("Device has correct local storage,"
1214 " wrong remote peer and is unable to"
1215 " disconnect in order to attach to"
1216 " the correct peer")
1217 # note: _AssembleNet also handles the case when we don't want
1218 # local storage (i.e. one or more of the _[lr](host|port) is
1220 if (self._AssembleNet(minor, (self._lhost, self._lport,
1221 self._rhost, self._rport), "C") and
1222 self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1228 self._SetFromMinor(minor)
1229 return minor is not None
1232 """Assemble the drbd.
1235 - if we have a local backing device, we bind to it by:
1236 - checking the list of used drbd devices
1237 - check if the local minor use of any of them is our own device
1240 - if we have a local/remote net info:
1241 - redo the local backing device step for the remote device
1242 - check if any drbd device is using the local port,
1244 - check if any remote drbd device is using the remote
1245 port, if yes abort (for now)
1247 - bind the remote net port
1251 if self.minor is not None:
1252 logger.Info("Already assembled")
1255 result = super(DRBD8, self).Assemble()
1259 minor = self._FindUnusedMinor()
1260 need_localdev_teardown = False
1261 if self._children and self._children[0] and self._children[1]:
1262 result = self._AssembleLocal(minor, self._children[0].dev_path,
1263 self._children[1].dev_path)
1266 need_localdev_teardown = True
1267 if self._lhost and self._lport and self._rhost and self._rport:
1268 result = self._AssembleNet(minor,
1269 (self._lhost, self._lport,
1270 self._rhost, self._rport),
1273 if need_localdev_teardown:
1274 # we will ignore failures from this
1275 logger.Error("net setup failed, tearing down local device")
1276 self._ShutdownAll(minor)
1278 self._SetFromMinor(minor)
1282 def _ShutdownLocal(cls, minor):
1283 """Detach from the local device.
1285 I/Os will continue to be served from the remote device. If we
1286 don't have a remote device, this operation will fail.
1289 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1291 logger.Error("Can't detach local device: %s" % result.output)
1292 return not result.failed
1295 def _ShutdownNet(cls, minor):
1296 """Disconnect from the remote peer.
1298 This fails if we don't have a local device.
1301 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1303 logger.Error("Can't shutdown network: %s" % result.output)
1304 return not result.failed
1307 def _ShutdownAll(cls, minor):
1308 """Deactivate the device.
1310 This will, of course, fail if the device is in use.
1313 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1315 logger.Error("Can't shutdown drbd device: %s" % result.output)
1316 return not result.failed
1319 """Shutdown the DRBD device.
1322 if self.minor is None and not self.Attach():
1323 logger.Info("DRBD device not attached to a device during Shutdown")
1325 if not self._ShutdownAll(self.minor):
1328 self.dev_path = None
1332 """Stub remove for DRBD devices.
1335 return self.Shutdown()
1338 def Create(cls, unique_id, children, size):
1339 """Create a new DRBD8 device.
1341 Since DRBD devices are not created per se, just assembled, this
1342 function only initializes the metadata.
1345 if len(children) != 2:
1346 raise errors.ProgrammerError("Invalid setup for the drbd device")
1349 if not meta.Attach():
1350 raise errors.BlockDeviceError("Can't attach to meta device")
1351 if not cls._CheckMetaSize(meta.dev_path):
1352 raise errors.BlockDeviceError("Invalid meta device size")
1353 cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1354 if not cls._IsValidMeta(meta.dev_path):
1355 raise errors.BlockDeviceError("Cannot initalize meta device")
1356 return cls(unique_id, children)
1358 def Grow(self, amount):
1359 """Resize the DRBD device and its backing storage.
1362 if self.minor is None:
1363 raise errors.ProgrammerError("drbd8: Grow called while not attached")
1364 if len(self._children) != 2 or None in self._children:
1365 raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1366 self._children[0].Grow(amount)
1367 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1369 raise errors.BlockDeviceError("resize failed for %s: %s" %
1370 (self.dev_path, result.output))
1374 class FileStorage(BlockDev):
1377 This class represents the a file storage backend device.
1379 The unique_id for the file device is a (file_driver, file_path) tuple.
1382 def __init__(self, unique_id, children):
1383 """Initalizes a file device backend.
1387 raise errors.BlockDeviceError("Invalid setup for file device")
1388 super(FileStorage, self).__init__(unique_id, children)
1389 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1390 raise ValueError("Invalid configuration data %s" % str(unique_id))
1391 self.driver = unique_id[0]
1392 self.dev_path = unique_id[1]
1395 """Assemble the device.
1397 Checks whether the file device exists, raises BlockDeviceError otherwise.
1400 if not os.path.exists(self.dev_path):
1401 raise errors.BlockDeviceError("File device '%s' does not exist." %
1406 """Shutdown the device.
1408 This is a no-op for the file type, as we don't deacivate
1409 the file on shutdown.
1414 def Open(self, force=False):
1415 """Make the device ready for I/O.
1417 This is a no-op for the file type.
1423 """Notifies that the device will no longer be used for I/O.
1425 This is a no-op for the file type.
1431 """Remove the file backing the block device.
1434 boolean indicating wheter removal of file was successful or not.
1437 if not os.path.exists(self.dev_path):
1440 os.remove(self.dev_path)
1442 except OSError, err:
1443 logger.Error("Can't remove file '%s': %s"
1444 % (self.dev_path, err))
1448 """Attach to an existing file.
1450 Check if this file already exists.
1453 boolean indicating if file exists or not.
1456 if os.path.exists(self.dev_path):
1461 def Create(cls, unique_id, children, size):
1462 """Create a new file.
1466 size: integer size of file in MiB
1469 A ganeti.bdev.FileStorage object.
1472 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1473 raise ValueError("Invalid configuration data %s" % str(unique_id))
1474 dev_path = unique_id[1]
1476 f = open(dev_path, 'w')
1477 except IOError, err:
1478 raise errors.BlockDeviceError("Could not create '%'" % err)
1480 f.truncate(size * 1024 * 1024)
1483 return FileStorage(unique_id, children)
1487 constants.LD_LV: LogicalVolume,
1488 constants.LD_DRBD8: DRBD8,
1489 constants.LD_FILE: FileStorage,
1493 def FindDevice(dev_type, unique_id, children):
1494 """Search for an existing, assembled device.
1496 This will succeed only if the device exists and is assembled, but it
1497 does not do any actions in order to activate the device.
1500 if dev_type not in DEV_MAP:
1501 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1502 device = DEV_MAP[dev_type](unique_id, children)
1503 if not device.Attach():
1508 def AttachOrAssemble(dev_type, unique_id, children):
1509 """Try to attach or assemble an existing device.
1511 This will attach to an existing assembled device or will assemble
1512 the device, as needed, to bring it fully up.
1515 if dev_type not in DEV_MAP:
1516 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1517 device = DEV_MAP[dev_type](unique_id, children)
1518 if not device.Attach():
1520 if not device.Attach():
1521 raise errors.BlockDeviceError("Can't find a valid block device for"
1523 (dev_type, unique_id, children))
1527 def Create(dev_type, unique_id, children, size):
1531 if dev_type not in DEV_MAP:
1532 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1533 device = DEV_MAP[dev_type].Create(unique_id, children, size)