1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Block device abstraction.
32
33 """
34
35 import re
36 import stat
37 import os
38 import logging
39 import math
40
41 from ganeti import utils
42 from ganeti import errors
43 from ganeti import constants
44 from ganeti import objects
45 from ganeti import compat
46 from ganeti import pathutils
47 from ganeti import serializer
48 from ganeti.storage import base
49 from ganeti.storage import drbd
50 from ganeti.storage.filestorage import FileStorage
51 from ganeti.storage.gluster import GlusterStorage
55 """`rbd showmmapped' JSON formatting error Exception class.
56
57 """
58 pass
59
62 """Throws an error if the given result is a failed one.
63
64 @param result: result from RunCmd
65
66 """
67 if result.failed:
68 base.ThrowError("Command: %s error: %s - %s",
69 result.cmd, result.fail_reason, result.output)
70
73 """Logical Volume block device.
74
75 """
76 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
77 _PARSE_PV_DEV_RE = re.compile(r"^([^ ()]+)\([0-9]+\)$")
78 _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
79 _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
80
81 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
82 """Attaches to a LV device.
83
84 The unique_id is a tuple (vg_name, lv_name)
85
86 """
87 super(LogicalVolume, self).__init__(unique_id, children, size, params,
88 dyn_params, *args)
89 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
90 raise ValueError("Invalid configuration data %s" % str(unique_id))
91 self._vg_name, self._lv_name = unique_id
92 self._ValidateName(self._vg_name)
93 self._ValidateName(self._lv_name)
94 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
95 self._degraded = True
96 self.major = self.minor = self.pe_size = self.stripe_count = None
97 self.pv_names = None
98 self.Attach()
99
100 @staticmethod
102 """Return the the standard PV size (used with exclusive storage).
103
104 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
105 @rtype: float
106 @return: size in MiB
107
108 """
109 assert len(pvs_info) > 0
110 smallest = min([pv.size for pv in pvs_info])
111 return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
112
113 @staticmethod
115 """Compute the number of PVs needed for an LV (with exclusive storage).
116
117 @type size: float
118 @param size: LV size in MiB
119 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
120 @rtype: integer
121 @return: number of PVs needed
122 """
123 assert len(pvs_info) > 0
124 pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
125 return int(math.ceil(float(size) / pv_size))
126
127 @staticmethod
129 """Return a list of empty PVs, by name.
130
131 """
132 empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
133 if max_pvs is not None:
134 empty_pvs = empty_pvs[:max_pvs]
135 return map((lambda pv: pv.name), empty_pvs)
136
137 @classmethod
138 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
139 dyn_params, *args):
140 """Create a new logical volume.
141
142 """
143 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
144 raise errors.ProgrammerError("Invalid configuration data %s" %
145 str(unique_id))
146 vg_name, lv_name = unique_id
147 cls._ValidateName(vg_name)
148 cls._ValidateName(lv_name)
149 pvs_info = cls.GetPVInfo([vg_name])
150 if not pvs_info:
151 if excl_stor:
152 msg = "No (empty) PVs found"
153 else:
154 msg = "Can't compute PV info for vg %s" % vg_name
155 base.ThrowError(msg)
156 pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
157
158 pvlist = [pv.name for pv in pvs_info]
159 if compat.any(":" in v for v in pvlist):
160 base.ThrowError("Some of your PVs have the invalid character ':' in their"
161 " name, this is not supported - please filter them out"
162 " in lvm.conf using either 'filter' or 'preferred_names'")
163
164 current_pvs = len(pvlist)
165 desired_stripes = params[constants.LDP_STRIPES]
166 stripes = min(current_pvs, desired_stripes)
167
168 if excl_stor:
169 if spindles is None:
170 base.ThrowError("Unspecified number of spindles: this is required"
171 "when exclusive storage is enabled, try running"
172 " gnt-cluster repair-disk-sizes")
173 (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
174 if err_msgs:
175 for m in err_msgs:
176 logging.warning(m)
177 req_pvs = cls._ComputeNumPvs(size, pvs_info)
178 if spindles < req_pvs:
179 base.ThrowError("Requested number of spindles (%s) is not enough for"
180 " a disk of %d MB (at least %d spindles needed)",
181 spindles, size, req_pvs)
182 else:
183 req_pvs = spindles
184 pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
185 current_pvs = len(pvlist)
186 if current_pvs < req_pvs:
187 base.ThrowError("Not enough empty PVs (spindles) to create a disk of %d"
188 " MB: %d available, %d needed",
189 size, current_pvs, req_pvs)
190 assert current_pvs == len(pvlist)
191
192 stripes = current_pvs
193 if stripes > desired_stripes:
194
195 logging.warning("Using %s stripes instead of %s, to be able to use"
196 " %s spindles", stripes, desired_stripes, current_pvs)
197
198 else:
199 if stripes < desired_stripes:
200 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
201 " available.", desired_stripes, vg_name, current_pvs)
202 free_size = sum([pv.free for pv in pvs_info])
203
204
205 if free_size < size:
206 base.ThrowError("Not enough free space: required %s,"
207 " available %s", size, free_size)
208
209
210
211
212
213 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
214 for stripes_arg in range(stripes, 0, -1):
215 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
216 if not result.failed:
217 break
218 if result.failed:
219 base.ThrowError("LV create failed (%s): %s",
220 result.fail_reason, result.output)
221 return LogicalVolume(unique_id, children, size, params, dyn_params, *args)
222
223 @staticmethod
225 """Returns LVM Volume infos using lvm_cmd
226
227 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
228 @param fields: Fields to return
229 @return: A list of dicts each with the parsed fields
230
231 """
232 if not fields:
233 raise errors.ProgrammerError("No fields specified")
234
235 sep = "|"
236 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
237 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
238
239 result = utils.RunCmd(cmd)
240 if result.failed:
241 raise errors.CommandError("Can't get the volume information: %s - %s" %
242 (result.fail_reason, result.output))
243
244 data = []
245 for line in result.stdout.splitlines():
246 splitted_fields = line.strip().split(sep)
247
248 if len(fields) != len(splitted_fields):
249 raise errors.CommandError("Can't parse %s output: line '%s'" %
250 (lvm_cmd, line))
251
252 data.append(splitted_fields)
253
254 return data
255
256 @classmethod
257 - def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
258 """Get the free space info for PVs in a volume group.
259
260 @param vg_names: list of volume group names, if empty all will be returned
261 @param filter_allocatable: whether to skip over unallocatable PVs
262 @param include_lvs: whether to include a list of LVs hosted on each PV
263
264 @rtype: list
265 @return: list of objects.LvmPvInfo objects
266
267 """
268
269
270
271 if include_lvs:
272 lvfield = "lv_name"
273 else:
274 lvfield = "pv_name"
275 try:
276 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
277 "pv_attr", "pv_size", lvfield])
278 except errors.GenericError, err:
279 logging.error("Can't get PV information: %s", err)
280 return None
281
282
283
284
285 if include_lvs:
286 info.sort(key=(lambda i: (i[0], i[5])))
287 data = []
288 lastpvi = None
289 for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
290
291 if filter_allocatable and pv_attr[0] != "a":
292 continue
293
294 if vg_names and vg_name not in vg_names:
295 continue
296
297 if lastpvi and lastpvi.name == pv_name:
298 if include_lvs and lv_name:
299 if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
300 lastpvi.lv_list.append(lv_name)
301 else:
302 if include_lvs and lv_name:
303 lvl = [lv_name]
304 else:
305 lvl = []
306 lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
307 size=float(pv_size), free=float(pv_free),
308 attributes=pv_attr, lv_list=lvl)
309 data.append(lastpvi)
310
311 return data
312
313 @classmethod
315 """Return info (size/free) about PVs.
316
317 @type vg_name: string
318 @param vg_name: VG name
319 @rtype: tuple
320 @return: (standard_pv_size_in_MiB, number_of_free_pvs, total_number_of_pvs)
321
322 """
323 pvs_info = cls.GetPVInfo([vg_name])
324 if not pvs_info:
325 pv_size = 0.0
326 free_pvs = 0
327 num_pvs = 0
328 else:
329 pv_size = cls._GetStdPvSize(pvs_info)
330 free_pvs = len(cls._GetEmptyPvNames(pvs_info))
331 num_pvs = len(pvs_info)
332 return (pv_size, free_pvs, num_pvs)
333
334 @classmethod
336 """Return the free disk space in the given VG, in exclusive storage mode.
337
338 @type vg_name: string
339 @param vg_name: VG name
340 @rtype: float
341 @return: free space in MiB
342 """
343 (pv_size, free_pvs, _) = cls._GetRawFreePvInfo(vg_name)
344 return pv_size * free_pvs
345
346 @classmethod
348 """Get the free space info for specific VGs.
349
350 @param vg_name: volume group name
351 @rtype: tuple
352 @return: (free_spindles, total_spindles)
353
354 """
355 (_, free_pvs, num_pvs) = cls._GetRawFreePvInfo(vg_name)
356 return (free_pvs, num_pvs)
357
358 @classmethod
359 - def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
360 """Get the free space info for specific VGs.
361
362 @param vg_names: list of volume group names, if empty all will be returned
363 @param excl_stor: whether exclusive_storage is enabled
364 @param filter_readonly: whether to skip over readonly VGs
365
366 @rtype: list
367 @return: list of tuples (free_space, total_size, name) with free_space in
368 MiB
369
370 """
371 try:
372 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
373 "vg_size"])
374 except errors.GenericError, err:
375 logging.error("Can't get VG information: %s", err)
376 return None
377
378 data = []
379 for vg_name, vg_free, vg_attr, vg_size in info:
380
381 if filter_readonly and vg_attr[0] == "r":
382 continue
383
384 if vg_names and vg_name not in vg_names:
385 continue
386
387 if excl_stor:
388 es_free = cls._GetExclusiveStorageVgFree(vg_name)
389 assert es_free <= vg_free
390 vg_free = es_free
391 data.append((float(vg_free), float(vg_size), vg_name))
392
393 return data
394
395 @classmethod
397 """Validates that a given name is valid as VG or LV name.
398
399 The list of valid characters and restricted names is taken out of
400 the lvm(8) manpage, with the simplification that we enforce both
401 VG and LV restrictions on the names.
402
403 """
404 if (not cls._VALID_NAME_RE.match(name) or
405 name in cls._INVALID_NAMES or
406 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
407 base.ThrowError("Invalid LVM name '%s'", name)
408
410 """Remove this logical volume.
411
412 """
413 if not self.minor and not self.Attach():
414
415 return
416 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
417 (self._vg_name, self._lv_name)])
418 if result.failed:
419 base.ThrowError("Can't lvremove: %s - %s",
420 result.fail_reason, result.output)
421
423 """Rename this logical volume.
424
425 """
426 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
427 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
428 new_vg, new_name = new_id
429 if new_vg != self._vg_name:
430 raise errors.ProgrammerError("Can't move a logical volume across"
431 " volume groups (from %s to to %s)" %
432 (self._vg_name, new_vg))
433 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
434 if result.failed:
435 base.ThrowError("Failed to rename the logical volume: %s", result.output)
436 self._lv_name = new_name
437 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
438
439 @classmethod
441 """Parse one line of the lvs output used in L{_GetLvInfo}.
442
443 """
444 elems = line.strip().split(sep)
445
446
447
448
449
450 if len(elems) == 7 and elems[-1] == "":
451 elems.pop()
452
453 if len(elems) != 6:
454 base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
455
456 (status, major, minor, pe_size, stripes, pvs) = elems
457 if len(status) < 6:
458 base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
459
460 try:
461 major = int(major)
462 minor = int(minor)
463 except (TypeError, ValueError), err:
464 base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
465
466 try:
467 pe_size = int(float(pe_size))
468 except (TypeError, ValueError), err:
469 base.ThrowError("Can't parse vg extent size: %s", err)
470
471 try:
472 stripes = int(stripes)
473 except (TypeError, ValueError), err:
474 base.ThrowError("Can't parse the number of stripes: %s", err)
475
476 pv_names = []
477 if pvs != "":
478 for pv in pvs.split(","):
479 m = re.match(cls._PARSE_PV_DEV_RE, pv)
480 if not m:
481 base.ThrowError("Can't parse this device list: %s", pvs)
482 pv_names.append(m.group(1))
483
484 return (status, major, minor, pe_size, stripes, pv_names)
485
486 @classmethod
488 """Get info about the given existing LV to be used.
489
490 """
491 sep = "|"
492 result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
493 "--units=k", "--nosuffix",
494 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
495 "vg_extent_size,stripes,devices", dev_path])
496 if result.failed:
497 base.ThrowError("Can't find LV %s: %s, %s",
498 dev_path, result.fail_reason, result.output)
499
500
501
502
503
504 out = result.stdout.splitlines()
505 if not out:
506
507 base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
508 pv_names = set()
509 for line in out:
510 (status, major, minor, pe_size, stripes, more_pvs) = \
511 cls._ParseLvInfoLine(line, sep)
512 pv_names.update(more_pvs)
513 return (status, major, minor, pe_size, stripes, pv_names)
514
516 """Attach to an existing LV.
517
518 This method will try to see if an existing and active LV exists
519 which matches our name. If so, its major/minor will be
520 recorded.
521
522 """
523 self.attached = False
524 try:
525 (status, major, minor, pe_size, stripes, pv_names) = \
526 self._GetLvInfo(self.dev_path)
527 except errors.BlockDeviceError:
528 return False
529
530 self.major = major
531 self.minor = minor
532 self.pe_size = pe_size
533 self.stripe_count = stripes
534 self._degraded = status[0] == "v"
535
536 self.pv_names = pv_names
537 self.attached = True
538 return True
539
541 """Assemble the device.
542
543 We always run `lvchange -ay` on the LV to ensure it's active before
544 use, as there were cases when xenvg was not active after boot
545 (also possibly after disk issues).
546
547 """
548 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
549 if result.failed:
550 base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
551
553 """Shutdown the device.
554
555 This is a no-op for the LV device type, as we don't deactivate the
556 volumes on shutdown.
557
558 """
559 pass
560
562 """Returns the sync status of the device.
563
564 If this device is a mirroring device, this function returns the
565 status of the mirror.
566
567 For logical volumes, sync_percent and estimated_time are always
568 None (no recovery in progress, as we don't handle the mirrored LV
569 case). The is_degraded parameter is the inverse of the ldisk
570 parameter.
571
572 For the ldisk parameter, we check if the logical volume has the
573 'virtual' type, which means it's not backed by existing storage
574 anymore (read from it return I/O error). This happens after a
575 physical disk failure and subsequent 'vgreduce --removemissing' on
576 the volume group.
577
578 The status was already read in Attach, so we just return it.
579
580 @rtype: objects.BlockDevStatus
581
582 """
583 if self._degraded:
584 ldisk_status = constants.LDS_FAULTY
585 else:
586 ldisk_status = constants.LDS_OKAY
587
588 return objects.BlockDevStatus(dev_path=self.dev_path,
589 major=self.major,
590 minor=self.minor,
591 sync_percent=None,
592 estimated_time=None,
593 is_degraded=self._degraded,
594 ldisk_status=ldisk_status)
595
596 - def Open(self, force=False):
597 """Make the device ready for I/O.
598
599 This is a no-op for the LV device type.
600
601 """
602 pass
603
605 """Notifies that the device will no longer be used for I/O.
606
607 This is a no-op for the LV device type.
608
609 """
610 pass
611
613 """Create a snapshot copy of an lvm block device.
614
615 @returns: tuple (vg, lv)
616
617 """
618 snap_name = self._lv_name + ".snap"
619
620
621 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params,
622 self.dyn_params)
623 base.IgnoreError(snap.Remove)
624
625 vg_info = self.GetVGInfo([self._vg_name], False)
626 if not vg_info:
627 base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
628 free_size, _, _ = vg_info[0]
629 if free_size < size:
630 base.ThrowError("Not enough free space: required %s,"
631 " available %s", size, free_size)
632
633 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
634 "-n%s" % snap_name, self.dev_path]))
635
636 return (self._vg_name, snap_name)
637
639 """Try to remove old tags from the lv.
640
641 """
642 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
643 self.dev_path])
644 _CheckResult(result)
645
646 raw_tags = result.stdout.strip()
647 if raw_tags:
648 for tag in raw_tags.split(","):
649 _CheckResult(utils.RunCmd(["lvchange", "--deltag",
650 tag.strip(), self.dev_path]))
651
668
670 """Return how much the disk can grow with exclusive storage.
671
672 @rtype: float
673 @return: available space in Mib
674
675 """
676 pvs_info = self.GetPVInfo([self._vg_name])
677 if not pvs_info:
678 base.ThrowError("Cannot get information about PVs for %s", self.dev_path)
679 std_pv_size = self._GetStdPvSize(pvs_info)
680 free_space = sum(pvi.free - (pvi.size - std_pv_size)
681 for pvi in pvs_info
682 if pvi.name in self.pv_names)
683 return free_space
684
685 - def Grow(self, amount, dryrun, backingstore, excl_stor):
686 """Grow the logical volume.
687
688 """
689 if not backingstore:
690 return
691 if self.pe_size is None or self.stripe_count is None:
692 if not self.Attach():
693 base.ThrowError("Can't attach to LV during Grow()")
694 full_stripe_size = self.pe_size * self.stripe_count
695
696 amount *= 1024
697 rest = amount % full_stripe_size
698 if rest != 0:
699 amount += full_stripe_size - rest
700 cmd = ["lvextend", "-L", "+%dk" % amount]
701 if dryrun:
702 cmd.append("--test")
703 if excl_stor:
704 free_space = self._GetGrowthAvaliabilityExclStor()
705
706 if amount > free_space * 1024:
707 base.ThrowError("Not enough free space to grow %s: %d MiB required,"
708 " %d available", self.dev_path, amount / 1024,
709 free_space)
710
711
712 pvlist = list(self.pv_names)
713 else:
714 pvlist = []
715
716
717
718
719 for alloc_policy in "contiguous", "cling", "normal":
720 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path] +
721 pvlist)
722 if not result.failed:
723 return
724 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
725
727 """Return the number of spindles used.
728
729 """
730 assert self.attached, "BlockDevice not attached in GetActualSpindles()"
731 return len(self.pv_names)
732
735 """A block device with persistent node
736
737 May be either directly attached, or exposed through DM (e.g. dm-multipath).
738 udev helpers are probably required to give persistent, human-friendly
739 names.
740
741 For the time being, pathnames are required to lie under /dev.
742
743 """
744 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
745 """Attaches to a static block device.
746
747 The unique_id is a path under /dev.
748
749 """
750 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
751 params, dyn_params, *args)
752 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
753 raise ValueError("Invalid configuration data %s" % str(unique_id))
754 self.dev_path = unique_id[1]
755 if not os.path.realpath(self.dev_path).startswith("/dev/"):
756 raise ValueError("Full path '%s' lies outside /dev" %
757 os.path.realpath(self.dev_path))
758
759
760
761
762 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
763 raise ValueError("Got persistent block device of invalid type: %s" %
764 unique_id[0])
765
766 self.major = self.minor = None
767 self.Attach()
768
769 @classmethod
770 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
771 dyn_params, *args):
772 """Create a new device
773
774 This is a noop, we only return a PersistentBlockDevice instance
775
776 """
777 if excl_stor:
778 raise errors.ProgrammerError("Persistent block device requested with"
779 " exclusive_storage")
780 return PersistentBlockDevice(unique_id, children, 0, params, dyn_params,
781 *args)
782
784 """Remove a device
785
786 This is a noop
787
788 """
789 pass
790
792 """Rename this device.
793
794 """
795 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
796
798 """Attach to an existing block device.
799
800
801 """
802 self.attached = False
803 try:
804 st = os.stat(self.dev_path)
805 except OSError, err:
806 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
807 return False
808
809 if not stat.S_ISBLK(st.st_mode):
810 logging.error("%s is not a block device", self.dev_path)
811 return False
812
813 self.major = os.major(st.st_rdev)
814 self.minor = utils.osminor(st.st_rdev)
815 self.attached = True
816
817 return True
818
820 """Assemble the device.
821
822 """
823 pass
824
826 """Shutdown the device.
827
828 """
829 pass
830
831 - def Open(self, force=False):
832 """Make the device ready for I/O.
833
834 """
835 pass
836
838 """Notifies that the device will no longer be used for I/O.
839
840 """
841 pass
842
843 - def Grow(self, amount, dryrun, backingstore, excl_stor):
844 """Grow the logical volume.
845
846 """
847 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
848
851 """A RADOS Block Device (rbd).
852
853 This class implements the RADOS Block Device for the backend. You need
854 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
855 this to be functional.
856
857 """
858 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
859 """Attaches to an rbd device.
860
861 """
862 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params,
863 dyn_params, *args)
864 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
865 raise ValueError("Invalid configuration data %s" % str(unique_id))
866
867 self.driver, self.rbd_name = unique_id
868 self.rbd_pool = params[constants.LDP_POOL]
869
870 self.major = self.minor = None
871 self.Attach()
872
873 @classmethod
874 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
875 dyn_params, *args):
876 """Create a new rbd device.
877
878 Provision a new rbd volume inside a RADOS pool.
879
880 """
881 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
882 raise errors.ProgrammerError("Invalid configuration data %s" %
883 str(unique_id))
884 if excl_stor:
885 raise errors.ProgrammerError("RBD device requested with"
886 " exclusive_storage")
887 rbd_pool = params[constants.LDP_POOL]
888 rbd_name = unique_id[1]
889
890
891 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
892 rbd_name, "--size", "%s" % size]
893 result = utils.RunCmd(cmd)
894 if result.failed:
895 base.ThrowError("rbd creation failed (%s): %s",
896 result.fail_reason, result.output)
897
898 return RADOSBlockDevice(unique_id, children, size, params, dyn_params,
899 *args)
900
902 """Remove the rbd device.
903
904 """
905 rbd_pool = self.params[constants.LDP_POOL]
906 rbd_name = self.unique_id[1]
907
908 if not self.minor and not self.Attach():
909
910 return
911
912
913 self.Shutdown()
914
915
916 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
917 result = utils.RunCmd(cmd)
918 if result.failed:
919 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
920 result.fail_reason, result.output)
921
923 """Rename this device.
924
925 """
926 pass
927
929 """Attach to an existing rbd device.
930
931 This method maps the rbd volume that matches our name with
932 an rbd device and then attaches to this device.
933
934 """
935 self.attached = False
936
937
938 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
939
940 try:
941 st = os.stat(self.dev_path)
942 except OSError, err:
943 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
944 return False
945
946 if not stat.S_ISBLK(st.st_mode):
947 logging.error("%s is not a block device", self.dev_path)
948 return False
949
950 self.major = os.major(st.st_rdev)
951 self.minor = utils.osminor(st.st_rdev)
952 self.attached = True
953
954 return True
955
957 """Maps existing rbd volumes to block devices.
958
959 This method should be idempotent if the mapping already exists.
960
961 @rtype: string
962 @return: the block device path that corresponds to the volume
963
964 """
965 pool = self.params[constants.LDP_POOL]
966 name = unique_id[1]
967
968
969 rbd_dev = self._VolumeToBlockdev(pool, name)
970 if rbd_dev:
971
972 return rbd_dev
973
974
975 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
976 result = utils.RunCmd(map_cmd)
977 if result.failed:
978 base.ThrowError("rbd map failed (%s): %s",
979 result.fail_reason, result.output)
980
981
982 rbd_dev = self._VolumeToBlockdev(pool, name)
983 if not rbd_dev:
984 base.ThrowError("rbd map succeeded, but could not find the rbd block"
985 " device in output of showmapped, for volume: %s", name)
986
987
988 return rbd_dev
989
990 @classmethod
992 """Do the 'volume name'-to-'rbd block device' resolving.
993
994 @type pool: string
995 @param pool: RADOS pool to use
996 @type volume_name: string
997 @param volume_name: the name of the volume whose device we search for
998 @rtype: string or None
999 @return: block device path if the volume is mapped, else None
1000
1001 """
1002 try:
1003
1004
1005 showmap_cmd = [
1006 constants.RBD_CMD,
1007 "showmapped",
1008 "-p",
1009 pool,
1010 "--format",
1011 "json"
1012 ]
1013 result = utils.RunCmd(showmap_cmd)
1014 if result.failed:
1015 logging.error("rbd JSON output formatting returned error (%s): %s,"
1016 "falling back to plain output parsing",
1017 result.fail_reason, result.output)
1018 raise RbdShowmappedJsonError
1019
1020 return cls._ParseRbdShowmappedJson(result.output, volume_name)
1021 except RbdShowmappedJsonError:
1022
1023
1024 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
1025 result = utils.RunCmd(showmap_cmd)
1026 if result.failed:
1027 base.ThrowError("rbd showmapped failed (%s): %s",
1028 result.fail_reason, result.output)
1029
1030 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1031
1032 @staticmethod
1034 """Parse the json output of `rbd showmapped'.
1035
1036 This method parses the json output of `rbd showmapped' and returns the rbd
1037 block device path (e.g. /dev/rbd0) that matches the given rbd volume.
1038
1039 @type output: string
1040 @param output: the json output of `rbd showmapped'
1041 @type volume_name: string
1042 @param volume_name: the name of the volume whose device we search for
1043 @rtype: string or None
1044 @return: block device path if the volume is mapped, else None
1045
1046 """
1047 try:
1048 devices = serializer.LoadJson(output)
1049 except ValueError, err:
1050 base.ThrowError("Unable to parse JSON data: %s" % err)
1051
1052 rbd_dev = None
1053 for d in devices.values():
1054 try:
1055 name = d["name"]
1056 except KeyError:
1057 base.ThrowError("'name' key missing from json object %s", devices)
1058
1059 if name == volume_name:
1060 if rbd_dev is not None:
1061 base.ThrowError("rbd volume %s is mapped more than once", volume_name)
1062
1063 rbd_dev = d["device"]
1064
1065 return rbd_dev
1066
1067 @staticmethod
1069 """Parse the (plain / text) output of `rbd showmapped'.
1070
1071 This method parses the output of `rbd showmapped' and returns
1072 the rbd block device path (e.g. /dev/rbd0) that matches the
1073 given rbd volume.
1074
1075 @type output: string
1076 @param output: the plain text output of `rbd showmapped'
1077 @type volume_name: string
1078 @param volume_name: the name of the volume whose device we search for
1079 @rtype: string or None
1080 @return: block device path if the volume is mapped, else None
1081
1082 """
1083 allfields = 5
1084 volumefield = 2
1085 devicefield = 4
1086
1087 lines = output.splitlines()
1088
1089
1090 splitted_lines = map(lambda l: l.split(), lines)
1091
1092
1093 if not splitted_lines:
1094 return None
1095
1096
1097 field_cnt = len(splitted_lines[0])
1098 if field_cnt != allfields:
1099
1100
1101 splitted_lines = map(lambda l: l.split("\t"), lines)
1102 if field_cnt != allfields:
1103 base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
1104 " found %s", allfields, field_cnt)
1105
1106 matched_lines = \
1107 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
1108 splitted_lines)
1109
1110 if len(matched_lines) > 1:
1111 base.ThrowError("rbd volume %s mapped more than once", volume_name)
1112
1113 if matched_lines:
1114
1115 rbd_dev = matched_lines[0][devicefield]
1116 return rbd_dev
1117
1118
1119 return None
1120
1122 """Assemble the device.
1123
1124 """
1125 pass
1126
1128 """Shutdown the device.
1129
1130 """
1131 if not self.minor and not self.Attach():
1132
1133 return
1134
1135
1136 self._UnmapVolumeFromBlockdev(self.unique_id)
1137
1138 self.minor = None
1139 self.dev_path = None
1140
1142 """Unmaps the rbd device from the Volume it is mapped.
1143
1144 Unmaps the rbd device from the Volume it was previously mapped to.
1145 This method should be idempotent if the Volume isn't mapped.
1146
1147 """
1148 pool = self.params[constants.LDP_POOL]
1149 name = unique_id[1]
1150
1151
1152 rbd_dev = self._VolumeToBlockdev(pool, name)
1153
1154 if rbd_dev:
1155
1156 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
1157 result = utils.RunCmd(unmap_cmd)
1158 if result.failed:
1159 base.ThrowError("rbd unmap failed (%s): %s",
1160 result.fail_reason, result.output)
1161
1162 - def Open(self, force=False):
1163 """Make the device ready for I/O.
1164
1165 """
1166 pass
1167
1169 """Notifies that the device will no longer be used for I/O.
1170
1171 """
1172 pass
1173
1174 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1175 """Grow the Volume.
1176
1177 @type amount: integer
1178 @param amount: the amount (in mebibytes) to grow with
1179 @type dryrun: boolean
1180 @param dryrun: whether to execute the operation in simulation mode
1181 only, without actually increasing the size
1182
1183 """
1184 if not backingstore:
1185 return
1186 if not self.Attach():
1187 base.ThrowError("Can't attach to rbd device during Grow()")
1188
1189 if dryrun:
1190
1191
1192
1193 return
1194
1195 rbd_pool = self.params[constants.LDP_POOL]
1196 rbd_name = self.unique_id[1]
1197 new_size = self.size + amount
1198
1199
1200 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
1201 rbd_name, "--size", "%s" % new_size]
1202 result = utils.RunCmd(cmd)
1203 if result.failed:
1204 base.ThrowError("rbd resize failed (%s): %s",
1205 result.fail_reason, result.output)
1206
1208 """Generate KVM userspace URIs to be used as `-drive file` settings.
1209
1210 @see: L{BlockDev.GetUserspaceAccessUri}
1211
1212 """
1213 if hypervisor == constants.HT_KVM:
1214 return "rbd:" + self.rbd_pool + "/" + self.rbd_name
1215 else:
1216 base.ThrowError("Hypervisor %s doesn't support RBD userspace access" %
1217 hypervisor)
1218
1221 """A block device provided by an ExtStorage Provider.
1222
1223 This class implements the External Storage Interface, which means
1224 handling of the externally provided block devices.
1225
1226 """
1227 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
1228 """Attaches to an extstorage block device.
1229
1230 """
1231 super(ExtStorageDevice, self).__init__(unique_id, children, size, params,
1232 dyn_params, *args)
1233 (self.name, self.uuid) = args
1234
1235 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1236 raise ValueError("Invalid configuration data %s" % str(unique_id))
1237
1238 self.driver, self.vol_name = unique_id
1239 self.ext_params = params
1240
1241 self.major = self.minor = None
1242 self.Attach()
1243
1244 @classmethod
1245 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
1246 dyn_params, *args):
1247 """Create a new extstorage device.
1248
1249 Provision a new volume using an extstorage provider, which will
1250 then be mapped to a block device.
1251
1252 """
1253 (name, uuid) = args
1254
1255 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1256 raise errors.ProgrammerError("Invalid configuration data %s" %
1257 str(unique_id))
1258 if excl_stor:
1259 raise errors.ProgrammerError("extstorage device requested with"
1260 " exclusive_storage")
1261
1262
1263
1264 _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
1265 params, size=str(size), name=name, uuid=uuid)
1266
1267 return ExtStorageDevice(unique_id, children, size, params, dyn_params,
1268 *args)
1269
1285
1287 """Rename this device.
1288
1289 """
1290 pass
1291
1293 """Attach to an existing extstorage device.
1294
1295 This method maps the extstorage volume that matches our name with
1296 a corresponding block device and then attaches to this device.
1297
1298 """
1299 self.attached = False
1300
1301
1302
1303 self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
1304 self.unique_id, self.ext_params,
1305 name=self.name, uuid=self.uuid)
1306
1307 try:
1308 st = os.stat(self.dev_path)
1309 except OSError, err:
1310 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
1311 return False
1312
1313 if not stat.S_ISBLK(st.st_mode):
1314 logging.error("%s is not a block device", self.dev_path)
1315 return False
1316
1317 self.major = os.major(st.st_rdev)
1318 self.minor = utils.osminor(st.st_rdev)
1319 self.attached = True
1320
1321 return True
1322
1324 """Assemble the device.
1325
1326 """
1327 pass
1328
1330 """Shutdown the device.
1331
1332 """
1333 if not self.minor and not self.Attach():
1334
1335 return
1336
1337
1338
1339 _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
1340 self.ext_params, name=self.name, uuid=self.uuid)
1341
1342 self.minor = None
1343 self.dev_path = None
1344
1345 - def Open(self, force=False):
1346 """Make the device ready for I/O.
1347
1348 """
1349 pass
1350
1352 """Notifies that the device will no longer be used for I/O.
1353
1354 """
1355 pass
1356
1357 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1358 """Grow the Volume.
1359
1360 @type amount: integer
1361 @param amount: the amount (in mebibytes) to grow with
1362 @type dryrun: boolean
1363 @param dryrun: whether to execute the operation in simulation mode
1364 only, without actually increasing the size
1365
1366 """
1367 if not backingstore:
1368 return
1369 if not self.Attach():
1370 base.ThrowError("Can't attach to extstorage device during Grow()")
1371
1372 if dryrun:
1373
1374 return
1375
1376 new_size = self.size + amount
1377
1378
1379
1380 _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
1381 self.ext_params, size=str(self.size), grow=str(new_size),
1382 name=self.name, uuid=self.uuid)
1383
1400
1401
1402 -def _ExtStorageAction(action, unique_id, ext_params,
1403 size=None, grow=None, metadata=None,
1404 name=None, uuid=None):
1405 """Take an External Storage action.
1406
1407 Take an External Storage action concerning or affecting
1408 a specific Volume inside the External Storage.
1409
1410 @type action: string
1411 @param action: which action to perform. One of:
1412 create / remove / grow / attach / detach
1413 @type unique_id: tuple (driver, vol_name)
1414 @param unique_id: a tuple containing the type of ExtStorage (driver)
1415 and the Volume name
1416 @type ext_params: dict
1417 @param ext_params: ExtStorage parameters
1418 @type size: integer
1419 @param size: the size of the Volume in mebibytes
1420 @type grow: integer
1421 @param grow: the new size in mebibytes (after grow)
1422 @type metadata: string
1423 @param metadata: metadata info of the Volume, for use by the provider
1424 @type name: string
1425 @param name: name of the Volume (objects.Disk.name)
1426 @type uuid: string
1427 @param uuid: uuid of the Volume (objects.Disk.uuid)
1428 @rtype: None or a block device path (during attach)
1429
1430 """
1431 driver, vol_name = unique_id
1432
1433
1434 status, inst_es = ExtStorageFromDisk(driver)
1435 if not status:
1436 base.ThrowError("%s" % inst_es)
1437
1438
1439 create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
1440 grow, metadata, name, uuid)
1441
1442
1443
1444
1445 logfile = None
1446 if action is not constants.ES_ACTION_ATTACH:
1447 logfile = _VolumeLogName(action, driver, vol_name)
1448
1449
1450 if action not in constants.ES_SCRIPTS:
1451 base.ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
1452 action)
1453
1454
1455 script_name = action + "_script"
1456 script = getattr(inst_es, script_name)
1457
1458
1459 result = utils.RunCmd([script], env=create_env,
1460 cwd=inst_es.path, output=logfile,)
1461 if result.failed:
1462 logging.error("External storage's %s command '%s' returned"
1463 " error: %s, logfile: %s, output: %s",
1464 action, result.cmd, result.fail_reason,
1465 logfile, result.output)
1466
1467
1468
1469 if action is not constants.ES_ACTION_ATTACH:
1470 lines = [utils.SafeEncode(val)
1471 for val in utils.TailFile(logfile, lines=20)]
1472 else:
1473 lines = result.output[-20:]
1474
1475 base.ThrowError("External storage's %s script failed (%s), last"
1476 " lines of output:\n%s",
1477 action, result.fail_reason, "\n".join(lines))
1478
1479 if action == constants.ES_ACTION_ATTACH:
1480 return result.stdout
1481
1484 """Create an ExtStorage instance from disk.
1485
1486 This function will return an ExtStorage instance
1487 if the given name is a valid ExtStorage name.
1488
1489 @type base_dir: string
1490 @keyword base_dir: Base directory containing ExtStorage installations.
1491 Defaults to a search in all the ES_SEARCH_PATH dirs.
1492 @rtype: tuple
1493 @return: True and the ExtStorage instance if we find a valid one, or
1494 False and the diagnose message on error
1495
1496 """
1497 if base_dir is None:
1498 es_base_dir = pathutils.ES_SEARCH_PATH
1499 else:
1500 es_base_dir = [base_dir]
1501
1502 es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
1503
1504 if es_dir is None:
1505 return False, ("Directory for External Storage Provider %s not"
1506 " found in search path" % name)
1507
1508
1509
1510
1511 es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
1512
1513 es_files[constants.ES_PARAMETERS_FILE] = True
1514
1515 for (filename, _) in es_files.items():
1516 es_files[filename] = utils.PathJoin(es_dir, filename)
1517
1518 try:
1519 st = os.stat(es_files[filename])
1520 except EnvironmentError, err:
1521 return False, ("File '%s' under path '%s' is missing (%s)" %
1522 (filename, es_dir, utils.ErrnoOrStr(err)))
1523
1524 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1525 return False, ("File '%s' under path '%s' is not a regular file" %
1526 (filename, es_dir))
1527
1528 if filename in constants.ES_SCRIPTS:
1529 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
1530 return False, ("File '%s' under path '%s' is not executable" %
1531 (filename, es_dir))
1532
1533 parameters = []
1534 if constants.ES_PARAMETERS_FILE in es_files:
1535 parameters_file = es_files[constants.ES_PARAMETERS_FILE]
1536 try:
1537 parameters = utils.ReadFile(parameters_file).splitlines()
1538 except EnvironmentError, err:
1539 return False, ("Error while reading the EXT parameters file at %s: %s" %
1540 (parameters_file, utils.ErrnoOrStr(err)))
1541 parameters = [v.split(None, 1) for v in parameters]
1542
1543 es_obj = \
1544 objects.ExtStorage(name=name, path=es_dir,
1545 create_script=es_files[constants.ES_SCRIPT_CREATE],
1546 remove_script=es_files[constants.ES_SCRIPT_REMOVE],
1547 grow_script=es_files[constants.ES_SCRIPT_GROW],
1548 attach_script=es_files[constants.ES_SCRIPT_ATTACH],
1549 detach_script=es_files[constants.ES_SCRIPT_DETACH],
1550 setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
1551 verify_script=es_files[constants.ES_SCRIPT_VERIFY],
1552 supported_parameters=parameters)
1553 return True, es_obj
1554
1555
1556 -def _ExtStorageEnvironment(unique_id, ext_params,
1557 size=None, grow=None, metadata=None,
1558 name=None, uuid=None):
1559 """Calculate the environment for an External Storage script.
1560
1561 @type unique_id: tuple (driver, vol_name)
1562 @param unique_id: ExtStorage pool and name of the Volume
1563 @type ext_params: dict
1564 @param ext_params: the EXT parameters
1565 @type size: string
1566 @param size: size of the Volume (in mebibytes)
1567 @type grow: string
1568 @param grow: new size of Volume after grow (in mebibytes)
1569 @type metadata: string
1570 @param metadata: metadata info of the Volume
1571 @type name: string
1572 @param name: name of the Volume (objects.Disk.name)
1573 @type uuid: string
1574 @param uuid: uuid of the Volume (objects.Disk.uuid)
1575 @rtype: dict
1576 @return: dict of environment variables
1577
1578 """
1579 vol_name = unique_id[1]
1580
1581 result = {}
1582 result["VOL_NAME"] = vol_name
1583
1584
1585 for pname, pvalue in ext_params.items():
1586 result["EXTP_%s" % pname.upper()] = str(pvalue)
1587
1588 if size is not None:
1589 result["VOL_SIZE"] = size
1590
1591 if grow is not None:
1592 result["VOL_NEW_SIZE"] = grow
1593
1594 if metadata is not None:
1595 result["VOL_METADATA"] = metadata
1596
1597 if name is not None:
1598 result["VOL_CNAME"] = name
1599
1600 if uuid is not None:
1601 result["VOL_UUID"] = uuid
1602
1603 return result
1604
1607 """Compute the ExtStorage log filename for a given Volume and operation.
1608
1609 @type kind: string
1610 @param kind: the operation type (e.g. create, remove etc.)
1611 @type es_name: string
1612 @param es_name: the ExtStorage name
1613 @type volume: string
1614 @param volume: the name of the Volume inside the External Storage
1615
1616 """
1617
1618 if not os.path.isdir(pathutils.LOG_ES_DIR):
1619 base.ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
1620
1621
1622 basename = ("%s-%s-%s-%s.log" %
1623 (kind, es_name, volume, utils.TimestampForFilename()))
1624 return utils.PathJoin(pathutils.LOG_ES_DIR, basename)
1625
1630
1633 """Verifies if all disk parameters are set.
1634
1635 """
1636 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
1637 if missing:
1638 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
1639 missing)
1640
1643 """Search for an existing, assembled device.
1644
1645 This will succeed only if the device exists and is assembled, but it
1646 does not do any actions in order to activate the device.
1647
1648 @type disk: L{objects.Disk}
1649 @param disk: the disk object to find
1650 @type children: list of L{bdev.BlockDev}
1651 @param children: the list of block devices that are children of the device
1652 represented by the disk parameter
1653
1654 """
1655 _VerifyDiskType(disk.dev_type)
1656 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
1657 disk.params, disk.dynamic_params,
1658 disk.name, disk.uuid)
1659 if not device.attached:
1660 return None
1661 return device
1662
1665 """Try to attach or assemble an existing device.
1666
1667 This will attach to assemble the device, as needed, to bring it
1668 fully up. It must be safe to run on already-assembled devices.
1669
1670 @type disk: L{objects.Disk}
1671 @param disk: the disk object to assemble
1672 @type children: list of L{bdev.BlockDev}
1673 @param children: the list of block devices that are children of the device
1674 represented by the disk parameter
1675
1676 """
1677 _VerifyDiskType(disk.dev_type)
1678 _VerifyDiskParams(disk)
1679 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
1680 disk.params, disk.dynamic_params,
1681 disk.name, disk.uuid)
1682 device.Assemble()
1683 return device
1684
1685
1686 -def Create(disk, children, excl_stor):
1687 """Create a device.
1688
1689 @type disk: L{objects.Disk}
1690 @param disk: the disk object to create
1691 @type children: list of L{bdev.BlockDev}
1692 @param children: the list of block devices that are children of the device
1693 represented by the disk parameter
1694 @type excl_stor: boolean
1695 @param excl_stor: Whether exclusive_storage is active
1696 @rtype: L{bdev.BlockDev}
1697 @return: the created device, or C{None} in case of an error
1698
1699 """
1700 _VerifyDiskType(disk.dev_type)
1701 _VerifyDiskParams(disk)
1702 device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size,
1703 disk.spindles, disk.params, excl_stor,
1704 disk.dynamic_params,
1705 disk.name, disk.uuid)
1706 return device
1707
1708
1709 DEV_MAP = {
1710 constants.DT_PLAIN: LogicalVolume,
1711 constants.DT_DRBD8: drbd.DRBD8Dev,
1712 constants.DT_BLOCK: PersistentBlockDevice,
1713 constants.DT_RBD: RADOSBlockDevice,
1714 constants.DT_EXT: ExtStorageDevice,
1715 constants.DT_FILE: FileStorage,
1716 constants.DT_SHARED_FILE: FileStorage,
1717 constants.DT_GLUSTER: GlusterStorage,
1718 }
1719 """Map disk types to disk type classes.
1720
1721 @see: L{Assemble}, L{FindDevice}, L{Create}."""
1722