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().rstrip(sep).split(sep)
445 if len(elems) != 6:
446 base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
447
448 (status, major, minor, pe_size, stripes, pvs) = elems
449 if len(status) < 6:
450 base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
451
452 try:
453 major = int(major)
454 minor = int(minor)
455 except (TypeError, ValueError), err:
456 base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
457
458 try:
459 pe_size = int(float(pe_size))
460 except (TypeError, ValueError), err:
461 base.ThrowError("Can't parse vg extent size: %s", err)
462
463 try:
464 stripes = int(stripes)
465 except (TypeError, ValueError), err:
466 base.ThrowError("Can't parse the number of stripes: %s", err)
467
468 pv_names = []
469 for pv in pvs.split(","):
470 m = re.match(cls._PARSE_PV_DEV_RE, pv)
471 if not m:
472 base.ThrowError("Can't parse this device list: %s", pvs)
473 pv_names.append(m.group(1))
474 assert len(pv_names) > 0
475
476 return (status, major, minor, pe_size, stripes, pv_names)
477
478 @classmethod
480 """Get info about the given existing LV to be used.
481
482 """
483 sep = "|"
484 result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
485 "--units=k", "--nosuffix",
486 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
487 "vg_extent_size,stripes,devices", dev_path])
488 if result.failed:
489 base.ThrowError("Can't find LV %s: %s, %s",
490 dev_path, result.fail_reason, result.output)
491
492
493
494
495
496 out = result.stdout.splitlines()
497 if not out:
498
499 base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
500 pv_names = set()
501 for line in out:
502 (status, major, minor, pe_size, stripes, more_pvs) = \
503 cls._ParseLvInfoLine(line, sep)
504 pv_names.update(more_pvs)
505 return (status, major, minor, pe_size, stripes, pv_names)
506
508 """Attach to an existing LV.
509
510 This method will try to see if an existing and active LV exists
511 which matches our name. If so, its major/minor will be
512 recorded.
513
514 """
515 self.attached = False
516 try:
517 (status, major, minor, pe_size, stripes, pv_names) = \
518 self._GetLvInfo(self.dev_path)
519 except errors.BlockDeviceError:
520 return False
521
522 self.major = major
523 self.minor = minor
524 self.pe_size = pe_size
525 self.stripe_count = stripes
526 self._degraded = status[0] == "v"
527
528 self.pv_names = pv_names
529 self.attached = True
530 return True
531
533 """Assemble the device.
534
535 We always run `lvchange -ay` on the LV to ensure it's active before
536 use, as there were cases when xenvg was not active after boot
537 (also possibly after disk issues).
538
539 """
540 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
541 if result.failed:
542 base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
543
545 """Shutdown the device.
546
547 This is a no-op for the LV device type, as we don't deactivate the
548 volumes on shutdown.
549
550 """
551 pass
552
554 """Returns the sync status of the device.
555
556 If this device is a mirroring device, this function returns the
557 status of the mirror.
558
559 For logical volumes, sync_percent and estimated_time are always
560 None (no recovery in progress, as we don't handle the mirrored LV
561 case). The is_degraded parameter is the inverse of the ldisk
562 parameter.
563
564 For the ldisk parameter, we check if the logical volume has the
565 'virtual' type, which means it's not backed by existing storage
566 anymore (read from it return I/O error). This happens after a
567 physical disk failure and subsequent 'vgreduce --removemissing' on
568 the volume group.
569
570 The status was already read in Attach, so we just return it.
571
572 @rtype: objects.BlockDevStatus
573
574 """
575 if self._degraded:
576 ldisk_status = constants.LDS_FAULTY
577 else:
578 ldisk_status = constants.LDS_OKAY
579
580 return objects.BlockDevStatus(dev_path=self.dev_path,
581 major=self.major,
582 minor=self.minor,
583 sync_percent=None,
584 estimated_time=None,
585 is_degraded=self._degraded,
586 ldisk_status=ldisk_status)
587
588 - def Open(self, force=False):
589 """Make the device ready for I/O.
590
591 This is a no-op for the LV device type.
592
593 """
594 pass
595
597 """Notifies that the device will no longer be used for I/O.
598
599 This is a no-op for the LV device type.
600
601 """
602 pass
603
605 """Create a snapshot copy of an lvm block device.
606
607 @returns: tuple (vg, lv)
608
609 """
610 snap_name = self._lv_name + ".snap"
611
612
613 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params,
614 self.dyn_params)
615 base.IgnoreError(snap.Remove)
616
617 vg_info = self.GetVGInfo([self._vg_name], False)
618 if not vg_info:
619 base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
620 free_size, _, _ = vg_info[0]
621 if free_size < size:
622 base.ThrowError("Not enough free space: required %s,"
623 " available %s", size, free_size)
624
625 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
626 "-n%s" % snap_name, self.dev_path]))
627
628 return (self._vg_name, snap_name)
629
631 """Try to remove old tags from the lv.
632
633 """
634 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
635 self.dev_path])
636 _CheckResult(result)
637
638 raw_tags = result.stdout.strip()
639 if raw_tags:
640 for tag in raw_tags.split(","):
641 _CheckResult(utils.RunCmd(["lvchange", "--deltag",
642 tag.strip(), self.dev_path]))
643
660
662 """Return how much the disk can grow with exclusive storage.
663
664 @rtype: float
665 @return: available space in Mib
666
667 """
668 pvs_info = self.GetPVInfo([self._vg_name])
669 if not pvs_info:
670 base.ThrowError("Cannot get information about PVs for %s", self.dev_path)
671 std_pv_size = self._GetStdPvSize(pvs_info)
672 free_space = sum(pvi.free - (pvi.size - std_pv_size)
673 for pvi in pvs_info
674 if pvi.name in self.pv_names)
675 return free_space
676
677 - def Grow(self, amount, dryrun, backingstore, excl_stor):
678 """Grow the logical volume.
679
680 """
681 if not backingstore:
682 return
683 if self.pe_size is None or self.stripe_count is None:
684 if not self.Attach():
685 base.ThrowError("Can't attach to LV during Grow()")
686 full_stripe_size = self.pe_size * self.stripe_count
687
688 amount *= 1024
689 rest = amount % full_stripe_size
690 if rest != 0:
691 amount += full_stripe_size - rest
692 cmd = ["lvextend", "-L", "+%dk" % amount]
693 if dryrun:
694 cmd.append("--test")
695 if excl_stor:
696 free_space = self._GetGrowthAvaliabilityExclStor()
697
698 if amount > free_space * 1024:
699 base.ThrowError("Not enough free space to grow %s: %d MiB required,"
700 " %d available", self.dev_path, amount / 1024,
701 free_space)
702
703
704 pvlist = list(self.pv_names)
705 else:
706 pvlist = []
707
708
709
710
711 for alloc_policy in "contiguous", "cling", "normal":
712 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path] +
713 pvlist)
714 if not result.failed:
715 return
716 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
717
719 """Return the number of spindles used.
720
721 """
722 assert self.attached, "BlockDevice not attached in GetActualSpindles()"
723 return len(self.pv_names)
724
727 """A block device with persistent node
728
729 May be either directly attached, or exposed through DM (e.g. dm-multipath).
730 udev helpers are probably required to give persistent, human-friendly
731 names.
732
733 For the time being, pathnames are required to lie under /dev.
734
735 """
736 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
737 """Attaches to a static block device.
738
739 The unique_id is a path under /dev.
740
741 """
742 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
743 params, dyn_params, *args)
744 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
745 raise ValueError("Invalid configuration data %s" % str(unique_id))
746 self.dev_path = unique_id[1]
747 if not os.path.realpath(self.dev_path).startswith("/dev/"):
748 raise ValueError("Full path '%s' lies outside /dev" %
749 os.path.realpath(self.dev_path))
750
751
752
753
754 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
755 raise ValueError("Got persistent block device of invalid type: %s" %
756 unique_id[0])
757
758 self.major = self.minor = None
759 self.Attach()
760
761 @classmethod
762 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
763 dyn_params, *args):
764 """Create a new device
765
766 This is a noop, we only return a PersistentBlockDevice instance
767
768 """
769 if excl_stor:
770 raise errors.ProgrammerError("Persistent block device requested with"
771 " exclusive_storage")
772 return PersistentBlockDevice(unique_id, children, 0, params, dyn_params,
773 *args)
774
776 """Remove a device
777
778 This is a noop
779
780 """
781 pass
782
784 """Rename this device.
785
786 """
787 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
788
790 """Attach to an existing block device.
791
792
793 """
794 self.attached = False
795 try:
796 st = os.stat(self.dev_path)
797 except OSError, err:
798 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
799 return False
800
801 if not stat.S_ISBLK(st.st_mode):
802 logging.error("%s is not a block device", self.dev_path)
803 return False
804
805 self.major = os.major(st.st_rdev)
806 self.minor = utils.osminor(st.st_rdev)
807 self.attached = True
808
809 return True
810
812 """Assemble the device.
813
814 """
815 pass
816
818 """Shutdown the device.
819
820 """
821 pass
822
823 - def Open(self, force=False):
824 """Make the device ready for I/O.
825
826 """
827 pass
828
830 """Notifies that the device will no longer be used for I/O.
831
832 """
833 pass
834
835 - def Grow(self, amount, dryrun, backingstore, excl_stor):
836 """Grow the logical volume.
837
838 """
839 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
840
843 """A RADOS Block Device (rbd).
844
845 This class implements the RADOS Block Device for the backend. You need
846 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
847 this to be functional.
848
849 """
850 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
851 """Attaches to an rbd device.
852
853 """
854 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params,
855 dyn_params, *args)
856 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
857 raise ValueError("Invalid configuration data %s" % str(unique_id))
858
859 self.driver, self.rbd_name = unique_id
860 self.rbd_pool = params[constants.LDP_POOL]
861
862 self.major = self.minor = None
863 self.Attach()
864
865 @classmethod
866 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
867 dyn_params, *args):
868 """Create a new rbd device.
869
870 Provision a new rbd volume inside a RADOS pool.
871
872 """
873 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
874 raise errors.ProgrammerError("Invalid configuration data %s" %
875 str(unique_id))
876 if excl_stor:
877 raise errors.ProgrammerError("RBD device requested with"
878 " exclusive_storage")
879 rbd_pool = params[constants.LDP_POOL]
880 rbd_name = unique_id[1]
881
882
883 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
884 rbd_name, "--size", "%s" % size]
885 result = utils.RunCmd(cmd)
886 if result.failed:
887 base.ThrowError("rbd creation failed (%s): %s",
888 result.fail_reason, result.output)
889
890 return RADOSBlockDevice(unique_id, children, size, params, dyn_params,
891 *args)
892
894 """Remove the rbd device.
895
896 """
897 rbd_pool = self.params[constants.LDP_POOL]
898 rbd_name = self.unique_id[1]
899
900 if not self.minor and not self.Attach():
901
902 return
903
904
905 self.Shutdown()
906
907
908 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
909 result = utils.RunCmd(cmd)
910 if result.failed:
911 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
912 result.fail_reason, result.output)
913
915 """Rename this device.
916
917 """
918 pass
919
921 """Attach to an existing rbd device.
922
923 This method maps the rbd volume that matches our name with
924 an rbd device and then attaches to this device.
925
926 """
927 self.attached = False
928
929
930 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
931
932 try:
933 st = os.stat(self.dev_path)
934 except OSError, err:
935 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
936 return False
937
938 if not stat.S_ISBLK(st.st_mode):
939 logging.error("%s is not a block device", self.dev_path)
940 return False
941
942 self.major = os.major(st.st_rdev)
943 self.minor = utils.osminor(st.st_rdev)
944 self.attached = True
945
946 return True
947
949 """Maps existing rbd volumes to block devices.
950
951 This method should be idempotent if the mapping already exists.
952
953 @rtype: string
954 @return: the block device path that corresponds to the volume
955
956 """
957 pool = self.params[constants.LDP_POOL]
958 name = unique_id[1]
959
960
961 rbd_dev = self._VolumeToBlockdev(pool, name)
962 if rbd_dev:
963
964 return rbd_dev
965
966
967 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
968 result = utils.RunCmd(map_cmd)
969 if result.failed:
970 base.ThrowError("rbd map failed (%s): %s",
971 result.fail_reason, result.output)
972
973
974 rbd_dev = self._VolumeToBlockdev(pool, name)
975 if not rbd_dev:
976 base.ThrowError("rbd map succeeded, but could not find the rbd block"
977 " device in output of showmapped, for volume: %s", name)
978
979
980 return rbd_dev
981
982 @classmethod
984 """Do the 'volume name'-to-'rbd block device' resolving.
985
986 @type pool: string
987 @param pool: RADOS pool to use
988 @type volume_name: string
989 @param volume_name: the name of the volume whose device we search for
990 @rtype: string or None
991 @return: block device path if the volume is mapped, else None
992
993 """
994 try:
995
996
997 showmap_cmd = [
998 constants.RBD_CMD,
999 "showmapped",
1000 "-p",
1001 pool,
1002 "--format",
1003 "json"
1004 ]
1005 result = utils.RunCmd(showmap_cmd)
1006 if result.failed:
1007 logging.error("rbd JSON output formatting returned error (%s): %s,"
1008 "falling back to plain output parsing",
1009 result.fail_reason, result.output)
1010 raise RbdShowmappedJsonError
1011
1012 return cls._ParseRbdShowmappedJson(result.output, volume_name)
1013 except RbdShowmappedJsonError:
1014
1015
1016 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
1017 result = utils.RunCmd(showmap_cmd)
1018 if result.failed:
1019 base.ThrowError("rbd showmapped failed (%s): %s",
1020 result.fail_reason, result.output)
1021
1022 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1023
1024 @staticmethod
1026 """Parse the json output of `rbd showmapped'.
1027
1028 This method parses the json output of `rbd showmapped' and returns the rbd
1029 block device path (e.g. /dev/rbd0) that matches the given rbd volume.
1030
1031 @type output: string
1032 @param output: the json output of `rbd showmapped'
1033 @type volume_name: string
1034 @param volume_name: the name of the volume whose device we search for
1035 @rtype: string or None
1036 @return: block device path if the volume is mapped, else None
1037
1038 """
1039 try:
1040 devices = serializer.LoadJson(output)
1041 except ValueError, err:
1042 base.ThrowError("Unable to parse JSON data: %s" % err)
1043
1044 rbd_dev = None
1045 for d in devices.values():
1046 try:
1047 name = d["name"]
1048 except KeyError:
1049 base.ThrowError("'name' key missing from json object %s", devices)
1050
1051 if name == volume_name:
1052 if rbd_dev is not None:
1053 base.ThrowError("rbd volume %s is mapped more than once", volume_name)
1054
1055 rbd_dev = d["device"]
1056
1057 return rbd_dev
1058
1059 @staticmethod
1061 """Parse the (plain / text) output of `rbd showmapped'.
1062
1063 This method parses the output of `rbd showmapped' and returns
1064 the rbd block device path (e.g. /dev/rbd0) that matches the
1065 given rbd volume.
1066
1067 @type output: string
1068 @param output: the plain text output of `rbd showmapped'
1069 @type volume_name: string
1070 @param volume_name: the name of the volume whose device we search for
1071 @rtype: string or None
1072 @return: block device path if the volume is mapped, else None
1073
1074 """
1075 allfields = 5
1076 volumefield = 2
1077 devicefield = 4
1078
1079 lines = output.splitlines()
1080
1081
1082 splitted_lines = map(lambda l: l.split(), lines)
1083
1084
1085 if not splitted_lines:
1086 return None
1087
1088
1089 field_cnt = len(splitted_lines[0])
1090 if field_cnt != allfields:
1091
1092
1093 splitted_lines = map(lambda l: l.split("\t"), lines)
1094 if field_cnt != allfields:
1095 base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
1096 " found %s", allfields, field_cnt)
1097
1098 matched_lines = \
1099 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
1100 splitted_lines)
1101
1102 if len(matched_lines) > 1:
1103 base.ThrowError("rbd volume %s mapped more than once", volume_name)
1104
1105 if matched_lines:
1106
1107 rbd_dev = matched_lines[0][devicefield]
1108 return rbd_dev
1109
1110
1111 return None
1112
1114 """Assemble the device.
1115
1116 """
1117 pass
1118
1120 """Shutdown the device.
1121
1122 """
1123 if not self.minor and not self.Attach():
1124
1125 return
1126
1127
1128 self._UnmapVolumeFromBlockdev(self.unique_id)
1129
1130 self.minor = None
1131 self.dev_path = None
1132
1134 """Unmaps the rbd device from the Volume it is mapped.
1135
1136 Unmaps the rbd device from the Volume it was previously mapped to.
1137 This method should be idempotent if the Volume isn't mapped.
1138
1139 """
1140 pool = self.params[constants.LDP_POOL]
1141 name = unique_id[1]
1142
1143
1144 rbd_dev = self._VolumeToBlockdev(pool, name)
1145
1146 if rbd_dev:
1147
1148 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
1149 result = utils.RunCmd(unmap_cmd)
1150 if result.failed:
1151 base.ThrowError("rbd unmap failed (%s): %s",
1152 result.fail_reason, result.output)
1153
1154 - def Open(self, force=False):
1155 """Make the device ready for I/O.
1156
1157 """
1158 pass
1159
1161 """Notifies that the device will no longer be used for I/O.
1162
1163 """
1164 pass
1165
1166 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1167 """Grow the Volume.
1168
1169 @type amount: integer
1170 @param amount: the amount (in mebibytes) to grow with
1171 @type dryrun: boolean
1172 @param dryrun: whether to execute the operation in simulation mode
1173 only, without actually increasing the size
1174
1175 """
1176 if not backingstore:
1177 return
1178 if not self.Attach():
1179 base.ThrowError("Can't attach to rbd device during Grow()")
1180
1181 if dryrun:
1182
1183
1184
1185 return
1186
1187 rbd_pool = self.params[constants.LDP_POOL]
1188 rbd_name = self.unique_id[1]
1189 new_size = self.size + amount
1190
1191
1192 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
1193 rbd_name, "--size", "%s" % new_size]
1194 result = utils.RunCmd(cmd)
1195 if result.failed:
1196 base.ThrowError("rbd resize failed (%s): %s",
1197 result.fail_reason, result.output)
1198
1200 """Generate KVM userspace URIs to be used as `-drive file` settings.
1201
1202 @see: L{BlockDev.GetUserspaceAccessUri}
1203
1204 """
1205 if hypervisor == constants.HT_KVM:
1206 return "rbd:" + self.rbd_pool + "/" + self.rbd_name
1207 else:
1208 base.ThrowError("Hypervisor %s doesn't support RBD userspace access" %
1209 hypervisor)
1210
1213 """A block device provided by an ExtStorage Provider.
1214
1215 This class implements the External Storage Interface, which means
1216 handling of the externally provided block devices.
1217
1218 """
1219 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
1220 """Attaches to an extstorage block device.
1221
1222 """
1223 super(ExtStorageDevice, self).__init__(unique_id, children, size, params,
1224 dyn_params, *args)
1225 (self.name, self.uuid) = args
1226
1227 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1228 raise ValueError("Invalid configuration data %s" % str(unique_id))
1229
1230 self.driver, self.vol_name = unique_id
1231 self.ext_params = params
1232
1233 self.major = self.minor = None
1234 self.Attach()
1235
1236 @classmethod
1237 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
1238 dyn_params, *args):
1239 """Create a new extstorage device.
1240
1241 Provision a new volume using an extstorage provider, which will
1242 then be mapped to a block device.
1243
1244 """
1245 (name, uuid) = args
1246
1247 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1248 raise errors.ProgrammerError("Invalid configuration data %s" %
1249 str(unique_id))
1250 if excl_stor:
1251 raise errors.ProgrammerError("extstorage device requested with"
1252 " exclusive_storage")
1253
1254
1255
1256 _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
1257 params, size=str(size), name=name, uuid=uuid)
1258
1259 return ExtStorageDevice(unique_id, children, size, params, dyn_params,
1260 *args)
1261
1277
1279 """Rename this device.
1280
1281 """
1282 pass
1283
1285 """Attach to an existing extstorage device.
1286
1287 This method maps the extstorage volume that matches our name with
1288 a corresponding block device and then attaches to this device.
1289
1290 """
1291 self.attached = False
1292
1293
1294
1295 self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
1296 self.unique_id, self.ext_params,
1297 name=self.name, uuid=self.uuid)
1298
1299 try:
1300 st = os.stat(self.dev_path)
1301 except OSError, err:
1302 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
1303 return False
1304
1305 if not stat.S_ISBLK(st.st_mode):
1306 logging.error("%s is not a block device", self.dev_path)
1307 return False
1308
1309 self.major = os.major(st.st_rdev)
1310 self.minor = utils.osminor(st.st_rdev)
1311 self.attached = True
1312
1313 return True
1314
1316 """Assemble the device.
1317
1318 """
1319 pass
1320
1322 """Shutdown the device.
1323
1324 """
1325 if not self.minor and not self.Attach():
1326
1327 return
1328
1329
1330
1331 _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
1332 self.ext_params, name=self.name, uuid=self.uuid)
1333
1334 self.minor = None
1335 self.dev_path = None
1336
1337 - def Open(self, force=False):
1338 """Make the device ready for I/O.
1339
1340 """
1341 pass
1342
1344 """Notifies that the device will no longer be used for I/O.
1345
1346 """
1347 pass
1348
1349 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1350 """Grow the Volume.
1351
1352 @type amount: integer
1353 @param amount: the amount (in mebibytes) to grow with
1354 @type dryrun: boolean
1355 @param dryrun: whether to execute the operation in simulation mode
1356 only, without actually increasing the size
1357
1358 """
1359 if not backingstore:
1360 return
1361 if not self.Attach():
1362 base.ThrowError("Can't attach to extstorage device during Grow()")
1363
1364 if dryrun:
1365
1366 return
1367
1368 new_size = self.size + amount
1369
1370
1371
1372 _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
1373 self.ext_params, size=str(self.size), grow=str(new_size),
1374 name=self.name, uuid=self.uuid)
1375
1392
1393
1394 -def _ExtStorageAction(action, unique_id, ext_params,
1395 size=None, grow=None, metadata=None,
1396 name=None, uuid=None):
1397 """Take an External Storage action.
1398
1399 Take an External Storage action concerning or affecting
1400 a specific Volume inside the External Storage.
1401
1402 @type action: string
1403 @param action: which action to perform. One of:
1404 create / remove / grow / attach / detach
1405 @type unique_id: tuple (driver, vol_name)
1406 @param unique_id: a tuple containing the type of ExtStorage (driver)
1407 and the Volume name
1408 @type ext_params: dict
1409 @param ext_params: ExtStorage parameters
1410 @type size: integer
1411 @param size: the size of the Volume in mebibytes
1412 @type grow: integer
1413 @param grow: the new size in mebibytes (after grow)
1414 @type metadata: string
1415 @param metadata: metadata info of the Volume, for use by the provider
1416 @type name: string
1417 @param name: name of the Volume (objects.Disk.name)
1418 @type uuid: string
1419 @param uuid: uuid of the Volume (objects.Disk.uuid)
1420 @rtype: None or a block device path (during attach)
1421
1422 """
1423 driver, vol_name = unique_id
1424
1425
1426 status, inst_es = ExtStorageFromDisk(driver)
1427 if not status:
1428 base.ThrowError("%s" % inst_es)
1429
1430
1431 create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
1432 grow, metadata, name, uuid)
1433
1434
1435
1436
1437 logfile = None
1438 if action is not constants.ES_ACTION_ATTACH:
1439 logfile = _VolumeLogName(action, driver, vol_name)
1440
1441
1442 if action not in constants.ES_SCRIPTS:
1443 base.ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
1444 action)
1445
1446
1447 script_name = action + "_script"
1448 script = getattr(inst_es, script_name)
1449
1450
1451 result = utils.RunCmd([script], env=create_env,
1452 cwd=inst_es.path, output=logfile,)
1453 if result.failed:
1454 logging.error("External storage's %s command '%s' returned"
1455 " error: %s, logfile: %s, output: %s",
1456 action, result.cmd, result.fail_reason,
1457 logfile, result.output)
1458
1459
1460
1461 if action is not constants.ES_ACTION_ATTACH:
1462 lines = [utils.SafeEncode(val)
1463 for val in utils.TailFile(logfile, lines=20)]
1464 else:
1465 lines = result.output[-20:]
1466
1467 base.ThrowError("External storage's %s script failed (%s), last"
1468 " lines of output:\n%s",
1469 action, result.fail_reason, "\n".join(lines))
1470
1471 if action == constants.ES_ACTION_ATTACH:
1472 return result.stdout
1473
1476 """Create an ExtStorage instance from disk.
1477
1478 This function will return an ExtStorage instance
1479 if the given name is a valid ExtStorage name.
1480
1481 @type base_dir: string
1482 @keyword base_dir: Base directory containing ExtStorage installations.
1483 Defaults to a search in all the ES_SEARCH_PATH dirs.
1484 @rtype: tuple
1485 @return: True and the ExtStorage instance if we find a valid one, or
1486 False and the diagnose message on error
1487
1488 """
1489 if base_dir is None:
1490 es_base_dir = pathutils.ES_SEARCH_PATH
1491 else:
1492 es_base_dir = [base_dir]
1493
1494 es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
1495
1496 if es_dir is None:
1497 return False, ("Directory for External Storage Provider %s not"
1498 " found in search path" % name)
1499
1500
1501
1502
1503 es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
1504
1505 es_files[constants.ES_PARAMETERS_FILE] = True
1506
1507 for (filename, _) in es_files.items():
1508 es_files[filename] = utils.PathJoin(es_dir, filename)
1509
1510 try:
1511 st = os.stat(es_files[filename])
1512 except EnvironmentError, err:
1513 return False, ("File '%s' under path '%s' is missing (%s)" %
1514 (filename, es_dir, utils.ErrnoOrStr(err)))
1515
1516 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1517 return False, ("File '%s' under path '%s' is not a regular file" %
1518 (filename, es_dir))
1519
1520 if filename in constants.ES_SCRIPTS:
1521 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
1522 return False, ("File '%s' under path '%s' is not executable" %
1523 (filename, es_dir))
1524
1525 parameters = []
1526 if constants.ES_PARAMETERS_FILE in es_files:
1527 parameters_file = es_files[constants.ES_PARAMETERS_FILE]
1528 try:
1529 parameters = utils.ReadFile(parameters_file).splitlines()
1530 except EnvironmentError, err:
1531 return False, ("Error while reading the EXT parameters file at %s: %s" %
1532 (parameters_file, utils.ErrnoOrStr(err)))
1533 parameters = [v.split(None, 1) for v in parameters]
1534
1535 es_obj = \
1536 objects.ExtStorage(name=name, path=es_dir,
1537 create_script=es_files[constants.ES_SCRIPT_CREATE],
1538 remove_script=es_files[constants.ES_SCRIPT_REMOVE],
1539 grow_script=es_files[constants.ES_SCRIPT_GROW],
1540 attach_script=es_files[constants.ES_SCRIPT_ATTACH],
1541 detach_script=es_files[constants.ES_SCRIPT_DETACH],
1542 setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
1543 verify_script=es_files[constants.ES_SCRIPT_VERIFY],
1544 supported_parameters=parameters)
1545 return True, es_obj
1546
1547
1548 -def _ExtStorageEnvironment(unique_id, ext_params,
1549 size=None, grow=None, metadata=None,
1550 name=None, uuid=None):
1551 """Calculate the environment for an External Storage script.
1552
1553 @type unique_id: tuple (driver, vol_name)
1554 @param unique_id: ExtStorage pool and name of the Volume
1555 @type ext_params: dict
1556 @param ext_params: the EXT parameters
1557 @type size: string
1558 @param size: size of the Volume (in mebibytes)
1559 @type grow: string
1560 @param grow: new size of Volume after grow (in mebibytes)
1561 @type metadata: string
1562 @param metadata: metadata info of the Volume
1563 @type name: string
1564 @param name: name of the Volume (objects.Disk.name)
1565 @type uuid: string
1566 @param uuid: uuid of the Volume (objects.Disk.uuid)
1567 @rtype: dict
1568 @return: dict of environment variables
1569
1570 """
1571 vol_name = unique_id[1]
1572
1573 result = {}
1574 result["VOL_NAME"] = vol_name
1575
1576
1577 for pname, pvalue in ext_params.items():
1578 result["EXTP_%s" % pname.upper()] = str(pvalue)
1579
1580 if size is not None:
1581 result["VOL_SIZE"] = size
1582
1583 if grow is not None:
1584 result["VOL_NEW_SIZE"] = grow
1585
1586 if metadata is not None:
1587 result["VOL_METADATA"] = metadata
1588
1589 if name is not None:
1590 result["VOL_CNAME"] = name
1591
1592 if uuid is not None:
1593 result["VOL_UUID"] = uuid
1594
1595 return result
1596
1599 """Compute the ExtStorage log filename for a given Volume and operation.
1600
1601 @type kind: string
1602 @param kind: the operation type (e.g. create, remove etc.)
1603 @type es_name: string
1604 @param es_name: the ExtStorage name
1605 @type volume: string
1606 @param volume: the name of the Volume inside the External Storage
1607
1608 """
1609
1610 if not os.path.isdir(pathutils.LOG_ES_DIR):
1611 base.ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
1612
1613
1614 basename = ("%s-%s-%s-%s.log" %
1615 (kind, es_name, volume, utils.TimestampForFilename()))
1616 return utils.PathJoin(pathutils.LOG_ES_DIR, basename)
1617
1622
1625 """Verifies if all disk parameters are set.
1626
1627 """
1628 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
1629 if missing:
1630 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
1631 missing)
1632
1635 """Search for an existing, assembled device.
1636
1637 This will succeed only if the device exists and is assembled, but it
1638 does not do any actions in order to activate the device.
1639
1640 @type disk: L{objects.Disk}
1641 @param disk: the disk object to find
1642 @type children: list of L{bdev.BlockDev}
1643 @param children: the list of block devices that are children of the device
1644 represented by the disk parameter
1645
1646 """
1647 _VerifyDiskType(disk.dev_type)
1648 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
1649 disk.params, disk.dynamic_params,
1650 disk.name, disk.uuid)
1651 if not device.attached:
1652 return None
1653 return device
1654
1657 """Try to attach or assemble an existing device.
1658
1659 This will attach to assemble the device, as needed, to bring it
1660 fully up. It must be safe to run on already-assembled devices.
1661
1662 @type disk: L{objects.Disk}
1663 @param disk: the disk object to assemble
1664 @type children: list of L{bdev.BlockDev}
1665 @param children: the list of block devices that are children of the device
1666 represented by the disk parameter
1667
1668 """
1669 _VerifyDiskType(disk.dev_type)
1670 _VerifyDiskParams(disk)
1671 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
1672 disk.params, disk.dynamic_params,
1673 disk.name, disk.uuid)
1674 device.Assemble()
1675 return device
1676
1677
1678 -def Create(disk, children, excl_stor):
1679 """Create a device.
1680
1681 @type disk: L{objects.Disk}
1682 @param disk: the disk object to create
1683 @type children: list of L{bdev.BlockDev}
1684 @param children: the list of block devices that are children of the device
1685 represented by the disk parameter
1686 @type excl_stor: boolean
1687 @param excl_stor: Whether exclusive_storage is active
1688 @rtype: L{bdev.BlockDev}
1689 @return: the created device, or C{None} in case of an error
1690
1691 """
1692 _VerifyDiskType(disk.dev_type)
1693 _VerifyDiskParams(disk)
1694 device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size,
1695 disk.spindles, disk.params, excl_stor,
1696 disk.dynamic_params,
1697 disk.name, disk.uuid)
1698 return device
1699
1700
1701 DEV_MAP = {
1702 constants.DT_PLAIN: LogicalVolume,
1703 constants.DT_DRBD8: drbd.DRBD8Dev,
1704 constants.DT_BLOCK: PersistentBlockDevice,
1705 constants.DT_RBD: RADOSBlockDevice,
1706 constants.DT_EXT: ExtStorageDevice,
1707 constants.DT_FILE: FileStorage,
1708 constants.DT_SHARED_FILE: FileStorage,
1709 constants.DT_GLUSTER: GlusterStorage,
1710 }
1711 """Map disk types to disk type classes.
1712
1713 @see: L{Assemble}, L{FindDevice}, L{Create}."""
1714