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