4 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 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"""
31 from ganeti import utils
32 from ganeti import errors
33 from ganeti import constants
34 from ganeti import objects
35 from ganeti import compat
36 from ganeti import pathutils
37 from ganeti import serializer
38 from ganeti.storage import drbd
39 from ganeti.storage import base
42 class RbdShowmappedJsonError(Exception):
43 """`rbd showmmapped' JSON formatting error Exception class.
49 def _CheckResult(result):
50 """Throws an error if the given result is a failed one.
52 @param result: result from RunCmd
56 base.ThrowError("Command: %s error: %s - %s",
57 result.cmd, result.fail_reason, result.output)
60 def _GetForbiddenFileStoragePaths():
61 """Builds a list of path prefixes which shouldn't be used for file storage.
76 for prefix in ["", "/usr", "/usr/local"]:
77 paths.update(map(lambda s: "%s/%s" % (prefix, s),
78 ["bin", "lib", "lib32", "lib64", "sbin"]))
80 return compat.UniqueFrozenset(map(os.path.normpath, paths))
83 def _ComputeWrongFileStoragePaths(paths,
84 _forbidden=_GetForbiddenFileStoragePaths()):
85 """Cross-checks a list of paths for prefixes considered bad.
87 Some paths, e.g. "/bin", should not be used for file storage.
90 @param paths: List of paths to be checked
92 @return: Sorted list of paths for which the user should be warned
96 return (not os.path.isabs(path) or
98 filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
100 return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
103 def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
104 """Returns a list of file storage paths whose prefix is considered bad.
106 See L{_ComputeWrongFileStoragePaths}.
109 return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
112 def _CheckFileStoragePath(path, allowed):
113 """Checks if a path is in a list of allowed paths for file storage.
116 @param path: Path to check
118 @param allowed: List of allowed paths
119 @raise errors.FileStoragePathError: If the path is not allowed
122 if not os.path.isabs(path):
123 raise errors.FileStoragePathError("File storage path must be absolute,"
127 if not os.path.isabs(i):
128 logging.info("Ignoring relative path '%s' for file storage", i)
131 if utils.IsBelowDir(i, path):
134 raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
138 def _LoadAllowedFileStoragePaths(filename):
139 """Loads file containing allowed file storage paths.
142 @return: List of allowed paths (can be an empty list)
146 contents = utils.ReadFile(filename)
147 except EnvironmentError:
150 return utils.FilterEmptyLinesAndComments(contents)
153 def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
154 """Checks if a path is allowed for file storage.
157 @param path: Path to check
158 @raise errors.FileStoragePathError: If the path is not allowed
161 allowed = _LoadAllowedFileStoragePaths(_filename)
163 if _ComputeWrongFileStoragePaths([path]):
164 raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
167 _CheckFileStoragePath(path, allowed)
170 class LogicalVolume(base.BlockDev):
171 """Logical Volume block device.
174 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
175 _PARSE_PV_DEV_RE = re.compile("^([^ ()]+)\([0-9]+\)$")
176 _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
177 _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
179 def __init__(self, unique_id, children, size, params):
180 """Attaches to a LV device.
182 The unique_id is a tuple (vg_name, lv_name)
185 super(LogicalVolume, self).__init__(unique_id, children, size, params)
186 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
187 raise ValueError("Invalid configuration data %s" % str(unique_id))
188 self._vg_name, self._lv_name = unique_id
189 self._ValidateName(self._vg_name)
190 self._ValidateName(self._lv_name)
191 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
192 self._degraded = True
193 self.major = self.minor = self.pe_size = self.stripe_count = None
198 def _GetStdPvSize(pvs_info):
199 """Return the the standard PV size (used with exclusive storage).
201 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
206 assert len(pvs_info) > 0
207 smallest = min([pv.size for pv in pvs_info])
208 return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
211 def _ComputeNumPvs(size, pvs_info):
212 """Compute the number of PVs needed for an LV (with exclusive storage).
215 @param size: LV size in MiB
216 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
218 @return: number of PVs needed
220 assert len(pvs_info) > 0
221 pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
222 return int(math.ceil(float(size) / pv_size))
225 def _GetEmptyPvNames(pvs_info, max_pvs=None):
226 """Return a list of empty PVs, by name.
229 empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
230 if max_pvs is not None:
231 empty_pvs = empty_pvs[:max_pvs]
232 return map((lambda pv: pv.name), empty_pvs)
235 def Create(cls, unique_id, children, size, spindles, params, excl_stor):
236 """Create a new logical volume.
239 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
240 raise errors.ProgrammerError("Invalid configuration data %s" %
242 vg_name, lv_name = unique_id
243 cls._ValidateName(vg_name)
244 cls._ValidateName(lv_name)
245 pvs_info = cls.GetPVInfo([vg_name])
248 msg = "No (empty) PVs found"
250 msg = "Can't compute PV info for vg %s" % vg_name
252 pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
254 pvlist = [pv.name for pv in pvs_info]
255 if compat.any(":" in v for v in pvlist):
256 base.ThrowError("Some of your PVs have the invalid character ':' in their"
257 " name, this is not supported - please filter them out"
258 " in lvm.conf using either 'filter' or 'preferred_names'")
260 current_pvs = len(pvlist)
261 desired_stripes = params[constants.LDP_STRIPES]
262 stripes = min(current_pvs, desired_stripes)
265 (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
269 req_pvs = cls._ComputeNumPvs(size, pvs_info)
271 if spindles < req_pvs:
272 base.ThrowError("Requested number of spindles (%s) is not enough for"
273 " a disk of %d MB (at least %d spindles needed)",
274 spindles, size, req_pvs)
277 pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
278 current_pvs = len(pvlist)
279 if current_pvs < req_pvs:
280 base.ThrowError("Not enough empty PVs (spindles) to create a disk of %d"
281 " MB: %d available, %d needed",
282 size, current_pvs, req_pvs)
283 assert current_pvs == len(pvlist)
284 if stripes > current_pvs:
285 # No warning issued for this, as it's no surprise
286 stripes = current_pvs
289 if stripes < desired_stripes:
290 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
291 " available.", desired_stripes, vg_name, current_pvs)
292 free_size = sum([pv.free for pv in pvs_info])
293 # The size constraint should have been checked from the master before
294 # calling the create function.
296 base.ThrowError("Not enough free space: required %s,"
297 " available %s", size, free_size)
299 # If the free space is not well distributed, we won't be able to
300 # create an optimally-striped volume; in that case, we want to try
301 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
303 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
304 for stripes_arg in range(stripes, 0, -1):
305 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
306 if not result.failed:
309 base.ThrowError("LV create failed (%s): %s",
310 result.fail_reason, result.output)
311 return LogicalVolume(unique_id, children, size, params)
314 def _GetVolumeInfo(lvm_cmd, fields):
315 """Returns LVM Volume infos using lvm_cmd
317 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
318 @param fields: Fields to return
319 @return: A list of dicts each with the parsed fields
323 raise errors.ProgrammerError("No fields specified")
326 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
327 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
329 result = utils.RunCmd(cmd)
331 raise errors.CommandError("Can't get the volume information: %s - %s" %
332 (result.fail_reason, result.output))
335 for line in result.stdout.splitlines():
336 splitted_fields = line.strip().split(sep)
338 if len(fields) != len(splitted_fields):
339 raise errors.CommandError("Can't parse %s output: line '%s'" %
342 data.append(splitted_fields)
347 def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
348 """Get the free space info for PVs in a volume group.
350 @param vg_names: list of volume group names, if empty all will be returned
351 @param filter_allocatable: whether to skip over unallocatable PVs
352 @param include_lvs: whether to include a list of LVs hosted on each PV
355 @return: list of objects.LvmPvInfo objects
358 # We request "lv_name" field only if we care about LVs, so we don't get
359 # a long list of entries with many duplicates unless we really have to.
360 # The duplicate "pv_name" field will be ignored.
366 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
367 "pv_attr", "pv_size", lvfield])
368 except errors.GenericError, err:
369 logging.error("Can't get PV information: %s", err)
372 # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
373 # pair. We sort entries by PV name and then LV name, so it's easy to weed
376 info.sort(key=(lambda i: (i[0], i[5])))
379 for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
380 # (possibly) skip over pvs which are not allocatable
381 if filter_allocatable and pv_attr[0] != "a":
383 # (possibly) skip over pvs which are not in the right volume group(s)
384 if vg_names and vg_name not in vg_names:
386 # Beware of duplicates (check before inserting)
387 if lastpvi and lastpvi.name == pv_name:
388 if include_lvs and lv_name:
389 if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
390 lastpvi.lv_list.append(lv_name)
392 if include_lvs and lv_name:
396 lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
397 size=float(pv_size), free=float(pv_free),
398 attributes=pv_attr, lv_list=lvl)
404 def _GetExclusiveStorageVgFree(cls, vg_name):
405 """Return the free disk space in the given VG, in exclusive storage mode.
407 @type vg_name: string
408 @param vg_name: VG name
410 @return: free space in MiB
412 pvs_info = cls.GetPVInfo([vg_name])
415 pv_size = cls._GetStdPvSize(pvs_info)
416 num_pvs = len(cls._GetEmptyPvNames(pvs_info))
417 return pv_size * num_pvs
420 def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
421 """Get the free space info for specific VGs.
423 @param vg_names: list of volume group names, if empty all will be returned
424 @param excl_stor: whether exclusive_storage is enabled
425 @param filter_readonly: whether to skip over readonly VGs
428 @return: list of tuples (free_space, total_size, name) with free_space in
433 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
435 except errors.GenericError, err:
436 logging.error("Can't get VG information: %s", err)
440 for vg_name, vg_free, vg_attr, vg_size in info:
441 # (possibly) skip over vgs which are not writable
442 if filter_readonly and vg_attr[0] == "r":
444 # (possibly) skip over vgs which are not in the right volume group(s)
445 if vg_names and vg_name not in vg_names:
447 # Exclusive storage needs a different concept of free space
449 es_free = cls._GetExclusiveStorageVgFree(vg_name)
450 assert es_free <= vg_free
452 data.append((float(vg_free), float(vg_size), vg_name))
457 def _ValidateName(cls, name):
458 """Validates that a given name is valid as VG or LV name.
460 The list of valid characters and restricted names is taken out of
461 the lvm(8) manpage, with the simplification that we enforce both
462 VG and LV restrictions on the names.
465 if (not cls._VALID_NAME_RE.match(name) or
466 name in cls._INVALID_NAMES or
467 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
468 base.ThrowError("Invalid LVM name '%s'", name)
471 """Remove this logical volume.
474 if not self.minor and not self.Attach():
475 # the LV does not exist
477 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
478 (self._vg_name, self._lv_name)])
480 base.ThrowError("Can't lvremove: %s - %s",
481 result.fail_reason, result.output)
483 def Rename(self, new_id):
484 """Rename this logical volume.
487 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
488 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
489 new_vg, new_name = new_id
490 if new_vg != self._vg_name:
491 raise errors.ProgrammerError("Can't move a logical volume across"
492 " volume groups (from %s to to %s)" %
493 (self._vg_name, new_vg))
494 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
496 base.ThrowError("Failed to rename the logical volume: %s", result.output)
497 self._lv_name = new_name
498 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
501 def _ParseLvInfoLine(cls, line, sep):
502 """Parse one line of the lvs output used in L{_GetLvInfo}.
505 elems = line.strip().rstrip(sep).split(sep)
507 base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
509 (status, major, minor, pe_size, stripes, pvs) = elems
511 base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
516 except (TypeError, ValueError), err:
517 base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
520 pe_size = int(float(pe_size))
521 except (TypeError, ValueError), err:
522 base.ThrowError("Can't parse vg extent size: %s", err)
525 stripes = int(stripes)
526 except (TypeError, ValueError), err:
527 base.ThrowError("Can't parse the number of stripes: %s", err)
530 for pv in pvs.split(","):
531 m = re.match(cls._PARSE_PV_DEV_RE, pv)
533 base.ThrowError("Can't parse this device list: %s", pvs)
534 pv_names.append(m.group(1))
535 assert len(pv_names) > 0
537 return (status, major, minor, pe_size, stripes, pv_names)
540 def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd):
541 """Get info about the given existing LV to be used.
545 result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
546 "--units=k", "--nosuffix",
547 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
548 "vg_extent_size,stripes,devices", dev_path])
550 base.ThrowError("Can't find LV %s: %s, %s",
551 dev_path, result.fail_reason, result.output)
552 # the output can (and will) have multiple lines for multi-segment
553 # LVs, as the 'stripes' parameter is a segment one, so we take
554 # only the last entry, which is the one we're interested in; note
555 # that with LVM2 anyway the 'stripes' value must be constant
556 # across segments, so this is a no-op actually
557 out = result.stdout.splitlines()
558 if not out: # totally empty result? splitlines() returns at least
559 # one line for any non-empty string
560 base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
563 (status, major, minor, pe_size, stripes, more_pvs) = \
564 cls._ParseLvInfoLine(line, sep)
565 pv_names.update(more_pvs)
566 return (status, major, minor, pe_size, stripes, pv_names)
569 """Attach to an existing LV.
571 This method will try to see if an existing and active LV exists
572 which matches our name. If so, its major/minor will be
576 self.attached = False
578 (status, major, minor, pe_size, stripes, pv_names) = \
579 self._GetLvInfo(self.dev_path)
580 except errors.BlockDeviceError:
585 self.pe_size = pe_size
586 self.stripe_count = stripes
587 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
589 self.pv_names = pv_names
594 """Assemble the device.
596 We always run `lvchange -ay` on the LV to ensure it's active before
597 use, as there were cases when xenvg was not active after boot
598 (also possibly after disk issues).
601 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
603 base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
606 """Shutdown the device.
608 This is a no-op for the LV device type, as we don't deactivate the
614 def GetSyncStatus(self):
615 """Returns the sync status of the device.
617 If this device is a mirroring device, this function returns the
618 status of the mirror.
620 For logical volumes, sync_percent and estimated_time are always
621 None (no recovery in progress, as we don't handle the mirrored LV
622 case). The is_degraded parameter is the inverse of the ldisk
625 For the ldisk parameter, we check if the logical volume has the
626 'virtual' type, which means it's not backed by existing storage
627 anymore (read from it return I/O error). This happens after a
628 physical disk failure and subsequent 'vgreduce --removemissing' on
631 The status was already read in Attach, so we just return it.
633 @rtype: objects.BlockDevStatus
637 ldisk_status = constants.LDS_FAULTY
639 ldisk_status = constants.LDS_OKAY
641 return objects.BlockDevStatus(dev_path=self.dev_path,
646 is_degraded=self._degraded,
647 ldisk_status=ldisk_status)
649 def Open(self, force=False):
650 """Make the device ready for I/O.
652 This is a no-op for the LV device type.
658 """Notifies that the device will no longer be used for I/O.
660 This is a no-op for the LV device type.
665 def Snapshot(self, size):
666 """Create a snapshot copy of an lvm block device.
668 @returns: tuple (vg, lv)
671 snap_name = self._lv_name + ".snap"
673 # remove existing snapshot if found
674 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
675 base.IgnoreError(snap.Remove)
677 vg_info = self.GetVGInfo([self._vg_name], False)
679 base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
680 free_size, _, _ = vg_info[0]
682 base.ThrowError("Not enough free space: required %s,"
683 " available %s", size, free_size)
685 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
686 "-n%s" % snap_name, self.dev_path]))
688 return (self._vg_name, snap_name)
690 def _RemoveOldInfo(self):
691 """Try to remove old tags from the lv.
694 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
698 raw_tags = result.stdout.strip()
700 for tag in raw_tags.split(","):
701 _CheckResult(utils.RunCmd(["lvchange", "--deltag",
702 tag.strip(), self.dev_path]))
704 def SetInfo(self, text):
705 """Update metadata with info text.
708 base.BlockDev.SetInfo(self, text)
710 self._RemoveOldInfo()
712 # Replace invalid characters
713 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
714 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
716 # Only up to 128 characters are allowed
719 _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
721 def Grow(self, amount, dryrun, backingstore):
722 """Grow the logical volume.
727 if self.pe_size is None or self.stripe_count is None:
728 if not self.Attach():
729 base.ThrowError("Can't attach to LV during Grow()")
730 full_stripe_size = self.pe_size * self.stripe_count
733 rest = amount % full_stripe_size
735 amount += full_stripe_size - rest
736 cmd = ["lvextend", "-L", "+%dk" % amount]
739 # we try multiple algorithms since the 'best' ones might not have
740 # space available in the right place, but later ones might (since
741 # they have less constraints); also note that only recent LVM
743 for alloc_policy in "contiguous", "cling", "normal":
744 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
745 if not result.failed:
747 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
750 class FileStorage(base.BlockDev):
753 This class represents the a file storage backend device.
755 The unique_id for the file device is a (file_driver, file_path) tuple.
758 def __init__(self, unique_id, children, size, params):
759 """Initalizes a file device backend.
763 raise errors.BlockDeviceError("Invalid setup for file device")
764 super(FileStorage, self).__init__(unique_id, children, size, params)
765 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
766 raise ValueError("Invalid configuration data %s" % str(unique_id))
767 self.driver = unique_id[0]
768 self.dev_path = unique_id[1]
770 CheckFileStoragePath(self.dev_path)
775 """Assemble the device.
777 Checks whether the file device exists, raises BlockDeviceError otherwise.
780 if not os.path.exists(self.dev_path):
781 base.ThrowError("File device '%s' does not exist" % self.dev_path)
784 """Shutdown the device.
786 This is a no-op for the file type, as we don't deactivate
787 the file on shutdown.
792 def Open(self, force=False):
793 """Make the device ready for I/O.
795 This is a no-op for the file type.
801 """Notifies that the device will no longer be used for I/O.
803 This is a no-op for the file type.
809 """Remove the file backing the block device.
812 @return: True if the removal was successful
816 os.remove(self.dev_path)
818 if err.errno != errno.ENOENT:
819 base.ThrowError("Can't remove file '%s': %s", self.dev_path, err)
821 def Rename(self, new_id):
825 # TODO: implement rename for file-based storage
826 base.ThrowError("Rename is not supported for file-based storage")
828 def Grow(self, amount, dryrun, backingstore):
831 @param amount: the amount (in mebibytes) to grow with
836 # Check that the file exists
838 current_size = self.GetActualSize()
839 new_size = current_size + amount * 1024 * 1024
840 assert new_size > current_size, "Cannot Grow with a negative amount"
841 # We can't really simulate the growth
845 f = open(self.dev_path, "a+")
848 except EnvironmentError, err:
849 base.ThrowError("Error in file growth: %", str(err))
852 """Attach to an existing file.
854 Check if this file already exists.
857 @return: True if file exists
860 self.attached = os.path.exists(self.dev_path)
863 def GetActualSize(self):
864 """Return the actual disk size.
866 @note: the device needs to be active when this is called
869 assert self.attached, "BlockDevice not attached in GetActualSize()"
871 st = os.stat(self.dev_path)
874 base.ThrowError("Can't stat %s: %s", self.dev_path, err)
877 def Create(cls, unique_id, children, size, spindles, params, excl_stor):
878 """Create a new file.
880 @param size: the size of file in MiB
882 @rtype: L{bdev.FileStorage}
883 @return: an instance of FileStorage
887 raise errors.ProgrammerError("FileStorage device requested with"
888 " exclusive_storage")
889 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
890 raise ValueError("Invalid configuration data %s" % str(unique_id))
892 dev_path = unique_id[1]
894 CheckFileStoragePath(dev_path)
897 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
898 f = os.fdopen(fd, "w")
899 f.truncate(size * 1024 * 1024)
901 except EnvironmentError, err:
902 if err.errno == errno.EEXIST:
903 base.ThrowError("File already existing: %s", dev_path)
904 base.ThrowError("Error in file creation: %", str(err))
906 return FileStorage(unique_id, children, size, params)
909 class PersistentBlockDevice(base.BlockDev):
910 """A block device with persistent node
912 May be either directly attached, or exposed through DM (e.g. dm-multipath).
913 udev helpers are probably required to give persistent, human-friendly
916 For the time being, pathnames are required to lie under /dev.
919 def __init__(self, unique_id, children, size, params):
920 """Attaches to a static block device.
922 The unique_id is a path under /dev.
925 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
927 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
928 raise ValueError("Invalid configuration data %s" % str(unique_id))
929 self.dev_path = unique_id[1]
930 if not os.path.realpath(self.dev_path).startswith("/dev/"):
931 raise ValueError("Full path '%s' lies outside /dev" %
932 os.path.realpath(self.dev_path))
933 # TODO: this is just a safety guard checking that we only deal with devices
934 # we know how to handle. In the future this will be integrated with
935 # external storage backends and possible values will probably be collected
936 # from the cluster configuration.
937 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
938 raise ValueError("Got persistent block device of invalid type: %s" %
941 self.major = self.minor = None
945 def Create(cls, unique_id, children, size, spindles, params, excl_stor):
946 """Create a new device
948 This is a noop, we only return a PersistentBlockDevice instance
952 raise errors.ProgrammerError("Persistent block device requested with"
953 " exclusive_storage")
954 return PersistentBlockDevice(unique_id, children, 0, params)
964 def Rename(self, new_id):
965 """Rename this device.
968 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
971 """Attach to an existing block device.
975 self.attached = False
977 st = os.stat(self.dev_path)
979 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
982 if not stat.S_ISBLK(st.st_mode):
983 logging.error("%s is not a block device", self.dev_path)
986 self.major = os.major(st.st_rdev)
987 self.minor = os.minor(st.st_rdev)
993 """Assemble the device.
999 """Shutdown the device.
1004 def Open(self, force=False):
1005 """Make the device ready for I/O.
1011 """Notifies that the device will no longer be used for I/O.
1016 def Grow(self, amount, dryrun, backingstore):
1017 """Grow the logical volume.
1020 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
1023 class RADOSBlockDevice(base.BlockDev):
1024 """A RADOS Block Device (rbd).
1026 This class implements the RADOS Block Device for the backend. You need
1027 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
1028 this to be functional.
1031 def __init__(self, unique_id, children, size, params):
1032 """Attaches to an rbd device.
1035 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
1036 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1037 raise ValueError("Invalid configuration data %s" % str(unique_id))
1039 self.driver, self.rbd_name = unique_id
1041 self.major = self.minor = None
1045 def Create(cls, unique_id, children, size, spindles, params, excl_stor):
1046 """Create a new rbd device.
1048 Provision a new rbd volume inside a RADOS pool.
1051 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1052 raise errors.ProgrammerError("Invalid configuration data %s" %
1055 raise errors.ProgrammerError("RBD device requested with"
1056 " exclusive_storage")
1057 rbd_pool = params[constants.LDP_POOL]
1058 rbd_name = unique_id[1]
1060 # Provision a new rbd volume (Image) inside the RADOS cluster.
1061 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
1062 rbd_name, "--size", "%s" % size]
1063 result = utils.RunCmd(cmd)
1065 base.ThrowError("rbd creation failed (%s): %s",
1066 result.fail_reason, result.output)
1068 return RADOSBlockDevice(unique_id, children, size, params)
1071 """Remove the rbd device.
1074 rbd_pool = self.params[constants.LDP_POOL]
1075 rbd_name = self.unique_id[1]
1077 if not self.minor and not self.Attach():
1078 # The rbd device doesn't exist.
1081 # First shutdown the device (remove mappings).
1084 # Remove the actual Volume (Image) from the RADOS cluster.
1085 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
1086 result = utils.RunCmd(cmd)
1088 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
1089 result.fail_reason, result.output)
1091 def Rename(self, new_id):
1092 """Rename this device.
1098 """Attach to an existing rbd device.
1100 This method maps the rbd volume that matches our name with
1101 an rbd device and then attaches to this device.
1104 self.attached = False
1106 # Map the rbd volume to a block device under /dev
1107 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
1110 st = os.stat(self.dev_path)
1111 except OSError, err:
1112 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
1115 if not stat.S_ISBLK(st.st_mode):
1116 logging.error("%s is not a block device", self.dev_path)
1119 self.major = os.major(st.st_rdev)
1120 self.minor = os.minor(st.st_rdev)
1121 self.attached = True
1125 def _MapVolumeToBlockdev(self, unique_id):
1126 """Maps existing rbd volumes to block devices.
1128 This method should be idempotent if the mapping already exists.
1131 @return: the block device path that corresponds to the volume
1134 pool = self.params[constants.LDP_POOL]
1137 # Check if the mapping already exists.
1138 rbd_dev = self._VolumeToBlockdev(pool, name)
1140 # The mapping exists. Return it.
1143 # The mapping doesn't exist. Create it.
1144 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
1145 result = utils.RunCmd(map_cmd)
1147 base.ThrowError("rbd map failed (%s): %s",
1148 result.fail_reason, result.output)
1150 # Find the corresponding rbd device.
1151 rbd_dev = self._VolumeToBlockdev(pool, name)
1153 base.ThrowError("rbd map succeeded, but could not find the rbd block"
1154 " device in output of showmapped, for volume: %s", name)
1156 # The device was successfully mapped. Return it.
1160 def _VolumeToBlockdev(cls, pool, volume_name):
1161 """Do the 'volume name'-to-'rbd block device' resolving.
1164 @param pool: RADOS pool to use
1165 @type volume_name: string
1166 @param volume_name: the name of the volume whose device we search for
1167 @rtype: string or None
1168 @return: block device path if the volume is mapped, else None
1172 # Newer versions of the rbd tool support json output formatting. Use it
1182 result = utils.RunCmd(showmap_cmd)
1184 logging.error("rbd JSON output formatting returned error (%s): %s,"
1185 "falling back to plain output parsing",
1186 result.fail_reason, result.output)
1187 raise RbdShowmappedJsonError
1189 return cls._ParseRbdShowmappedJson(result.output, volume_name)
1190 except RbdShowmappedJsonError:
1191 # For older versions of rbd, we have to parse the plain / text output
1193 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
1194 result = utils.RunCmd(showmap_cmd)
1196 base.ThrowError("rbd showmapped failed (%s): %s",
1197 result.fail_reason, result.output)
1199 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1202 def _ParseRbdShowmappedJson(output, volume_name):
1203 """Parse the json output of `rbd showmapped'.
1205 This method parses the json output of `rbd showmapped' and returns the rbd
1206 block device path (e.g. /dev/rbd0) that matches the given rbd volume.
1208 @type output: string
1209 @param output: the json output of `rbd showmapped'
1210 @type volume_name: string
1211 @param volume_name: the name of the volume whose device we search for
1212 @rtype: string or None
1213 @return: block device path if the volume is mapped, else None
1217 devices = serializer.LoadJson(output)
1218 except ValueError, err:
1219 base.ThrowError("Unable to parse JSON data: %s" % err)
1222 for d in devices.values(): # pylint: disable=E1103
1226 base.ThrowError("'name' key missing from json object %s", devices)
1228 if name == volume_name:
1229 if rbd_dev is not None:
1230 base.ThrowError("rbd volume %s is mapped more than once", volume_name)
1232 rbd_dev = d["device"]
1237 def _ParseRbdShowmappedPlain(output, volume_name):
1238 """Parse the (plain / text) output of `rbd showmapped'.
1240 This method parses the output of `rbd showmapped' and returns
1241 the rbd block device path (e.g. /dev/rbd0) that matches the
1244 @type output: string
1245 @param output: the plain text output of `rbd showmapped'
1246 @type volume_name: string
1247 @param volume_name: the name of the volume whose device we search for
1248 @rtype: string or None
1249 @return: block device path if the volume is mapped, else None
1256 lines = output.splitlines()
1258 # Try parsing the new output format (ceph >= 0.55).
1259 splitted_lines = map(lambda l: l.split(), lines)
1261 # Check for empty output.
1262 if not splitted_lines:
1265 # Check showmapped output, to determine number of fields.
1266 field_cnt = len(splitted_lines[0])
1267 if field_cnt != allfields:
1268 # Parsing the new format failed. Fallback to parsing the old output
1270 splitted_lines = map(lambda l: l.split("\t"), lines)
1271 if field_cnt != allfields:
1272 base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
1273 " found %s", allfields, field_cnt)
1276 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
1279 if len(matched_lines) > 1:
1280 base.ThrowError("rbd volume %s mapped more than once", volume_name)
1283 # rbd block device found. Return it.
1284 rbd_dev = matched_lines[0][devicefield]
1287 # The given volume is not mapped.
1291 """Assemble the device.
1297 """Shutdown the device.
1300 if not self.minor and not self.Attach():
1301 # The rbd device doesn't exist.
1304 # Unmap the block device from the Volume.
1305 self._UnmapVolumeFromBlockdev(self.unique_id)
1308 self.dev_path = None
1310 def _UnmapVolumeFromBlockdev(self, unique_id):
1311 """Unmaps the rbd device from the Volume it is mapped.
1313 Unmaps the rbd device from the Volume it was previously mapped to.
1314 This method should be idempotent if the Volume isn't mapped.
1317 pool = self.params[constants.LDP_POOL]
1320 # Check if the mapping already exists.
1321 rbd_dev = self._VolumeToBlockdev(pool, name)
1324 # The mapping exists. Unmap the rbd device.
1325 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
1326 result = utils.RunCmd(unmap_cmd)
1328 base.ThrowError("rbd unmap failed (%s): %s",
1329 result.fail_reason, result.output)
1331 def Open(self, force=False):
1332 """Make the device ready for I/O.
1338 """Notifies that the device will no longer be used for I/O.
1343 def Grow(self, amount, dryrun, backingstore):
1346 @type amount: integer
1347 @param amount: the amount (in mebibytes) to grow with
1348 @type dryrun: boolean
1349 @param dryrun: whether to execute the operation in simulation mode
1350 only, without actually increasing the size
1353 if not backingstore:
1355 if not self.Attach():
1356 base.ThrowError("Can't attach to rbd device during Grow()")
1359 # the rbd tool does not support dry runs of resize operations.
1360 # Since rbd volumes are thinly provisioned, we assume
1361 # there is always enough free space for the operation.
1364 rbd_pool = self.params[constants.LDP_POOL]
1365 rbd_name = self.unique_id[1]
1366 new_size = self.size + amount
1368 # Resize the rbd volume (Image) inside the RADOS cluster.
1369 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
1370 rbd_name, "--size", "%s" % new_size]
1371 result = utils.RunCmd(cmd)
1373 base.ThrowError("rbd resize failed (%s): %s",
1374 result.fail_reason, result.output)
1377 class ExtStorageDevice(base.BlockDev):
1378 """A block device provided by an ExtStorage Provider.
1380 This class implements the External Storage Interface, which means
1381 handling of the externally provided block devices.
1384 def __init__(self, unique_id, children, size, params):
1385 """Attaches to an extstorage block device.
1388 super(ExtStorageDevice, self).__init__(unique_id, children, size, params)
1389 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1390 raise ValueError("Invalid configuration data %s" % str(unique_id))
1392 self.driver, self.vol_name = unique_id
1393 self.ext_params = params
1395 self.major = self.minor = None
1399 def Create(cls, unique_id, children, size, spindles, params, excl_stor):
1400 """Create a new extstorage device.
1402 Provision a new volume using an extstorage provider, which will
1403 then be mapped to a block device.
1406 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1407 raise errors.ProgrammerError("Invalid configuration data %s" %
1410 raise errors.ProgrammerError("extstorage device requested with"
1411 " exclusive_storage")
1413 # Call the External Storage's create script,
1414 # to provision a new Volume inside the External Storage
1415 _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
1418 return ExtStorageDevice(unique_id, children, size, params)
1421 """Remove the extstorage device.
1424 if not self.minor and not self.Attach():
1425 # The extstorage device doesn't exist.
1428 # First shutdown the device (remove mappings).
1431 # Call the External Storage's remove script,
1432 # to remove the Volume from the External Storage
1433 _ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id,
1436 def Rename(self, new_id):
1437 """Rename this device.
1443 """Attach to an existing extstorage device.
1445 This method maps the extstorage volume that matches our name with
1446 a corresponding block device and then attaches to this device.
1449 self.attached = False
1451 # Call the External Storage's attach script,
1452 # to attach an existing Volume to a block device under /dev
1453 self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
1454 self.unique_id, self.ext_params)
1457 st = os.stat(self.dev_path)
1458 except OSError, err:
1459 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
1462 if not stat.S_ISBLK(st.st_mode):
1463 logging.error("%s is not a block device", self.dev_path)
1466 self.major = os.major(st.st_rdev)
1467 self.minor = os.minor(st.st_rdev)
1468 self.attached = True
1473 """Assemble the device.
1479 """Shutdown the device.
1482 if not self.minor and not self.Attach():
1483 # The extstorage device doesn't exist.
1486 # Call the External Storage's detach script,
1487 # to detach an existing Volume from it's block device under /dev
1488 _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
1492 self.dev_path = None
1494 def Open(self, force=False):
1495 """Make the device ready for I/O.
1501 """Notifies that the device will no longer be used for I/O.
1506 def Grow(self, amount, dryrun, backingstore):
1509 @type amount: integer
1510 @param amount: the amount (in mebibytes) to grow with
1511 @type dryrun: boolean
1512 @param dryrun: whether to execute the operation in simulation mode
1513 only, without actually increasing the size
1516 if not backingstore:
1518 if not self.Attach():
1519 base.ThrowError("Can't attach to extstorage device during Grow()")
1522 # we do not support dry runs of resize operations for now.
1525 new_size = self.size + amount
1527 # Call the External Storage's grow script,
1528 # to grow an existing Volume inside the External Storage
1529 _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
1530 self.ext_params, str(self.size), grow=str(new_size))
1532 def SetInfo(self, text):
1533 """Update metadata with info text.
1536 # Replace invalid characters
1537 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
1538 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
1540 # Only up to 128 characters are allowed
1543 # Call the External Storage's setinfo script,
1544 # to set metadata for an existing Volume inside the External Storage
1545 _ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id,
1546 self.ext_params, metadata=text)
1549 def _ExtStorageAction(action, unique_id, ext_params,
1550 size=None, grow=None, metadata=None):
1551 """Take an External Storage action.
1553 Take an External Storage action concerning or affecting
1554 a specific Volume inside the External Storage.
1556 @type action: string
1557 @param action: which action to perform. One of:
1558 create / remove / grow / attach / detach
1559 @type unique_id: tuple (driver, vol_name)
1560 @param unique_id: a tuple containing the type of ExtStorage (driver)
1562 @type ext_params: dict
1563 @param ext_params: ExtStorage parameters
1565 @param size: the size of the Volume in mebibytes
1567 @param grow: the new size in mebibytes (after grow)
1568 @type metadata: string
1569 @param metadata: metadata info of the Volume, for use by the provider
1570 @rtype: None or a block device path (during attach)
1573 driver, vol_name = unique_id
1575 # Create an External Storage instance of type `driver'
1576 status, inst_es = ExtStorageFromDisk(driver)
1578 base.ThrowError("%s" % inst_es)
1580 # Create the basic environment for the driver's scripts
1581 create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
1584 # Do not use log file for action `attach' as we need
1585 # to get the output from RunResult
1586 # TODO: find a way to have a log file for attach too
1588 if action is not constants.ES_ACTION_ATTACH:
1589 logfile = _VolumeLogName(action, driver, vol_name)
1591 # Make sure the given action results in a valid script
1592 if action not in constants.ES_SCRIPTS:
1593 base.ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
1596 # Find out which external script to run according the given action
1597 script_name = action + "_script"
1598 script = getattr(inst_es, script_name)
1600 # Run the external script
1601 result = utils.RunCmd([script], env=create_env,
1602 cwd=inst_es.path, output=logfile,)
1604 logging.error("External storage's %s command '%s' returned"
1605 " error: %s, logfile: %s, output: %s",
1606 action, result.cmd, result.fail_reason,
1607 logfile, result.output)
1609 # If logfile is 'None' (during attach), it breaks TailFile
1610 # TODO: have a log file for attach too
1611 if action is not constants.ES_ACTION_ATTACH:
1612 lines = [utils.SafeEncode(val)
1613 for val in utils.TailFile(logfile, lines=20)]
1615 lines = result.output[-20:]
1617 base.ThrowError("External storage's %s script failed (%s), last"
1618 " lines of output:\n%s",
1619 action, result.fail_reason, "\n".join(lines))
1621 if action == constants.ES_ACTION_ATTACH:
1622 return result.stdout
1625 def ExtStorageFromDisk(name, base_dir=None):
1626 """Create an ExtStorage instance from disk.
1628 This function will return an ExtStorage instance
1629 if the given name is a valid ExtStorage name.
1631 @type base_dir: string
1632 @keyword base_dir: Base directory containing ExtStorage installations.
1633 Defaults to a search in all the ES_SEARCH_PATH dirs.
1635 @return: True and the ExtStorage instance if we find a valid one, or
1636 False and the diagnose message on error
1639 if base_dir is None:
1640 es_base_dir = pathutils.ES_SEARCH_PATH
1642 es_base_dir = [base_dir]
1644 es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
1647 return False, ("Directory for External Storage Provider %s not"
1648 " found in search path" % name)
1650 # ES Files dictionary, we will populate it with the absolute path
1651 # names; if the value is True, then it is a required file, otherwise
1653 es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
1655 es_files[constants.ES_PARAMETERS_FILE] = True
1657 for (filename, _) in es_files.items():
1658 es_files[filename] = utils.PathJoin(es_dir, filename)
1661 st = os.stat(es_files[filename])
1662 except EnvironmentError, err:
1663 return False, ("File '%s' under path '%s' is missing (%s)" %
1664 (filename, es_dir, utils.ErrnoOrStr(err)))
1666 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1667 return False, ("File '%s' under path '%s' is not a regular file" %
1670 if filename in constants.ES_SCRIPTS:
1671 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
1672 return False, ("File '%s' under path '%s' is not executable" %
1676 if constants.ES_PARAMETERS_FILE in es_files:
1677 parameters_file = es_files[constants.ES_PARAMETERS_FILE]
1679 parameters = utils.ReadFile(parameters_file).splitlines()
1680 except EnvironmentError, err:
1681 return False, ("Error while reading the EXT parameters file at %s: %s" %
1682 (parameters_file, utils.ErrnoOrStr(err)))
1683 parameters = [v.split(None, 1) for v in parameters]
1686 objects.ExtStorage(name=name, path=es_dir,
1687 create_script=es_files[constants.ES_SCRIPT_CREATE],
1688 remove_script=es_files[constants.ES_SCRIPT_REMOVE],
1689 grow_script=es_files[constants.ES_SCRIPT_GROW],
1690 attach_script=es_files[constants.ES_SCRIPT_ATTACH],
1691 detach_script=es_files[constants.ES_SCRIPT_DETACH],
1692 setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
1693 verify_script=es_files[constants.ES_SCRIPT_VERIFY],
1694 supported_parameters=parameters)
1698 def _ExtStorageEnvironment(unique_id, ext_params,
1699 size=None, grow=None, metadata=None):
1700 """Calculate the environment for an External Storage script.
1702 @type unique_id: tuple (driver, vol_name)
1703 @param unique_id: ExtStorage pool and name of the Volume
1704 @type ext_params: dict
1705 @param ext_params: the EXT parameters
1707 @param size: size of the Volume (in mebibytes)
1709 @param grow: new size of Volume after grow (in mebibytes)
1710 @type metadata: string
1711 @param metadata: metadata info of the Volume
1713 @return: dict of environment variables
1716 vol_name = unique_id[1]
1719 result["VOL_NAME"] = vol_name
1722 for pname, pvalue in ext_params.items():
1723 result["EXTP_%s" % pname.upper()] = str(pvalue)
1725 if size is not None:
1726 result["VOL_SIZE"] = size
1728 if grow is not None:
1729 result["VOL_NEW_SIZE"] = grow
1731 if metadata is not None:
1732 result["VOL_METADATA"] = metadata
1737 def _VolumeLogName(kind, es_name, volume):
1738 """Compute the ExtStorage log filename for a given Volume and operation.
1741 @param kind: the operation type (e.g. create, remove etc.)
1742 @type es_name: string
1743 @param es_name: the ExtStorage name
1744 @type volume: string
1745 @param volume: the name of the Volume inside the External Storage
1748 # Check if the extstorage log dir is a valid dir
1749 if not os.path.isdir(pathutils.LOG_ES_DIR):
1750 base.ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
1752 # TODO: Use tempfile.mkstemp to create unique filename
1753 basename = ("%s-%s-%s-%s.log" %
1754 (kind, es_name, volume, utils.TimestampForFilename()))
1755 return utils.PathJoin(pathutils.LOG_ES_DIR, basename)
1759 constants.LD_LV: LogicalVolume,
1760 constants.LD_DRBD8: drbd.DRBD8Dev,
1761 constants.LD_BLOCKDEV: PersistentBlockDevice,
1762 constants.LD_RBD: RADOSBlockDevice,
1763 constants.LD_EXT: ExtStorageDevice,
1766 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
1767 DEV_MAP[constants.LD_FILE] = FileStorage
1770 def _VerifyDiskType(dev_type):
1771 if dev_type not in DEV_MAP:
1772 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1775 def _VerifyDiskParams(disk):
1776 """Verifies if all disk parameters are set.
1779 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
1781 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
1785 def FindDevice(disk, children):
1786 """Search for an existing, assembled device.
1788 This will succeed only if the device exists and is assembled, but it
1789 does not do any actions in order to activate the device.
1791 @type disk: L{objects.Disk}
1792 @param disk: the disk object to find
1793 @type children: list of L{bdev.BlockDev}
1794 @param children: the list of block devices that are children of the device
1795 represented by the disk parameter
1798 _VerifyDiskType(disk.dev_type)
1799 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
1801 if not device.attached:
1806 def Assemble(disk, children):
1807 """Try to attach or assemble an existing device.
1809 This will attach to assemble the device, as needed, to bring it
1810 fully up. It must be safe to run on already-assembled devices.
1812 @type disk: L{objects.Disk}
1813 @param disk: the disk object to assemble
1814 @type children: list of L{bdev.BlockDev}
1815 @param children: the list of block devices that are children of the device
1816 represented by the disk parameter
1819 _VerifyDiskType(disk.dev_type)
1820 _VerifyDiskParams(disk)
1821 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
1827 def Create(disk, children, excl_stor):
1830 @type disk: L{objects.Disk}
1831 @param disk: the disk object to create
1832 @type children: list of L{bdev.BlockDev}
1833 @param children: the list of block devices that are children of the device
1834 represented by the disk parameter
1835 @type excl_stor: boolean
1836 @param excl_stor: Whether exclusive_storage is active
1837 @rtype: L{bdev.BlockDev}
1838 @return: the created device, or C{None} in case of an error
1841 _VerifyDiskType(disk.dev_type)
1842 _VerifyDiskParams(disk)
1843 device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
1844 disk.spindles, disk.params, excl_stor)