1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Block device abstraction.
32
33 """
34
35 import re
36 import stat
37 import os
38 import logging
39 import math
40
41 from ganeti import utils
42 from ganeti import errors
43 from ganeti import constants
44 from ganeti import objects
45 from ganeti import compat
46 from ganeti import serializer
47 from ganeti.storage import base
48 from ganeti.storage import drbd
49 from ganeti.storage.filestorage import FileStorage
50 from ganeti.storage.gluster import GlusterStorage
51 from ganeti.storage.extstorage import ExtStorageDevice
55 """`rbd showmmapped' JSON formatting error Exception class.
56
57 """
58 pass
59
62 """Throws an error if the given result is a failed one.
63
64 @param result: result from RunCmd
65
66 """
67 if result.failed:
68 base.ThrowError("Command: %s error: %s - %s",
69 result.cmd, result.fail_reason, result.output)
70
73 """Logical Volume block device.
74
75 """
76 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
77 _PARSE_PV_DEV_RE = re.compile(r"^([^ ()]+)\([0-9]+\)$")
78 _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
79 _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
80
81 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
82 """Attaches to a LV device.
83
84 The unique_id is a tuple (vg_name, lv_name)
85
86 """
87 super(LogicalVolume, self).__init__(unique_id, children, size, params,
88 dyn_params, *args)
89 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
90 raise ValueError("Invalid configuration data %s" % str(unique_id))
91 self._vg_name, self._lv_name = unique_id
92 self._ValidateName(self._vg_name)
93 self._ValidateName(self._lv_name)
94 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
95 self._degraded = True
96 self.major = self.minor = self.pe_size = self.stripe_count = None
97 self.pv_names = None
98 self.Attach()
99
100 @staticmethod
102 """Return the the standard PV size (used with exclusive storage).
103
104 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
105 @rtype: float
106 @return: size in MiB
107
108 """
109 assert len(pvs_info) > 0
110 smallest = min([pv.size for pv in pvs_info])
111 return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
112
113 @staticmethod
115 """Compute the number of PVs needed for an LV (with exclusive storage).
116
117 @type size: float
118 @param size: LV size in MiB
119 @param pvs_info: list of objects.LvmPvInfo, cannot be empty
120 @rtype: integer
121 @return: number of PVs needed
122 """
123 assert len(pvs_info) > 0
124 pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
125 return int(math.ceil(float(size) / pv_size))
126
127 @staticmethod
129 """Return a list of empty PVs, by name.
130
131 """
132 empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
133 if max_pvs is not None:
134 empty_pvs = empty_pvs[:max_pvs]
135 return map((lambda pv: pv.name), empty_pvs)
136
137 @classmethod
138 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
139 dyn_params, *args):
140 """Create a new logical volume.
141
142 """
143 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
144 raise errors.ProgrammerError("Invalid configuration data %s" %
145 str(unique_id))
146 vg_name, lv_name = unique_id
147 cls._ValidateName(vg_name)
148 cls._ValidateName(lv_name)
149 pvs_info = cls.GetPVInfo([vg_name])
150 if not pvs_info:
151 if excl_stor:
152 msg = "No (empty) PVs found"
153 else:
154 msg = "Can't compute PV info for vg %s" % vg_name
155 base.ThrowError(msg)
156 pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
157
158 pvlist = [pv.name for pv in pvs_info]
159 if compat.any(":" in v for v in pvlist):
160 base.ThrowError("Some of your PVs have the invalid character ':' in their"
161 " name, this is not supported - please filter them out"
162 " in lvm.conf using either 'filter' or 'preferred_names'")
163
164 current_pvs = len(pvlist)
165 desired_stripes = params[constants.LDP_STRIPES]
166 stripes = min(current_pvs, desired_stripes)
167
168 if excl_stor:
169 if spindles is None:
170 base.ThrowError("Unspecified number of spindles: this is required"
171 "when exclusive storage is enabled, try running"
172 " gnt-cluster repair-disk-sizes")
173 (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
174 if err_msgs:
175 for m in err_msgs:
176 logging.warning(m)
177 req_pvs = cls._ComputeNumPvs(size, pvs_info)
178 if spindles < req_pvs:
179 base.ThrowError("Requested number of spindles (%s) is not enough for"
180 " a disk of %d MB (at least %d spindles needed)",
181 spindles, size, req_pvs)
182 else:
183 req_pvs = spindles
184 pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
185 current_pvs = len(pvlist)
186 if current_pvs < req_pvs:
187 base.ThrowError("Not enough empty PVs (spindles) to create a disk of %d"
188 " MB: %d available, %d needed",
189 size, current_pvs, req_pvs)
190 assert current_pvs == len(pvlist)
191
192 stripes = current_pvs
193 if stripes > desired_stripes:
194
195 logging.warning("Using %s stripes instead of %s, to be able to use"
196 " %s spindles", stripes, desired_stripes, current_pvs)
197
198 else:
199 if stripes < desired_stripes:
200 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
201 " available.", desired_stripes, vg_name, current_pvs)
202 free_size = sum([pv.free for pv in pvs_info])
203
204
205 if free_size < size:
206 base.ThrowError("Not enough free space: required %s,"
207 " available %s", size, free_size)
208
209
210
211
212
213 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
214 for stripes_arg in range(stripes, 0, -1):
215 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
216 if not result.failed:
217 break
218 if result.failed:
219 base.ThrowError("LV create failed (%s): %s",
220 result.fail_reason, result.output)
221 return LogicalVolume(unique_id, children, size, params, dyn_params, *args)
222
223 @staticmethod
225 """Returns LVM Volume infos using lvm_cmd
226
227 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
228 @param fields: Fields to return
229 @return: A list of dicts each with the parsed fields
230
231 """
232 if not fields:
233 raise errors.ProgrammerError("No fields specified")
234
235 sep = "|"
236 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
237 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
238
239 result = utils.RunCmd(cmd)
240 if result.failed:
241 raise errors.CommandError("Can't get the volume information: %s - %s" %
242 (result.fail_reason, result.output))
243
244 data = []
245 for line in result.stdout.splitlines():
246 splitted_fields = line.strip().split(sep)
247
248 if len(fields) != len(splitted_fields):
249 raise errors.CommandError("Can't parse %s output: line '%s'" %
250 (lvm_cmd, line))
251
252 data.append(splitted_fields)
253
254 return data
255
256 @classmethod
257 - def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
258 """Get the free space info for PVs in a volume group.
259
260 @param vg_names: list of volume group names, if empty all will be returned
261 @param filter_allocatable: whether to skip over unallocatable PVs
262 @param include_lvs: whether to include a list of LVs hosted on each PV
263
264 @rtype: list
265 @return: list of objects.LvmPvInfo objects
266
267 """
268
269
270
271 if include_lvs:
272 lvfield = "lv_name"
273 else:
274 lvfield = "pv_name"
275 try:
276 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
277 "pv_attr", "pv_size", lvfield])
278 except errors.GenericError, err:
279 logging.error("Can't get PV information: %s", err)
280 return None
281
282
283
284
285 if include_lvs:
286 info.sort(key=(lambda i: (i[0], i[5])))
287 data = []
288 lastpvi = None
289 for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
290
291 if filter_allocatable and pv_attr[0] != "a":
292 continue
293
294 if vg_names and vg_name not in vg_names:
295 continue
296
297 if lastpvi and lastpvi.name == pv_name:
298 if include_lvs and lv_name:
299 if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
300 lastpvi.lv_list.append(lv_name)
301 else:
302 if include_lvs and lv_name:
303 lvl = [lv_name]
304 else:
305 lvl = []
306 lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
307 size=float(pv_size), free=float(pv_free),
308 attributes=pv_attr, lv_list=lvl)
309 data.append(lastpvi)
310
311 return data
312
313 @classmethod
315 """Return info (size/free) about PVs.
316
317 @type vg_name: string
318 @param vg_name: VG name
319 @rtype: tuple
320 @return: (standard_pv_size_in_MiB, number_of_free_pvs, total_number_of_pvs)
321
322 """
323 pvs_info = cls.GetPVInfo([vg_name])
324 if not pvs_info:
325 pv_size = 0.0
326 free_pvs = 0
327 num_pvs = 0
328 else:
329 pv_size = cls._GetStdPvSize(pvs_info)
330 free_pvs = len(cls._GetEmptyPvNames(pvs_info))
331 num_pvs = len(pvs_info)
332 return (pv_size, free_pvs, num_pvs)
333
334 @classmethod
336 """Return the free disk space in the given VG, in exclusive storage mode.
337
338 @type vg_name: string
339 @param vg_name: VG name
340 @rtype: float
341 @return: free space in MiB
342 """
343 (pv_size, free_pvs, _) = cls._GetRawFreePvInfo(vg_name)
344 return pv_size * free_pvs
345
346 @classmethod
348 """Get the free space info for specific VGs.
349
350 @param vg_name: volume group name
351 @rtype: tuple
352 @return: (free_spindles, total_spindles)
353
354 """
355 (_, free_pvs, num_pvs) = cls._GetRawFreePvInfo(vg_name)
356 return (free_pvs, num_pvs)
357
358 @classmethod
359 - def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
360 """Get the free space info for specific VGs.
361
362 @param vg_names: list of volume group names, if empty all will be returned
363 @param excl_stor: whether exclusive_storage is enabled
364 @param filter_readonly: whether to skip over readonly VGs
365
366 @rtype: list
367 @return: list of tuples (free_space, total_size, name) with free_space in
368 MiB
369
370 """
371 try:
372 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
373 "vg_size"])
374 except errors.GenericError, err:
375 logging.error("Can't get VG information: %s", err)
376 return None
377
378 data = []
379 for vg_name, vg_free, vg_attr, vg_size in info:
380
381 if filter_readonly and vg_attr[0] == "r":
382 continue
383
384 if vg_names and vg_name not in vg_names:
385 continue
386
387 if excl_stor:
388 es_free = cls._GetExclusiveStorageVgFree(vg_name)
389 assert es_free <= vg_free
390 vg_free = es_free
391 data.append((float(vg_free), float(vg_size), vg_name))
392
393 return data
394
395 @classmethod
397 """Validates that a given name is valid as VG or LV name.
398
399 The list of valid characters and restricted names is taken out of
400 the lvm(8) manpage, with the simplification that we enforce both
401 VG and LV restrictions on the names.
402
403 """
404 if (not cls._VALID_NAME_RE.match(name) or
405 name in cls._INVALID_NAMES or
406 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
407 base.ThrowError("Invalid LVM name '%s'", name)
408
410 """Remove this logical volume.
411
412 """
413 if not self.minor and not self.Attach():
414
415 return
416 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
417 (self._vg_name, self._lv_name)])
418 if result.failed:
419 base.ThrowError("Can't lvremove: %s - %s",
420 result.fail_reason, result.output)
421
423 """Rename this logical volume.
424
425 """
426 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
427 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
428 new_vg, new_name = new_id
429 if new_vg != self._vg_name:
430 raise errors.ProgrammerError("Can't move a logical volume across"
431 " volume groups (from %s to to %s)" %
432 (self._vg_name, new_vg))
433 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
434 if result.failed:
435 base.ThrowError("Failed to rename the logical volume: %s", result.output)
436 self._lv_name = new_name
437 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
438
439 @classmethod
441 """Parse one line of the lvs output used in L{_GetLvInfo}.
442
443 """
444 elems = line.strip().split(sep)
445
446
447
448
449
450 if len(elems) == 7 and elems[-1] == "":
451 elems.pop()
452
453 if len(elems) != 6:
454 base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
455
456 (status, major, minor, pe_size, stripes, pvs) = elems
457 if len(status) < 6:
458 base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
459
460 try:
461 major = int(major)
462 minor = int(minor)
463 except (TypeError, ValueError), err:
464 base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
465
466 try:
467 pe_size = int(float(pe_size))
468 except (TypeError, ValueError), err:
469 base.ThrowError("Can't parse vg extent size: %s", err)
470
471 try:
472 stripes = int(stripes)
473 except (TypeError, ValueError), err:
474 base.ThrowError("Can't parse the number of stripes: %s", err)
475
476 pv_names = []
477 if pvs != "":
478 for pv in pvs.split(","):
479 m = re.match(cls._PARSE_PV_DEV_RE, pv)
480 if not m:
481 base.ThrowError("Can't parse this device list: %s", pvs)
482 pv_names.append(m.group(1))
483
484 return (status, major, minor, pe_size, stripes, pv_names)
485
486 @classmethod
488 """Get info about the given existing LV to be used.
489
490 """
491 sep = "|"
492 result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
493 "--units=k", "--nosuffix",
494 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
495 "vg_extent_size,stripes,devices", dev_path])
496 if result.failed:
497 base.ThrowError("Can't find LV %s: %s, %s",
498 dev_path, result.fail_reason, result.output)
499
500
501
502
503
504 out = result.stdout.splitlines()
505 if not out:
506
507 base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
508 pv_names = set()
509 for line in out:
510 (status, major, minor, pe_size, stripes, more_pvs) = \
511 cls._ParseLvInfoLine(line, sep)
512 pv_names.update(more_pvs)
513 return (status, major, minor, pe_size, stripes, pv_names)
514
516 """Attach to an existing LV.
517
518 This method will try to see if an existing and active LV exists
519 which matches our name. If so, its major/minor will be
520 recorded.
521
522 """
523 self.attached = False
524 try:
525 (status, major, minor, pe_size, stripes, pv_names) = \
526 self._GetLvInfo(self.dev_path)
527 except errors.BlockDeviceError:
528 return False
529
530 self.major = major
531 self.minor = minor
532 self.pe_size = pe_size
533 self.stripe_count = stripes
534 self._degraded = status[0] == "v"
535
536 self.pv_names = pv_names
537 self.attached = True
538 return True
539
541 """Assemble the device.
542
543 We always run `lvchange -ay` on the LV to ensure it's active before
544 use, as there were cases when xenvg was not active after boot
545 (also possibly after disk issues).
546
547 """
548 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
549 if result.failed:
550 base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
551
553 """Shutdown the device.
554
555 This is a no-op for the LV device type, as we don't deactivate the
556 volumes on shutdown.
557
558 """
559 pass
560
562 """Returns the sync status of the device.
563
564 If this device is a mirroring device, this function returns the
565 status of the mirror.
566
567 For logical volumes, sync_percent and estimated_time are always
568 None (no recovery in progress, as we don't handle the mirrored LV
569 case). The is_degraded parameter is the inverse of the ldisk
570 parameter.
571
572 For the ldisk parameter, we check if the logical volume has the
573 'virtual' type, which means it's not backed by existing storage
574 anymore (read from it return I/O error). This happens after a
575 physical disk failure and subsequent 'vgreduce --removemissing' on
576 the volume group.
577
578 The status was already read in Attach, so we just return it.
579
580 @rtype: objects.BlockDevStatus
581
582 """
583 if self._degraded:
584 ldisk_status = constants.LDS_FAULTY
585 else:
586 ldisk_status = constants.LDS_OKAY
587
588 return objects.BlockDevStatus(dev_path=self.dev_path,
589 major=self.major,
590 minor=self.minor,
591 sync_percent=None,
592 estimated_time=None,
593 is_degraded=self._degraded,
594 ldisk_status=ldisk_status)
595
596 - def Open(self, force=False, exclusive=True):
597 """Make the device ready for I/O.
598
599 This is a no-op for the LV device type.
600
601 """
602 pass
603
605 """Notifies that the device will no longer be used for I/O.
606
607 This is a no-op for the LV device type.
608
609 """
610 pass
611
612 - def Snapshot(self, snap_name=None, snap_size=None):
613 """Create a snapshot copy of an lvm block device.
614
615 @returns: tuple (vg, lv)
616
617 """
618 if not snap_name:
619 snap_name = self._lv_name + ".snap"
620
621 if not snap_size:
622
623
624 snap_size = self.size
625
626
627 snap = LogicalVolume((self._vg_name, snap_name), None, snap_size,
628 self.params, self.dyn_params)
629 base.IgnoreError(snap.Remove)
630
631 vg_info = self.GetVGInfo([self._vg_name], False)
632 if not vg_info:
633 base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
634 free_size, _, _ = vg_info[0]
635 if free_size < snap_size:
636 base.ThrowError("Not enough free space: required %s,"
637 " available %s", snap_size, free_size)
638
639 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % snap_size, "-s",
640 "-n%s" % snap_name, self.dev_path]))
641
642 return (self._vg_name, snap_name)
643
645 """Try to remove old tags from the lv.
646
647 """
648 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
649 self.dev_path])
650 _CheckResult(result)
651
652 raw_tags = result.stdout.strip()
653 if raw_tags:
654 for tag in raw_tags.split(","):
655 _CheckResult(utils.RunCmd(["lvchange", "--deltag",
656 tag.strip(), self.dev_path]))
657
674
676 """Return how much the disk can grow with exclusive storage.
677
678 @rtype: float
679 @return: available space in Mib
680
681 """
682 pvs_info = self.GetPVInfo([self._vg_name])
683 if not pvs_info:
684 base.ThrowError("Cannot get information about PVs for %s", self.dev_path)
685 std_pv_size = self._GetStdPvSize(pvs_info)
686 free_space = sum(pvi.free - (pvi.size - std_pv_size)
687 for pvi in pvs_info
688 if pvi.name in self.pv_names)
689 return free_space
690
691 - def Grow(self, amount, dryrun, backingstore, excl_stor):
692 """Grow the logical volume.
693
694 """
695 if not backingstore:
696 return
697 if self.pe_size is None or self.stripe_count is None:
698 if not self.Attach():
699 base.ThrowError("Can't attach to LV during Grow()")
700 full_stripe_size = self.pe_size * self.stripe_count
701
702 amount *= 1024
703 rest = amount % full_stripe_size
704 if rest != 0:
705 amount += full_stripe_size - rest
706 cmd = ["lvextend", "-L", "+%dk" % amount]
707 if dryrun:
708 cmd.append("--test")
709 if excl_stor:
710 free_space = self._GetGrowthAvaliabilityExclStor()
711
712 if amount > free_space * 1024:
713 base.ThrowError("Not enough free space to grow %s: %d MiB required,"
714 " %d available", self.dev_path, amount / 1024,
715 free_space)
716
717
718 pvlist = list(self.pv_names)
719 else:
720 pvlist = []
721
722
723
724
725 for alloc_policy in "contiguous", "cling", "normal":
726 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path] +
727 pvlist)
728 if not result.failed:
729 return
730 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
731
733 """Return the number of spindles used.
734
735 """
736 assert self.attached, "BlockDevice not attached in GetActualSpindles()"
737 return len(self.pv_names)
738
741 """A block device with persistent node
742
743 May be either directly attached, or exposed through DM (e.g. dm-multipath).
744 udev helpers are probably required to give persistent, human-friendly
745 names.
746
747 For the time being, pathnames are required to lie under /dev.
748
749 """
750 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
751 """Attaches to a static block device.
752
753 The unique_id is a path under /dev.
754
755 """
756 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
757 params, dyn_params, *args)
758 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
759 raise ValueError("Invalid configuration data %s" % str(unique_id))
760 self.dev_path = unique_id[1]
761 if not os.path.realpath(self.dev_path).startswith("/dev/"):
762 raise ValueError("Full path '%s' lies outside /dev" %
763 os.path.realpath(self.dev_path))
764
765
766
767
768 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
769 raise ValueError("Got persistent block device of invalid type: %s" %
770 unique_id[0])
771
772 self.major = self.minor = None
773 self.Attach()
774
775 @classmethod
776 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
777 dyn_params, *args):
778 """Create a new device
779
780 This is a noop, we only return a PersistentBlockDevice instance
781
782 """
783 if excl_stor:
784 raise errors.ProgrammerError("Persistent block device requested with"
785 " exclusive_storage")
786 return PersistentBlockDevice(unique_id, children, 0, params, dyn_params,
787 *args)
788
790 """Remove a device
791
792 This is a noop
793
794 """
795 pass
796
798 """Rename this device.
799
800 """
801 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
802
804 """Attach to an existing block device.
805
806
807 """
808 self.attached = False
809 try:
810 st = os.stat(self.dev_path)
811 except OSError, err:
812 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
813 return False
814
815 if not stat.S_ISBLK(st.st_mode):
816 logging.error("%s is not a block device", self.dev_path)
817 return False
818
819 self.major = os.major(st.st_rdev)
820 self.minor = utils.osminor(st.st_rdev)
821 self.attached = True
822
823 return True
824
826 """Assemble the device.
827
828 """
829 pass
830
832 """Shutdown the device.
833
834 """
835 pass
836
837 - def Open(self, force=False, exclusive=True):
838 """Make the device ready for I/O.
839
840 """
841 pass
842
844 """Notifies that the device will no longer be used for I/O.
845
846 """
847 pass
848
849 - def Grow(self, amount, dryrun, backingstore, excl_stor):
850 """Grow the logical volume.
851
852 """
853 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
854
856 """Builds the shell command for importing data to device.
857
858 @see: L{BlockDev.Import} for details
859
860 """
861 base.ThrowError("Importing data is not supported for the"
862 " PersistentBlockDevice template")
863
866 """A RADOS Block Device (rbd).
867
868 This class implements the RADOS Block Device for the backend. You need
869 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
870 this to be functional.
871
872 """
873 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
874 """Attaches to an rbd device.
875
876 """
877 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params,
878 dyn_params, *args)
879 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
880 raise ValueError("Invalid configuration data %s" % str(unique_id))
881
882 self.driver, self.rbd_name = unique_id
883 self.rbd_pool = params[constants.LDP_POOL]
884
885 self.major = self.minor = None
886 self.Attach()
887
888 @classmethod
889 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
890 dyn_params, *args):
891 """Create a new rbd device.
892
893 Provision a new rbd volume inside a RADOS pool.
894
895 """
896 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
897 raise errors.ProgrammerError("Invalid configuration data %s" %
898 str(unique_id))
899 if excl_stor:
900 raise errors.ProgrammerError("RBD device requested with"
901 " exclusive_storage")
902 rbd_pool = params[constants.LDP_POOL]
903 rbd_name = unique_id[1]
904
905
906 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
907 rbd_name, "--size", "%s" % size]
908 result = utils.RunCmd(cmd)
909 if result.failed:
910 base.ThrowError("rbd creation failed (%s): %s",
911 result.fail_reason, result.output)
912
913 return RADOSBlockDevice(unique_id, children, size, params, dyn_params,
914 *args)
915
917 """Remove the rbd device.
918
919 """
920 rbd_pool = self.params[constants.LDP_POOL]
921 rbd_name = self.unique_id[1]
922
923 if not self.minor and not self.Attach():
924
925 return
926
927
928 self.Shutdown()
929
930
931 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
932 result = utils.RunCmd(cmd)
933 if result.failed:
934 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
935 result.fail_reason, result.output)
936
938 """Rename this device.
939
940 """
941 pass
942
944 """Attach to an existing rbd device.
945
946 This method maps the rbd volume that matches our name with
947 an rbd device and then attaches to this device.
948
949 """
950 self.attached = False
951
952
953 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
954
955 try:
956 st = os.stat(self.dev_path)
957 except OSError, err:
958 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
959 return False
960
961 if not stat.S_ISBLK(st.st_mode):
962 logging.error("%s is not a block device", self.dev_path)
963 return False
964
965 self.major = os.major(st.st_rdev)
966 self.minor = utils.osminor(st.st_rdev)
967 self.attached = True
968
969 return True
970
972 """Maps existing rbd volumes to block devices.
973
974 This method should be idempotent if the mapping already exists.
975
976 @rtype: string
977 @return: the block device path that corresponds to the volume
978
979 """
980 pool = self.params[constants.LDP_POOL]
981 name = unique_id[1]
982
983
984 rbd_dev = self._VolumeToBlockdev(pool, name)
985 if rbd_dev:
986
987 return rbd_dev
988
989
990 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
991 result = utils.RunCmd(map_cmd)
992 if result.failed:
993 base.ThrowError("rbd map failed (%s): %s",
994 result.fail_reason, result.output)
995
996
997 rbd_dev = self._VolumeToBlockdev(pool, name)
998 if not rbd_dev:
999 base.ThrowError("rbd map succeeded, but could not find the rbd block"
1000 " device in output of showmapped, for volume: %s", name)
1001
1002
1003 return rbd_dev
1004
1005 @classmethod
1007 """Do the 'volume name'-to-'rbd block device' resolving.
1008
1009 @type pool: string
1010 @param pool: RADOS pool to use
1011 @type volume_name: string
1012 @param volume_name: the name of the volume whose device we search for
1013 @rtype: string or None
1014 @return: block device path if the volume is mapped, else None
1015
1016 """
1017 try:
1018
1019
1020 showmap_cmd = [
1021 constants.RBD_CMD,
1022 "showmapped",
1023 "-p",
1024 pool,
1025 "--format",
1026 "json"
1027 ]
1028 result = utils.RunCmd(showmap_cmd)
1029 if result.failed:
1030 logging.error("rbd JSON output formatting returned error (%s): %s,"
1031 "falling back to plain output parsing",
1032 result.fail_reason, result.output)
1033 raise RbdShowmappedJsonError
1034
1035 return cls._ParseRbdShowmappedJson(result.output, volume_name)
1036 except RbdShowmappedJsonError:
1037
1038
1039 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
1040 result = utils.RunCmd(showmap_cmd)
1041 if result.failed:
1042 base.ThrowError("rbd showmapped failed (%s): %s",
1043 result.fail_reason, result.output)
1044
1045 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1046
1047 @staticmethod
1049 """Parse the json output of `rbd showmapped'.
1050
1051 This method parses the json output of `rbd showmapped' and returns the rbd
1052 block device path (e.g. /dev/rbd0) that matches the given rbd volume.
1053
1054 @type output: string
1055 @param output: the json output of `rbd showmapped'
1056 @type volume_name: string
1057 @param volume_name: the name of the volume whose device we search for
1058 @rtype: string or None
1059 @return: block device path if the volume is mapped, else None
1060
1061 """
1062 try:
1063 devices = serializer.LoadJson(output)
1064 except ValueError, err:
1065 base.ThrowError("Unable to parse JSON data: %s" % err)
1066
1067 rbd_dev = None
1068 for d in devices.values():
1069 try:
1070 name = d["name"]
1071 except KeyError:
1072 base.ThrowError("'name' key missing from json object %s", devices)
1073
1074 if name == volume_name:
1075 if rbd_dev is not None:
1076 base.ThrowError("rbd volume %s is mapped more than once", volume_name)
1077
1078 rbd_dev = d["device"]
1079
1080 return rbd_dev
1081
1082 @staticmethod
1084 """Parse the (plain / text) output of `rbd showmapped'.
1085
1086 This method parses the output of `rbd showmapped' and returns
1087 the rbd block device path (e.g. /dev/rbd0) that matches the
1088 given rbd volume.
1089
1090 @type output: string
1091 @param output: the plain text output of `rbd showmapped'
1092 @type volume_name: string
1093 @param volume_name: the name of the volume whose device we search for
1094 @rtype: string or None
1095 @return: block device path if the volume is mapped, else None
1096
1097 """
1098 allfields = 5
1099 volumefield = 2
1100 devicefield = 4
1101
1102 lines = output.splitlines()
1103
1104
1105 splitted_lines = map(lambda l: l.split(), lines)
1106
1107
1108 if not splitted_lines:
1109 return None
1110
1111
1112 field_cnt = len(splitted_lines[0])
1113 if field_cnt != allfields:
1114
1115
1116 splitted_lines = map(lambda l: l.split("\t"), lines)
1117 if field_cnt != allfields:
1118 base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
1119 " found %s", allfields, field_cnt)
1120
1121 matched_lines = \
1122 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
1123 splitted_lines)
1124
1125 if len(matched_lines) > 1:
1126 base.ThrowError("rbd volume %s mapped more than once", volume_name)
1127
1128 if matched_lines:
1129
1130 rbd_dev = matched_lines[0][devicefield]
1131 return rbd_dev
1132
1133
1134 return None
1135
1137 """Assemble the device.
1138
1139 """
1140 pass
1141
1143 """Shutdown the device.
1144
1145 """
1146 if not self.minor and not self.Attach():
1147
1148 return
1149
1150
1151 self._UnmapVolumeFromBlockdev(self.unique_id)
1152
1153 self.minor = None
1154 self.dev_path = None
1155
1157 """Unmaps the rbd device from the Volume it is mapped.
1158
1159 Unmaps the rbd device from the Volume it was previously mapped to.
1160 This method should be idempotent if the Volume isn't mapped.
1161
1162 """
1163 pool = self.params[constants.LDP_POOL]
1164 name = unique_id[1]
1165
1166
1167 rbd_dev = self._VolumeToBlockdev(pool, name)
1168
1169 if rbd_dev:
1170
1171 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
1172 result = utils.RunCmd(unmap_cmd)
1173 if result.failed:
1174 base.ThrowError("rbd unmap failed (%s): %s",
1175 result.fail_reason, result.output)
1176
1177 - def Open(self, force=False, exclusive=True):
1178 """Make the device ready for I/O.
1179
1180 """
1181 pass
1182
1184 """Notifies that the device will no longer be used for I/O.
1185
1186 """
1187 pass
1188
1189 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1190 """Grow the Volume.
1191
1192 @type amount: integer
1193 @param amount: the amount (in mebibytes) to grow with
1194 @type dryrun: boolean
1195 @param dryrun: whether to execute the operation in simulation mode
1196 only, without actually increasing the size
1197
1198 """
1199 if not backingstore:
1200 return
1201 if not self.Attach():
1202 base.ThrowError("Can't attach to rbd device during Grow()")
1203
1204 if dryrun:
1205
1206
1207
1208 return
1209
1210 rbd_pool = self.params[constants.LDP_POOL]
1211 rbd_name = self.unique_id[1]
1212 new_size = self.size + amount
1213
1214
1215 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
1216 rbd_name, "--size", "%s" % new_size]
1217 result = utils.RunCmd(cmd)
1218 if result.failed:
1219 base.ThrowError("rbd resize failed (%s): %s",
1220 result.fail_reason, result.output)
1221
1223 """Builds the shell command for importing data to device.
1224
1225 @see: L{BlockDev.Import} for details
1226
1227 """
1228 if not self.minor and not self.Attach():
1229
1230 base.ThrowError("Can't attach to rbd device during Import()")
1231
1232 rbd_pool = self.params[constants.LDP_POOL]
1233 rbd_name = self.unique_id[1]
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248 self._UnmapVolumeFromBlockdev(self.unique_id)
1249
1250
1251 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
1252 result = utils.RunCmd(cmd)
1253 if result.failed:
1254 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
1255 result.fail_reason, result.output)
1256
1257
1258 return [constants.RBD_CMD, "import",
1259 "-p", rbd_pool,
1260 "-", rbd_name]
1261
1263 """Builds the shell command for exporting data from device.
1264
1265 @see: L{BlockDev.Export} for details
1266
1267 """
1268 if not self.minor and not self.Attach():
1269
1270 base.ThrowError("Can't attach to rbd device during Export()")
1271
1272 rbd_pool = self.params[constants.LDP_POOL]
1273 rbd_name = self.unique_id[1]
1274
1275
1276 return [constants.RBD_CMD, "export",
1277 "-p", rbd_pool,
1278 rbd_name, "-"]
1279
1281 """Generate KVM userspace URIs to be used as `-drive file` settings.
1282
1283 @see: L{BlockDev.GetUserspaceAccessUri}
1284
1285 """
1286 if hypervisor == constants.HT_KVM:
1287 return "rbd:" + self.rbd_pool + "/" + self.rbd_name
1288 else:
1289 base.ThrowError("Hypervisor %s doesn't support RBD userspace access" %
1290 hypervisor)
1291
1296
1299 """Verifies if all disk parameters are set.
1300
1301 """
1302 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
1303 if missing:
1304 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
1305 missing)
1306
1309 """Search for an existing, assembled device.
1310
1311 This will succeed only if the device exists and is assembled, but it
1312 does not do any actions in order to activate the device.
1313
1314 @type disk: L{objects.Disk}
1315 @param disk: the disk object to find
1316 @type children: list of L{bdev.BlockDev}
1317 @param children: the list of block devices that are children of the device
1318 represented by the disk parameter
1319
1320 """
1321 _VerifyDiskType(disk.dev_type)
1322 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
1323 disk.params, disk.dynamic_params,
1324 disk.name, disk.uuid)
1325 if not device.attached:
1326 return None
1327 return device
1328
1331 """Try to attach or assemble an existing device.
1332
1333 This will attach to assemble the device, as needed, to bring it
1334 fully up. It must be safe to run on already-assembled devices.
1335
1336 @type disk: L{objects.Disk}
1337 @param disk: the disk object to assemble
1338 @type children: list of L{bdev.BlockDev}
1339 @param children: the list of block devices that are children of the device
1340 represented by the disk parameter
1341
1342 """
1343 _VerifyDiskType(disk.dev_type)
1344 _VerifyDiskParams(disk)
1345 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
1346 disk.params, disk.dynamic_params,
1347 disk.name, disk.uuid)
1348 device.Assemble()
1349 return device
1350
1351
1352 -def Create(disk, children, excl_stor):
1353 """Create a device.
1354
1355 @type disk: L{objects.Disk}
1356 @param disk: the disk object to create
1357 @type children: list of L{bdev.BlockDev}
1358 @param children: the list of block devices that are children of the device
1359 represented by the disk parameter
1360 @type excl_stor: boolean
1361 @param excl_stor: Whether exclusive_storage is active
1362 @rtype: L{bdev.BlockDev}
1363 @return: the created device, or C{None} in case of an error
1364
1365 """
1366 _VerifyDiskType(disk.dev_type)
1367 _VerifyDiskParams(disk)
1368 device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size,
1369 disk.spindles, disk.params, excl_stor,
1370 disk.dynamic_params,
1371 disk.name, disk.uuid)
1372 return device
1373
1374
1375 DEV_MAP = {
1376 constants.DT_PLAIN: LogicalVolume,
1377 constants.DT_DRBD8: drbd.DRBD8Dev,
1378 constants.DT_BLOCK: PersistentBlockDevice,
1379 constants.DT_RBD: RADOSBlockDevice,
1380 constants.DT_EXT: ExtStorageDevice,
1381 constants.DT_FILE: FileStorage,
1382 constants.DT_SHARED_FILE: FileStorage,
1383 constants.DT_GLUSTER: GlusterStorage,
1384 }
1385 """Map disk types to disk type classes.
1386
1387 @see: L{Assemble}, L{FindDevice}, L{Create}."""
1388