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.block import drbd
39 from ganeti.block 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 _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
176 _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
178 def __init__(self, unique_id, children, size, params):
179 """Attaches to a LV device.
181 The unique_id is a tuple (vg_name, lv_name)
184 super(LogicalVolume, self).__init__(unique_id, children, size, params)
185 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
186 raise ValueError("Invalid configuration data %s" % str(unique_id))
187 self._vg_name, self._lv_name = unique_id
188 self._ValidateName(self._vg_name)
189 self._ValidateName(self._lv_name)
190 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
191 self._degraded = True
192 self.major = self.minor = self.pe_size = self.stripe_count = None
196 def _GetStdPvSize(pvs_info):
197 """Return the the standard PV size (used with exclusive storage).
199 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
204 assert len(pvs_info) > 0
205 smallest = min([pv.size for pv in pvs_info])
206 return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
209 def _ComputeNumPvs(size, pvs_info):
210 """Compute the number of PVs needed for an LV (with exclusive storage).
213 @param size: LV size in MiB
214 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
216 @return: number of PVs needed
218 assert len(pvs_info) > 0
219 pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
220 return int(math.ceil(float(size) / pv_size))
223 def _GetEmptyPvNames(pvs_info, max_pvs=None):
224 """Return a list of empty PVs, by name.
227 empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
228 if max_pvs is not None:
229 empty_pvs = empty_pvs[:max_pvs]
230 return map((lambda pv: pv.name), empty_pvs)
233 def Create(cls, unique_id, children, size, params, excl_stor):
234 """Create a new logical volume.
237 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
238 raise errors.ProgrammerError("Invalid configuration data %s" %
240 vg_name, lv_name = unique_id
241 cls._ValidateName(vg_name)
242 cls._ValidateName(lv_name)
243 pvs_info = cls.GetPVInfo([vg_name])
246 msg = "No (empty) PVs found"
248 msg = "Can't compute PV info for vg %s" % vg_name
250 pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
252 pvlist = [pv.name for pv in pvs_info]
253 if compat.any(":" in v for v in pvlist):
254 base.ThrowError("Some of your PVs have the invalid character ':' in their"
255 " name, this is not supported - please filter them out"
256 " in lvm.conf using either 'filter' or 'preferred_names'")
258 current_pvs = len(pvlist)
259 desired_stripes = params[constants.LDP_STRIPES]
260 stripes = min(current_pvs, desired_stripes)
263 (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
267 req_pvs = cls._ComputeNumPvs(size, pvs_info)
268 pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
269 current_pvs = len(pvlist)
270 if current_pvs < req_pvs:
271 base.ThrowError("Not enough empty PVs to create a disk of %d MB:"
272 " %d available, %d needed", size, current_pvs, req_pvs)
273 assert current_pvs == len(pvlist)
274 if stripes > current_pvs:
275 # No warning issued for this, as it's no surprise
276 stripes = current_pvs
279 if stripes < desired_stripes:
280 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
281 " available.", desired_stripes, vg_name, current_pvs)
282 free_size = sum([pv.free for pv in pvs_info])
283 # The size constraint should have been checked from the master before
284 # calling the create function.
286 base.ThrowError("Not enough free space: required %s,"
287 " available %s", size, free_size)
289 # If the free space is not well distributed, we won't be able to
290 # create an optimally-striped volume; in that case, we want to try
291 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
293 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
294 for stripes_arg in range(stripes, 0, -1):
295 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
296 if not result.failed:
299 base.ThrowError("LV create failed (%s): %s",
300 result.fail_reason, result.output)
301 return LogicalVolume(unique_id, children, size, params)
304 def _GetVolumeInfo(lvm_cmd, fields):
305 """Returns LVM Volume infos using lvm_cmd
307 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
308 @param fields: Fields to return
309 @return: A list of dicts each with the parsed fields
313 raise errors.ProgrammerError("No fields specified")
316 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
317 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
319 result = utils.RunCmd(cmd)
321 raise errors.CommandError("Can't get the volume information: %s - %s" %
322 (result.fail_reason, result.output))
325 for line in result.stdout.splitlines():
326 splitted_fields = line.strip().split(sep)
328 if len(fields) != len(splitted_fields):
329 raise errors.CommandError("Can't parse %s output: line '%s'" %
332 data.append(splitted_fields)
337 def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
338 """Get the free space info for PVs in a volume group.
340 @param vg_names: list of volume group names, if empty all will be returned
341 @param filter_allocatable: whether to skip over unallocatable PVs
342 @param include_lvs: whether to include a list of LVs hosted on each PV
345 @return: list of objects.LvmPvInfo objects
348 # We request "lv_name" field only if we care about LVs, so we don't get
349 # a long list of entries with many duplicates unless we really have to.
350 # The duplicate "pv_name" field will be ignored.
356 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
357 "pv_attr", "pv_size", lvfield])
358 except errors.GenericError, err:
359 logging.error("Can't get PV information: %s", err)
362 # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
363 # pair. We sort entries by PV name and then LV name, so it's easy to weed
366 info.sort(key=(lambda i: (i[0], i[5])))
369 for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
370 # (possibly) skip over pvs which are not allocatable
371 if filter_allocatable and pv_attr[0] != "a":
373 # (possibly) skip over pvs which are not in the right volume group(s)
374 if vg_names and vg_name not in vg_names:
376 # Beware of duplicates (check before inserting)
377 if lastpvi and lastpvi.name == pv_name:
378 if include_lvs and lv_name:
379 if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
380 lastpvi.lv_list.append(lv_name)
382 if include_lvs and lv_name:
386 lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
387 size=float(pv_size), free=float(pv_free),
388 attributes=pv_attr, lv_list=lvl)
394 def _GetExclusiveStorageVgFree(cls, vg_name):
395 """Return the free disk space in the given VG, in exclusive storage mode.
397 @type vg_name: string
398 @param vg_name: VG name
400 @return: free space in MiB
402 pvs_info = cls.GetPVInfo([vg_name])
405 pv_size = cls._GetStdPvSize(pvs_info)
406 num_pvs = len(cls._GetEmptyPvNames(pvs_info))
407 return pv_size * num_pvs
410 def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
411 """Get the free space info for specific VGs.
413 @param vg_names: list of volume group names, if empty all will be returned
414 @param excl_stor: whether exclusive_storage is enabled
415 @param filter_readonly: whether to skip over readonly VGs
418 @return: list of tuples (free_space, total_size, name) with free_space in
423 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
425 except errors.GenericError, err:
426 logging.error("Can't get VG information: %s", err)
430 for vg_name, vg_free, vg_attr, vg_size in info:
431 # (possibly) skip over vgs which are not writable
432 if filter_readonly and vg_attr[0] == "r":
434 # (possibly) skip over vgs which are not in the right volume group(s)
435 if vg_names and vg_name not in vg_names:
437 # Exclusive storage needs a different concept of free space
439 es_free = cls._GetExclusiveStorageVgFree(vg_name)
440 assert es_free <= vg_free
442 data.append((float(vg_free), float(vg_size), vg_name))
447 def _ValidateName(cls, name):
448 """Validates that a given name is valid as VG or LV name.
450 The list of valid characters and restricted names is taken out of
451 the lvm(8) manpage, with the simplification that we enforce both
452 VG and LV restrictions on the names.
455 if (not cls._VALID_NAME_RE.match(name) or
456 name in cls._INVALID_NAMES or
457 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
458 base.ThrowError("Invalid LVM name '%s'", name)
461 """Remove this logical volume.
464 if not self.minor and not self.Attach():
465 # the LV does not exist
467 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
468 (self._vg_name, self._lv_name)])
470 base.ThrowError("Can't lvremove: %s - %s",
471 result.fail_reason, result.output)
473 def Rename(self, new_id):
474 """Rename this logical volume.
477 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
478 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
479 new_vg, new_name = new_id
480 if new_vg != self._vg_name:
481 raise errors.ProgrammerError("Can't move a logical volume across"
482 " volume groups (from %s to to %s)" %
483 (self._vg_name, new_vg))
484 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
486 base.ThrowError("Failed to rename the logical volume: %s", result.output)
487 self._lv_name = new_name
488 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
491 """Attach to an existing LV.
493 This method will try to see if an existing and active LV exists
494 which matches our name. If so, its major/minor will be
498 self.attached = False
499 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
500 "--units=k", "--nosuffix",
501 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
502 "vg_extent_size,stripes", self.dev_path])
504 logging.error("Can't find LV %s: %s, %s",
505 self.dev_path, result.fail_reason, result.output)
507 # the output can (and will) have multiple lines for multi-segment
508 # LVs, as the 'stripes' parameter is a segment one, so we take
509 # only the last entry, which is the one we're interested in; note
510 # that with LVM2 anyway the 'stripes' value must be constant
511 # across segments, so this is a no-op actually
512 out = result.stdout.splitlines()
513 if not out: # totally empty result? splitlines() returns at least
514 # one line for any non-empty string
515 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
517 out = out[-1].strip().rstrip(",")
520 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
523 status, major, minor, pe_size, stripes = out
525 logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
531 except (TypeError, ValueError), err:
532 logging.error("lvs major/minor cannot be parsed: %s", str(err))
535 pe_size = int(float(pe_size))
536 except (TypeError, ValueError), err:
537 logging.error("Can't parse vg extent size: %s", err)
541 stripes = int(stripes)
542 except (TypeError, ValueError), err:
543 logging.error("Can't parse the number of stripes: %s", err)
548 self.pe_size = pe_size
549 self.stripe_count = stripes
550 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
556 """Assemble the device.
558 We always run `lvchange -ay` on the LV to ensure it's active before
559 use, as there were cases when xenvg was not active after boot
560 (also possibly after disk issues).
563 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
565 base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
568 """Shutdown the device.
570 This is a no-op for the LV device type, as we don't deactivate the
576 def GetSyncStatus(self):
577 """Returns the sync status of the device.
579 If this device is a mirroring device, this function returns the
580 status of the mirror.
582 For logical volumes, sync_percent and estimated_time are always
583 None (no recovery in progress, as we don't handle the mirrored LV
584 case). The is_degraded parameter is the inverse of the ldisk
587 For the ldisk parameter, we check if the logical volume has the
588 'virtual' type, which means it's not backed by existing storage
589 anymore (read from it return I/O error). This happens after a
590 physical disk failure and subsequent 'vgreduce --removemissing' on
593 The status was already read in Attach, so we just return it.
595 @rtype: objects.BlockDevStatus
599 ldisk_status = constants.LDS_FAULTY
601 ldisk_status = constants.LDS_OKAY
603 return objects.BlockDevStatus(dev_path=self.dev_path,
608 is_degraded=self._degraded,
609 ldisk_status=ldisk_status)
611 def Open(self, force=False):
612 """Make the device ready for I/O.
614 This is a no-op for the LV device type.
620 """Notifies that the device will no longer be used for I/O.
622 This is a no-op for the LV device type.
627 def Snapshot(self, size):
628 """Create a snapshot copy of an lvm block device.
630 @returns: tuple (vg, lv)
633 snap_name = self._lv_name + ".snap"
635 # remove existing snapshot if found
636 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
637 base.IgnoreError(snap.Remove)
639 vg_info = self.GetVGInfo([self._vg_name], False)
641 base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
642 free_size, _, _ = vg_info[0]
644 base.ThrowError("Not enough free space: required %s,"
645 " available %s", size, free_size)
647 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
648 "-n%s" % snap_name, self.dev_path]))
650 return (self._vg_name, snap_name)
652 def _RemoveOldInfo(self):
653 """Try to remove old tags from the lv.
656 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
660 raw_tags = result.stdout.strip()
662 for tag in raw_tags.split(","):
663 _CheckResult(utils.RunCmd(["lvchange", "--deltag",
664 tag.strip(), self.dev_path]))
666 def SetInfo(self, text):
667 """Update metadata with info text.
670 base.BlockDev.SetInfo(self, text)
672 self._RemoveOldInfo()
674 # Replace invalid characters
675 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
676 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
678 # Only up to 128 characters are allowed
681 _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
683 def Grow(self, amount, dryrun, backingstore):
684 """Grow the logical volume.
689 if self.pe_size is None or self.stripe_count is None:
690 if not self.Attach():
691 base.ThrowError("Can't attach to LV during Grow()")
692 full_stripe_size = self.pe_size * self.stripe_count
695 rest = amount % full_stripe_size
697 amount += full_stripe_size - rest
698 cmd = ["lvextend", "-L", "+%dk" % amount]
701 # we try multiple algorithms since the 'best' ones might not have
702 # space available in the right place, but later ones might (since
703 # they have less constraints); also note that only recent LVM
705 for alloc_policy in "contiguous", "cling", "normal":
706 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
707 if not result.failed:
709 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
712 class FileStorage(base.BlockDev):
715 This class represents the a file storage backend device.
717 The unique_id for the file device is a (file_driver, file_path) tuple.
720 def __init__(self, unique_id, children, size, params):
721 """Initalizes a file device backend.
725 raise errors.BlockDeviceError("Invalid setup for file device")
726 super(FileStorage, self).__init__(unique_id, children, size, params)
727 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
728 raise ValueError("Invalid configuration data %s" % str(unique_id))
729 self.driver = unique_id[0]
730 self.dev_path = unique_id[1]
732 CheckFileStoragePath(self.dev_path)
737 """Assemble the device.
739 Checks whether the file device exists, raises BlockDeviceError otherwise.
742 if not os.path.exists(self.dev_path):
743 base.ThrowError("File device '%s' does not exist" % self.dev_path)
746 """Shutdown the device.
748 This is a no-op for the file type, as we don't deactivate
749 the file on shutdown.
754 def Open(self, force=False):
755 """Make the device ready for I/O.
757 This is a no-op for the file type.
763 """Notifies that the device will no longer be used for I/O.
765 This is a no-op for the file type.
771 """Remove the file backing the block device.
774 @return: True if the removal was successful
778 os.remove(self.dev_path)
780 if err.errno != errno.ENOENT:
781 base.ThrowError("Can't remove file '%s': %s", self.dev_path, err)
783 def Rename(self, new_id):
787 # TODO: implement rename for file-based storage
788 base.ThrowError("Rename is not supported for file-based storage")
790 def Grow(self, amount, dryrun, backingstore):
793 @param amount: the amount (in mebibytes) to grow with
798 # Check that the file exists
800 current_size = self.GetActualSize()
801 new_size = current_size + amount * 1024 * 1024
802 assert new_size > current_size, "Cannot Grow with a negative amount"
803 # We can't really simulate the growth
807 f = open(self.dev_path, "a+")
810 except EnvironmentError, err:
811 base.ThrowError("Error in file growth: %", str(err))
814 """Attach to an existing file.
816 Check if this file already exists.
819 @return: True if file exists
822 self.attached = os.path.exists(self.dev_path)
825 def GetActualSize(self):
826 """Return the actual disk size.
828 @note: the device needs to be active when this is called
831 assert self.attached, "BlockDevice not attached in GetActualSize()"
833 st = os.stat(self.dev_path)
836 base.ThrowError("Can't stat %s: %s", self.dev_path, err)
839 def Create(cls, unique_id, children, size, params, excl_stor):
840 """Create a new file.
842 @param size: the size of file in MiB
844 @rtype: L{bdev.FileStorage}
845 @return: an instance of FileStorage
849 raise errors.ProgrammerError("FileStorage device requested with"
850 " exclusive_storage")
851 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
852 raise ValueError("Invalid configuration data %s" % str(unique_id))
854 dev_path = unique_id[1]
856 CheckFileStoragePath(dev_path)
859 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
860 f = os.fdopen(fd, "w")
861 f.truncate(size * 1024 * 1024)
863 except EnvironmentError, err:
864 if err.errno == errno.EEXIST:
865 base.ThrowError("File already existing: %s", dev_path)
866 base.ThrowError("Error in file creation: %", str(err))
868 return FileStorage(unique_id, children, size, params)
871 class PersistentBlockDevice(base.BlockDev):
872 """A block device with persistent node
874 May be either directly attached, or exposed through DM (e.g. dm-multipath).
875 udev helpers are probably required to give persistent, human-friendly
878 For the time being, pathnames are required to lie under /dev.
881 def __init__(self, unique_id, children, size, params):
882 """Attaches to a static block device.
884 The unique_id is a path under /dev.
887 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
889 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
890 raise ValueError("Invalid configuration data %s" % str(unique_id))
891 self.dev_path = unique_id[1]
892 if not os.path.realpath(self.dev_path).startswith("/dev/"):
893 raise ValueError("Full path '%s' lies outside /dev" %
894 os.path.realpath(self.dev_path))
895 # TODO: this is just a safety guard checking that we only deal with devices
896 # we know how to handle. In the future this will be integrated with
897 # external storage backends and possible values will probably be collected
898 # from the cluster configuration.
899 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
900 raise ValueError("Got persistent block device of invalid type: %s" %
903 self.major = self.minor = None
907 def Create(cls, unique_id, children, size, params, excl_stor):
908 """Create a new device
910 This is a noop, we only return a PersistentBlockDevice instance
914 raise errors.ProgrammerError("Persistent block device requested with"
915 " exclusive_storage")
916 return PersistentBlockDevice(unique_id, children, 0, params)
926 def Rename(self, new_id):
927 """Rename this device.
930 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
933 """Attach to an existing block device.
937 self.attached = False
939 st = os.stat(self.dev_path)
941 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
944 if not stat.S_ISBLK(st.st_mode):
945 logging.error("%s is not a block device", self.dev_path)
948 self.major = os.major(st.st_rdev)
949 self.minor = os.minor(st.st_rdev)
955 """Assemble the device.
961 """Shutdown the device.
966 def Open(self, force=False):
967 """Make the device ready for I/O.
973 """Notifies that the device will no longer be used for I/O.
978 def Grow(self, amount, dryrun, backingstore):
979 """Grow the logical volume.
982 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
985 class RADOSBlockDevice(base.BlockDev):
986 """A RADOS Block Device (rbd).
988 This class implements the RADOS Block Device for the backend. You need
989 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
990 this to be functional.
993 def __init__(self, unique_id, children, size, params):
994 """Attaches to an rbd device.
997 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
998 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
999 raise ValueError("Invalid configuration data %s" % str(unique_id))
1001 self.driver, self.rbd_name = unique_id
1003 self.major = self.minor = None
1007 def Create(cls, unique_id, children, size, params, excl_stor):
1008 """Create a new rbd device.
1010 Provision a new rbd volume inside a RADOS pool.
1013 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1014 raise errors.ProgrammerError("Invalid configuration data %s" %
1017 raise errors.ProgrammerError("RBD device requested with"
1018 " exclusive_storage")
1019 rbd_pool = params[constants.LDP_POOL]
1020 rbd_name = unique_id[1]
1022 # Provision a new rbd volume (Image) inside the RADOS cluster.
1023 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
1024 rbd_name, "--size", "%s" % size]
1025 result = utils.RunCmd(cmd)
1027 base.ThrowError("rbd creation failed (%s): %s",
1028 result.fail_reason, result.output)
1030 return RADOSBlockDevice(unique_id, children, size, params)
1033 """Remove the rbd device.
1036 rbd_pool = self.params[constants.LDP_POOL]
1037 rbd_name = self.unique_id[1]
1039 if not self.minor and not self.Attach():
1040 # The rbd device doesn't exist.
1043 # First shutdown the device (remove mappings).
1046 # Remove the actual Volume (Image) from the RADOS cluster.
1047 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
1048 result = utils.RunCmd(cmd)
1050 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
1051 result.fail_reason, result.output)
1053 def Rename(self, new_id):
1054 """Rename this device.
1060 """Attach to an existing rbd device.
1062 This method maps the rbd volume that matches our name with
1063 an rbd device and then attaches to this device.
1066 self.attached = False
1068 # Map the rbd volume to a block device under /dev
1069 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
1072 st = os.stat(self.dev_path)
1073 except OSError, err:
1074 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
1077 if not stat.S_ISBLK(st.st_mode):
1078 logging.error("%s is not a block device", self.dev_path)
1081 self.major = os.major(st.st_rdev)
1082 self.minor = os.minor(st.st_rdev)
1083 self.attached = True
1087 def _MapVolumeToBlockdev(self, unique_id):
1088 """Maps existing rbd volumes to block devices.
1090 This method should be idempotent if the mapping already exists.
1093 @return: the block device path that corresponds to the volume
1096 pool = self.params[constants.LDP_POOL]
1099 # Check if the mapping already exists.
1100 rbd_dev = self._VolumeToBlockdev(pool, name)
1102 # The mapping exists. Return it.
1105 # The mapping doesn't exist. Create it.
1106 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
1107 result = utils.RunCmd(map_cmd)
1109 base.ThrowError("rbd map failed (%s): %s",
1110 result.fail_reason, result.output)
1112 # Find the corresponding rbd device.
1113 rbd_dev = self._VolumeToBlockdev(pool, name)
1115 base.ThrowError("rbd map succeeded, but could not find the rbd block"
1116 " device in output of showmapped, for volume: %s", name)
1118 # The device was successfully mapped. Return it.
1122 def _VolumeToBlockdev(cls, pool, volume_name):
1123 """Do the 'volume name'-to-'rbd block device' resolving.
1126 @param pool: RADOS pool to use
1127 @type volume_name: string
1128 @param volume_name: the name of the volume whose device we search for
1129 @rtype: string or None
1130 @return: block device path if the volume is mapped, else None
1134 # Newer versions of the rbd tool support json output formatting. Use it
1144 result = utils.RunCmd(showmap_cmd)
1146 logging.error("rbd JSON output formatting returned error (%s): %s,"
1147 "falling back to plain output parsing",
1148 result.fail_reason, result.output)
1149 raise RbdShowmappedJsonError
1151 return cls._ParseRbdShowmappedJson(result.output, volume_name)
1152 except RbdShowmappedJsonError:
1153 # For older versions of rbd, we have to parse the plain / text output
1155 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
1156 result = utils.RunCmd(showmap_cmd)
1158 base.ThrowError("rbd showmapped failed (%s): %s",
1159 result.fail_reason, result.output)
1161 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1164 def _ParseRbdShowmappedJson(output, volume_name):
1165 """Parse the json output of `rbd showmapped'.
1167 This method parses the json output of `rbd showmapped' and returns the rbd
1168 block device path (e.g. /dev/rbd0) that matches the given rbd volume.
1170 @type output: string
1171 @param output: the json output of `rbd showmapped'
1172 @type volume_name: string
1173 @param volume_name: the name of the volume whose device we search for
1174 @rtype: string or None
1175 @return: block device path if the volume is mapped, else None
1179 devices = serializer.LoadJson(output)
1180 except ValueError, err:
1181 base.ThrowError("Unable to parse JSON data: %s" % err)
1184 for d in devices.values(): # pylint: disable=E1103
1188 base.ThrowError("'name' key missing from json object %s", devices)
1190 if name == volume_name:
1191 if rbd_dev is not None:
1192 base.ThrowError("rbd volume %s is mapped more than once", volume_name)
1194 rbd_dev = d["device"]
1199 def _ParseRbdShowmappedPlain(output, volume_name):
1200 """Parse the (plain / text) output of `rbd showmapped'.
1202 This method parses the output of `rbd showmapped' and returns
1203 the rbd block device path (e.g. /dev/rbd0) that matches the
1206 @type output: string
1207 @param output: the plain text output of `rbd showmapped'
1208 @type volume_name: string
1209 @param volume_name: the name of the volume whose device we search for
1210 @rtype: string or None
1211 @return: block device path if the volume is mapped, else None
1218 lines = output.splitlines()
1220 # Try parsing the new output format (ceph >= 0.55).
1221 splitted_lines = map(lambda l: l.split(), lines)
1223 # Check for empty output.
1224 if not splitted_lines:
1227 # Check showmapped output, to determine number of fields.
1228 field_cnt = len(splitted_lines[0])
1229 if field_cnt != allfields:
1230 # Parsing the new format failed. Fallback to parsing the old output
1232 splitted_lines = map(lambda l: l.split("\t"), lines)
1233 if field_cnt != allfields:
1234 base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
1235 " found %s", allfields, field_cnt)
1238 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
1241 if len(matched_lines) > 1:
1242 base.ThrowError("rbd volume %s mapped more than once", volume_name)
1245 # rbd block device found. Return it.
1246 rbd_dev = matched_lines[0][devicefield]
1249 # The given volume is not mapped.
1253 """Assemble the device.
1259 """Shutdown the device.
1262 if not self.minor and not self.Attach():
1263 # The rbd device doesn't exist.
1266 # Unmap the block device from the Volume.
1267 self._UnmapVolumeFromBlockdev(self.unique_id)
1270 self.dev_path = None
1272 def _UnmapVolumeFromBlockdev(self, unique_id):
1273 """Unmaps the rbd device from the Volume it is mapped.
1275 Unmaps the rbd device from the Volume it was previously mapped to.
1276 This method should be idempotent if the Volume isn't mapped.
1279 pool = self.params[constants.LDP_POOL]
1282 # Check if the mapping already exists.
1283 rbd_dev = self._VolumeToBlockdev(pool, name)
1286 # The mapping exists. Unmap the rbd device.
1287 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
1288 result = utils.RunCmd(unmap_cmd)
1290 base.ThrowError("rbd unmap failed (%s): %s",
1291 result.fail_reason, result.output)
1293 def Open(self, force=False):
1294 """Make the device ready for I/O.
1300 """Notifies that the device will no longer be used for I/O.
1305 def Grow(self, amount, dryrun, backingstore):
1308 @type amount: integer
1309 @param amount: the amount (in mebibytes) to grow with
1310 @type dryrun: boolean
1311 @param dryrun: whether to execute the operation in simulation mode
1312 only, without actually increasing the size
1315 if not backingstore:
1317 if not self.Attach():
1318 base.ThrowError("Can't attach to rbd device during Grow()")
1321 # the rbd tool does not support dry runs of resize operations.
1322 # Since rbd volumes are thinly provisioned, we assume
1323 # there is always enough free space for the operation.
1326 rbd_pool = self.params[constants.LDP_POOL]
1327 rbd_name = self.unique_id[1]
1328 new_size = self.size + amount
1330 # Resize the rbd volume (Image) inside the RADOS cluster.
1331 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
1332 rbd_name, "--size", "%s" % new_size]
1333 result = utils.RunCmd(cmd)
1335 base.ThrowError("rbd resize failed (%s): %s",
1336 result.fail_reason, result.output)
1339 class ExtStorageDevice(base.BlockDev):
1340 """A block device provided by an ExtStorage Provider.
1342 This class implements the External Storage Interface, which means
1343 handling of the externally provided block devices.
1346 def __init__(self, unique_id, children, size, params):
1347 """Attaches to an extstorage block device.
1350 super(ExtStorageDevice, self).__init__(unique_id, children, size, params)
1351 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1352 raise ValueError("Invalid configuration data %s" % str(unique_id))
1354 self.driver, self.vol_name = unique_id
1355 self.ext_params = params
1357 self.major = self.minor = None
1361 def Create(cls, unique_id, children, size, params, excl_stor):
1362 """Create a new extstorage device.
1364 Provision a new volume using an extstorage provider, which will
1365 then be mapped to a block device.
1368 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1369 raise errors.ProgrammerError("Invalid configuration data %s" %
1372 raise errors.ProgrammerError("extstorage device requested with"
1373 " exclusive_storage")
1375 # Call the External Storage's create script,
1376 # to provision a new Volume inside the External Storage
1377 _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
1380 return ExtStorageDevice(unique_id, children, size, params)
1383 """Remove the extstorage device.
1386 if not self.minor and not self.Attach():
1387 # The extstorage device doesn't exist.
1390 # First shutdown the device (remove mappings).
1393 # Call the External Storage's remove script,
1394 # to remove the Volume from the External Storage
1395 _ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id,
1398 def Rename(self, new_id):
1399 """Rename this device.
1405 """Attach to an existing extstorage device.
1407 This method maps the extstorage volume that matches our name with
1408 a corresponding block device and then attaches to this device.
1411 self.attached = False
1413 # Call the External Storage's attach script,
1414 # to attach an existing Volume to a block device under /dev
1415 self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
1416 self.unique_id, self.ext_params)
1419 st = os.stat(self.dev_path)
1420 except OSError, err:
1421 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
1424 if not stat.S_ISBLK(st.st_mode):
1425 logging.error("%s is not a block device", self.dev_path)
1428 self.major = os.major(st.st_rdev)
1429 self.minor = os.minor(st.st_rdev)
1430 self.attached = True
1435 """Assemble the device.
1441 """Shutdown the device.
1444 if not self.minor and not self.Attach():
1445 # The extstorage device doesn't exist.
1448 # Call the External Storage's detach script,
1449 # to detach an existing Volume from it's block device under /dev
1450 _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
1454 self.dev_path = None
1456 def Open(self, force=False):
1457 """Make the device ready for I/O.
1463 """Notifies that the device will no longer be used for I/O.
1468 def Grow(self, amount, dryrun, backingstore):
1471 @type amount: integer
1472 @param amount: the amount (in mebibytes) to grow with
1473 @type dryrun: boolean
1474 @param dryrun: whether to execute the operation in simulation mode
1475 only, without actually increasing the size
1478 if not backingstore:
1480 if not self.Attach():
1481 base.ThrowError("Can't attach to extstorage device during Grow()")
1484 # we do not support dry runs of resize operations for now.
1487 new_size = self.size + amount
1489 # Call the External Storage's grow script,
1490 # to grow an existing Volume inside the External Storage
1491 _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
1492 self.ext_params, str(self.size), grow=str(new_size))
1494 def SetInfo(self, text):
1495 """Update metadata with info text.
1498 # Replace invalid characters
1499 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
1500 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
1502 # Only up to 128 characters are allowed
1505 # Call the External Storage's setinfo script,
1506 # to set metadata for an existing Volume inside the External Storage
1507 _ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id,
1508 self.ext_params, metadata=text)
1511 def _ExtStorageAction(action, unique_id, ext_params,
1512 size=None, grow=None, metadata=None):
1513 """Take an External Storage action.
1515 Take an External Storage action concerning or affecting
1516 a specific Volume inside the External Storage.
1518 @type action: string
1519 @param action: which action to perform. One of:
1520 create / remove / grow / attach / detach
1521 @type unique_id: tuple (driver, vol_name)
1522 @param unique_id: a tuple containing the type of ExtStorage (driver)
1524 @type ext_params: dict
1525 @param ext_params: ExtStorage parameters
1527 @param size: the size of the Volume in mebibytes
1529 @param grow: the new size in mebibytes (after grow)
1530 @type metadata: string
1531 @param metadata: metadata info of the Volume, for use by the provider
1532 @rtype: None or a block device path (during attach)
1535 driver, vol_name = unique_id
1537 # Create an External Storage instance of type `driver'
1538 status, inst_es = ExtStorageFromDisk(driver)
1540 base.ThrowError("%s" % inst_es)
1542 # Create the basic environment for the driver's scripts
1543 create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
1546 # Do not use log file for action `attach' as we need
1547 # to get the output from RunResult
1548 # TODO: find a way to have a log file for attach too
1550 if action is not constants.ES_ACTION_ATTACH:
1551 logfile = _VolumeLogName(action, driver, vol_name)
1553 # Make sure the given action results in a valid script
1554 if action not in constants.ES_SCRIPTS:
1555 base.ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
1558 # Find out which external script to run according the given action
1559 script_name = action + "_script"
1560 script = getattr(inst_es, script_name)
1562 # Run the external script
1563 result = utils.RunCmd([script], env=create_env,
1564 cwd=inst_es.path, output=logfile,)
1566 logging.error("External storage's %s command '%s' returned"
1567 " error: %s, logfile: %s, output: %s",
1568 action, result.cmd, result.fail_reason,
1569 logfile, result.output)
1571 # If logfile is 'None' (during attach), it breaks TailFile
1572 # TODO: have a log file for attach too
1573 if action is not constants.ES_ACTION_ATTACH:
1574 lines = [utils.SafeEncode(val)
1575 for val in utils.TailFile(logfile, lines=20)]
1577 lines = result.output[-20:]
1579 base.ThrowError("External storage's %s script failed (%s), last"
1580 " lines of output:\n%s",
1581 action, result.fail_reason, "\n".join(lines))
1583 if action == constants.ES_ACTION_ATTACH:
1584 return result.stdout
1587 def ExtStorageFromDisk(name, base_dir=None):
1588 """Create an ExtStorage instance from disk.
1590 This function will return an ExtStorage instance
1591 if the given name is a valid ExtStorage name.
1593 @type base_dir: string
1594 @keyword base_dir: Base directory containing ExtStorage installations.
1595 Defaults to a search in all the ES_SEARCH_PATH dirs.
1597 @return: True and the ExtStorage instance if we find a valid one, or
1598 False and the diagnose message on error
1601 if base_dir is None:
1602 es_base_dir = pathutils.ES_SEARCH_PATH
1604 es_base_dir = [base_dir]
1606 es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
1609 return False, ("Directory for External Storage Provider %s not"
1610 " found in search path" % name)
1612 # ES Files dictionary, we will populate it with the absolute path
1613 # names; if the value is True, then it is a required file, otherwise
1615 es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
1617 es_files[constants.ES_PARAMETERS_FILE] = True
1619 for (filename, _) in es_files.items():
1620 es_files[filename] = utils.PathJoin(es_dir, filename)
1623 st = os.stat(es_files[filename])
1624 except EnvironmentError, err:
1625 return False, ("File '%s' under path '%s' is missing (%s)" %
1626 (filename, es_dir, utils.ErrnoOrStr(err)))
1628 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1629 return False, ("File '%s' under path '%s' is not a regular file" %
1632 if filename in constants.ES_SCRIPTS:
1633 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
1634 return False, ("File '%s' under path '%s' is not executable" %
1638 if constants.ES_PARAMETERS_FILE in es_files:
1639 parameters_file = es_files[constants.ES_PARAMETERS_FILE]
1641 parameters = utils.ReadFile(parameters_file).splitlines()
1642 except EnvironmentError, err:
1643 return False, ("Error while reading the EXT parameters file at %s: %s" %
1644 (parameters_file, utils.ErrnoOrStr(err)))
1645 parameters = [v.split(None, 1) for v in parameters]
1648 objects.ExtStorage(name=name, path=es_dir,
1649 create_script=es_files[constants.ES_SCRIPT_CREATE],
1650 remove_script=es_files[constants.ES_SCRIPT_REMOVE],
1651 grow_script=es_files[constants.ES_SCRIPT_GROW],
1652 attach_script=es_files[constants.ES_SCRIPT_ATTACH],
1653 detach_script=es_files[constants.ES_SCRIPT_DETACH],
1654 setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
1655 verify_script=es_files[constants.ES_SCRIPT_VERIFY],
1656 supported_parameters=parameters)
1660 def _ExtStorageEnvironment(unique_id, ext_params,
1661 size=None, grow=None, metadata=None):
1662 """Calculate the environment for an External Storage script.
1664 @type unique_id: tuple (driver, vol_name)
1665 @param unique_id: ExtStorage pool and name of the Volume
1666 @type ext_params: dict
1667 @param ext_params: the EXT parameters
1669 @param size: size of the Volume (in mebibytes)
1671 @param grow: new size of Volume after grow (in mebibytes)
1672 @type metadata: string
1673 @param metadata: metadata info of the Volume
1675 @return: dict of environment variables
1678 vol_name = unique_id[1]
1681 result["VOL_NAME"] = vol_name
1684 for pname, pvalue in ext_params.items():
1685 result["EXTP_%s" % pname.upper()] = str(pvalue)
1687 if size is not None:
1688 result["VOL_SIZE"] = size
1690 if grow is not None:
1691 result["VOL_NEW_SIZE"] = grow
1693 if metadata is not None:
1694 result["VOL_METADATA"] = metadata
1699 def _VolumeLogName(kind, es_name, volume):
1700 """Compute the ExtStorage log filename for a given Volume and operation.
1703 @param kind: the operation type (e.g. create, remove etc.)
1704 @type es_name: string
1705 @param es_name: the ExtStorage name
1706 @type volume: string
1707 @param volume: the name of the Volume inside the External Storage
1710 # Check if the extstorage log dir is a valid dir
1711 if not os.path.isdir(pathutils.LOG_ES_DIR):
1712 base.ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
1714 # TODO: Use tempfile.mkstemp to create unique filename
1715 basename = ("%s-%s-%s-%s.log" %
1716 (kind, es_name, volume, utils.TimestampForFilename()))
1717 return utils.PathJoin(pathutils.LOG_ES_DIR, basename)
1721 constants.LD_LV: LogicalVolume,
1722 constants.LD_DRBD8: drbd.DRBD8Dev,
1723 constants.LD_BLOCKDEV: PersistentBlockDevice,
1724 constants.LD_RBD: RADOSBlockDevice,
1725 constants.LD_EXT: ExtStorageDevice,
1728 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
1729 DEV_MAP[constants.LD_FILE] = FileStorage
1732 def _VerifyDiskType(dev_type):
1733 if dev_type not in DEV_MAP:
1734 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1737 def _VerifyDiskParams(disk):
1738 """Verifies if all disk parameters are set.
1741 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
1743 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
1747 def FindDevice(disk, children):
1748 """Search for an existing, assembled device.
1750 This will succeed only if the device exists and is assembled, but it
1751 does not do any actions in order to activate the device.
1753 @type disk: L{objects.Disk}
1754 @param disk: the disk object to find
1755 @type children: list of L{bdev.BlockDev}
1756 @param children: the list of block devices that are children of the device
1757 represented by the disk parameter
1760 _VerifyDiskType(disk.dev_type)
1761 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
1763 if not device.attached:
1768 def Assemble(disk, children):
1769 """Try to attach or assemble an existing device.
1771 This will attach to assemble the device, as needed, to bring it
1772 fully up. It must be safe to run on already-assembled devices.
1774 @type disk: L{objects.Disk}
1775 @param disk: the disk object to assemble
1776 @type children: list of L{bdev.BlockDev}
1777 @param children: the list of block devices that are children of the device
1778 represented by the disk parameter
1781 _VerifyDiskType(disk.dev_type)
1782 _VerifyDiskParams(disk)
1783 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
1789 def Create(disk, children, excl_stor):
1792 @type disk: L{objects.Disk}
1793 @param disk: the disk object to create
1794 @type children: list of L{bdev.BlockDev}
1795 @param children: the list of block devices that are children of the device
1796 represented by the disk parameter
1797 @type excl_stor: boolean
1798 @param excl_stor: Whether exclusive_storage is active
1801 _VerifyDiskType(disk.dev_type)
1802 _VerifyDiskParams(disk)
1803 device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
1804 disk.params, excl_stor)