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