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 time
26 import errno
27 import shlex
28 import stat
29 import pyparsing as pyp
30 import os
31 import logging
32
33 from ganeti import utils
34 from ganeti import errors
35 from ganeti import constants
36 from ganeti import objects
37 from ganeti import compat
38 from ganeti import netutils
39
40
41
42 _DEVICE_READ_SIZE = 128 * 1024
46 """Executes the given function, ignoring BlockDeviceErrors.
47
48 This is used in order to simplify the execution of cleanup or
49 rollback functions.
50
51 @rtype: boolean
52 @return: True when fn didn't raise an exception, False otherwise
53
54 """
55 try:
56 fn(*args, **kwargs)
57 return True
58 except errors.BlockDeviceError, err:
59 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
60 return False
61
64 """Log an error to the node daemon and the raise an exception.
65
66 @type msg: string
67 @param msg: the text of the exception
68 @raise errors.BlockDeviceError
69
70 """
71 if args:
72 msg = msg % args
73 logging.error(msg)
74 raise errors.BlockDeviceError(msg)
75
78 """Check if we can read from the given device.
79
80 This tries to read the first 128k of the device.
81
82 """
83 try:
84 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
85 return True
86 except EnvironmentError:
87 logging.warning("Can't read from device %s", path, exc_info=True)
88 return False
89
92 """Block device abstract class.
93
94 A block device can be in the following states:
95 - not existing on the system, and by `Create()` it goes into:
96 - existing but not setup/not active, and by `Assemble()` goes into:
97 - active read-write and by `Open()` it goes into
98 - online (=used, or ready for use)
99
100 A device can also be online but read-only, however we are not using
101 the readonly state (LV has it, if needed in the future) and we are
102 usually looking at this like at a stack, so it's easier to
103 conceptualise the transition from not-existing to online and back
104 like a linear one.
105
106 The many different states of the device are due to the fact that we
107 need to cover many device types:
108 - logical volumes are created, lvchange -a y $lv, and used
109 - drbd devices are attached to a local disk/remote peer and made primary
110
111 A block device is identified by three items:
112 - the /dev path of the device (dynamic)
113 - a unique ID of the device (static)
114 - it's major/minor pair (dynamic)
115
116 Not all devices implement both the first two as distinct items. LVM
117 logical volumes have their unique ID (the pair volume group, logical
118 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
119 the /dev path is again dynamic and the unique id is the pair (host1,
120 dev1), (host2, dev2).
121
122 You can get to a device in two ways:
123 - creating the (real) device, which returns you
124 an attached instance (lvcreate)
125 - attaching of a python instance to an existing (real) device
126
127 The second point, the attachement to a device, is different
128 depending on whether the device is assembled or not. At init() time,
129 we search for a device with the same unique_id as us. If found,
130 good. It also means that the device is already assembled. If not,
131 after assembly we'll have our correct major/minor.
132
133 """
134 - def __init__(self, unique_id, children, size, params):
135 self._children = children
136 self.dev_path = None
137 self.unique_id = unique_id
138 self.major = None
139 self.minor = None
140 self.attached = False
141 self.size = size
142 self.params = params
143
145 """Assemble the device from its components.
146
147 Implementations of this method by child classes must ensure that:
148 - after the device has been assembled, it knows its major/minor
149 numbers; this allows other devices (usually parents) to probe
150 correctly for their children
151 - calling this method on an existing, in-use device is safe
152 - if the device is already configured (and in an OK state),
153 this method is idempotent
154
155 """
156 pass
157
159 """Find a device which matches our config and attach to it.
160
161 """
162 raise NotImplementedError
163
165 """Notifies that the device will no longer be used for I/O.
166
167 """
168 raise NotImplementedError
169
170 @classmethod
171 - def Create(cls, unique_id, children, size, params):
172 """Create the device.
173
174 If the device cannot be created, it will return None
175 instead. Error messages go to the logging system.
176
177 Note that for some devices, the unique_id is used, and for other,
178 the children. The idea is that these two, taken together, are
179 enough for both creation and assembly (later).
180
181 """
182 raise NotImplementedError
183
185 """Remove this device.
186
187 This makes sense only for some of the device types: LV and file
188 storage. Also note that if the device can't attach, the removal
189 can't be completed.
190
191 """
192 raise NotImplementedError
193
195 """Rename this device.
196
197 This may or may not make sense for a given device type.
198
199 """
200 raise NotImplementedError
201
202 - def Open(self, force=False):
203 """Make the device ready for use.
204
205 This makes the device ready for I/O. For now, just the DRBD
206 devices need this.
207
208 The force parameter signifies that if the device has any kind of
209 --force thing, it should be used, we know what we are doing.
210
211 """
212 raise NotImplementedError
213
215 """Shut down the device, freeing its children.
216
217 This undoes the `Assemble()` work, except for the child
218 assembling; as such, the children on the device are still
219 assembled after this call.
220
221 """
222 raise NotImplementedError
223
225 """Adjust the synchronization parameters of the mirror.
226
227 In case this is not a mirroring device, this is no-op.
228
229 @param params: dictionary of LD level disk parameters related to the
230 synchronization.
231 @rtype: list
232 @return: a list of error messages, emitted both by the current node and by
233 children. An empty list means no errors.
234
235 """
236 result = []
237 if self._children:
238 for child in self._children:
239 result.extend(child.SetSyncParams(params))
240 return result
241
243 """Pause/Resume the sync of the mirror.
244
245 In case this is not a mirroring device, this is no-op.
246
247 @param pause: Whether to pause or resume
248
249 """
250 result = True
251 if self._children:
252 for child in self._children:
253 result = result and child.PauseResumeSync(pause)
254 return result
255
257 """Returns the sync status of the device.
258
259 If this device is a mirroring device, this function returns the
260 status of the mirror.
261
262 If sync_percent is None, it means the device is not syncing.
263
264 If estimated_time is None, it means we can't estimate
265 the time needed, otherwise it's the time left in seconds.
266
267 If is_degraded is True, it means the device is missing
268 redundancy. This is usually a sign that something went wrong in
269 the device setup, if sync_percent is None.
270
271 The ldisk parameter represents the degradation of the local
272 data. This is only valid for some devices, the rest will always
273 return False (not degraded).
274
275 @rtype: objects.BlockDevStatus
276
277 """
278 return objects.BlockDevStatus(dev_path=self.dev_path,
279 major=self.major,
280 minor=self.minor,
281 sync_percent=None,
282 estimated_time=None,
283 is_degraded=False,
284 ldisk_status=constants.LDS_OKAY)
285
287 """Calculate the mirror status recursively for our children.
288
289 The return value is the same as for `GetSyncStatus()` except the
290 minimum percent and maximum time are calculated across our
291 children.
292
293 @rtype: objects.BlockDevStatus
294
295 """
296 status = self.GetSyncStatus()
297
298 min_percent = status.sync_percent
299 max_time = status.estimated_time
300 is_degraded = status.is_degraded
301 ldisk_status = status.ldisk_status
302
303 if self._children:
304 for child in self._children:
305 child_status = child.GetSyncStatus()
306
307 if min_percent is None:
308 min_percent = child_status.sync_percent
309 elif child_status.sync_percent is not None:
310 min_percent = min(min_percent, child_status.sync_percent)
311
312 if max_time is None:
313 max_time = child_status.estimated_time
314 elif child_status.estimated_time is not None:
315 max_time = max(max_time, child_status.estimated_time)
316
317 is_degraded = is_degraded or child_status.is_degraded
318
319 if ldisk_status is None:
320 ldisk_status = child_status.ldisk_status
321 elif child_status.ldisk_status is not None:
322 ldisk_status = max(ldisk_status, child_status.ldisk_status)
323
324 return objects.BlockDevStatus(dev_path=self.dev_path,
325 major=self.major,
326 minor=self.minor,
327 sync_percent=min_percent,
328 estimated_time=max_time,
329 is_degraded=is_degraded,
330 ldisk_status=ldisk_status)
331
333 """Update metadata with info text.
334
335 Only supported for some device types.
336
337 """
338 for child in self._children:
339 child.SetInfo(text)
340
341 - def Grow(self, amount, dryrun):
342 """Grow the block device.
343
344 @type amount: integer
345 @param amount: the amount (in mebibytes) to grow with
346 @type dryrun: boolean
347 @param dryrun: whether to execute the operation in simulation mode
348 only, without actually increasing the size
349
350 """
351 raise NotImplementedError
352
354 """Return the actual disk size.
355
356 @note: the device needs to be active when this is called
357
358 """
359 assert self.attached, "BlockDevice not attached in GetActualSize()"
360 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
361 if result.failed:
362 _ThrowError("blockdev failed (%s): %s",
363 result.fail_reason, result.output)
364 try:
365 sz = int(result.output.strip())
366 except (ValueError, TypeError), err:
367 _ThrowError("Failed to parse blockdev output: %s", str(err))
368 return sz
369
371 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
372 (self.__class__, self.unique_id, self._children,
373 self.major, self.minor, self.dev_path))
374
377 """Logical Volume block device.
378
379 """
380 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
381 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
382 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
383
384 - def __init__(self, unique_id, children, size, params):
385 """Attaches to a LV device.
386
387 The unique_id is a tuple (vg_name, lv_name)
388
389 """
390 super(LogicalVolume, self).__init__(unique_id, children, size, params)
391 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
392 raise ValueError("Invalid configuration data %s" % str(unique_id))
393 self._vg_name, self._lv_name = unique_id
394 self._ValidateName(self._vg_name)
395 self._ValidateName(self._lv_name)
396 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
397 self._degraded = True
398 self.major = self.minor = self.pe_size = self.stripe_count = None
399 self.Attach()
400
401 @classmethod
402 - def Create(cls, unique_id, children, size, params):
403 """Create a new logical volume.
404
405 """
406 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
407 raise errors.ProgrammerError("Invalid configuration data %s" %
408 str(unique_id))
409 vg_name, lv_name = unique_id
410 cls._ValidateName(vg_name)
411 cls._ValidateName(lv_name)
412 pvs_info = cls.GetPVInfo([vg_name])
413 if not pvs_info:
414 _ThrowError("Can't compute PV info for vg %s", vg_name)
415 pvs_info.sort()
416 pvs_info.reverse()
417
418 pvlist = [pv[1] for pv in pvs_info]
419 if compat.any(":" in v for v in pvlist):
420 _ThrowError("Some of your PVs have the invalid character ':' in their"
421 " name, this is not supported - please filter them out"
422 " in lvm.conf using either 'filter' or 'preferred_names'")
423 free_size = sum([pv[0] for pv in pvs_info])
424 current_pvs = len(pvlist)
425 desired_stripes = params[constants.LDP_STRIPES]
426 stripes = min(current_pvs, desired_stripes)
427 if stripes < desired_stripes:
428 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
429 " available.", desired_stripes, vg_name, current_pvs)
430
431
432
433 if free_size < size:
434 _ThrowError("Not enough free space: required %s,"
435 " available %s", size, free_size)
436 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
437
438
439
440
441 for stripes_arg in range(stripes, 0, -1):
442 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
443 if not result.failed:
444 break
445 if result.failed:
446 _ThrowError("LV create failed (%s): %s",
447 result.fail_reason, result.output)
448 return LogicalVolume(unique_id, children, size, params)
449
450 @staticmethod
452 """Returns LVM Volumen infos using lvm_cmd
453
454 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
455 @param fields: Fields to return
456 @return: A list of dicts each with the parsed fields
457
458 """
459 if not fields:
460 raise errors.ProgrammerError("No fields specified")
461
462 sep = "|"
463 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
464 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
465
466 result = utils.RunCmd(cmd)
467 if result.failed:
468 raise errors.CommandError("Can't get the volume information: %s - %s" %
469 (result.fail_reason, result.output))
470
471 data = []
472 for line in result.stdout.splitlines():
473 splitted_fields = line.strip().split(sep)
474
475 if len(fields) != len(splitted_fields):
476 raise errors.CommandError("Can't parse %s output: line '%s'" %
477 (lvm_cmd, line))
478
479 data.append(splitted_fields)
480
481 return data
482
483 @classmethod
484 - def GetPVInfo(cls, vg_names, filter_allocatable=True):
485 """Get the free space info for PVs in a volume group.
486
487 @param vg_names: list of volume group names, if empty all will be returned
488 @param filter_allocatable: whether to skip over unallocatable PVs
489
490 @rtype: list
491 @return: list of tuples (free_space, name) with free_space in mebibytes
492
493 """
494 try:
495 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
496 "pv_attr"])
497 except errors.GenericError, err:
498 logging.error("Can't get PV information: %s", err)
499 return None
500
501 data = []
502 for pv_name, vg_name, pv_free, pv_attr in info:
503
504 if filter_allocatable and pv_attr[0] != "a":
505 continue
506
507 if vg_names and vg_name not in vg_names:
508 continue
509 data.append((float(pv_free), pv_name, vg_name))
510
511 return data
512
513 @classmethod
514 - def GetVGInfo(cls, vg_names, filter_readonly=True):
515 """Get the free space info for specific VGs.
516
517 @param vg_names: list of volume group names, if empty all will be returned
518 @param filter_readonly: whether to skip over readonly VGs
519
520 @rtype: list
521 @return: list of tuples (free_space, total_size, name) with free_space in
522 MiB
523
524 """
525 try:
526 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
527 "vg_size"])
528 except errors.GenericError, err:
529 logging.error("Can't get VG information: %s", err)
530 return None
531
532 data = []
533 for vg_name, vg_free, vg_attr, vg_size in info:
534
535 if filter_readonly and vg_attr[0] == "r":
536 continue
537
538 if vg_names and vg_name not in vg_names:
539 continue
540 data.append((float(vg_free), float(vg_size), vg_name))
541
542 return data
543
544 @classmethod
546 """Validates that a given name is valid as VG or LV name.
547
548 The list of valid characters and restricted names is taken out of
549 the lvm(8) manpage, with the simplification that we enforce both
550 VG and LV restrictions on the names.
551
552 """
553 if (not cls._VALID_NAME_RE.match(name) or
554 name in cls._INVALID_NAMES or
555 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
556 _ThrowError("Invalid LVM name '%s'", name)
557
559 """Remove this logical volume.
560
561 """
562 if not self.minor and not self.Attach():
563
564 return
565 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
566 (self._vg_name, self._lv_name)])
567 if result.failed:
568 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
569
571 """Rename this logical volume.
572
573 """
574 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
575 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
576 new_vg, new_name = new_id
577 if new_vg != self._vg_name:
578 raise errors.ProgrammerError("Can't move a logical volume across"
579 " volume groups (from %s to to %s)" %
580 (self._vg_name, new_vg))
581 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
582 if result.failed:
583 _ThrowError("Failed to rename the logical volume: %s", result.output)
584 self._lv_name = new_name
585 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
586
588 """Attach to an existing LV.
589
590 This method will try to see if an existing and active LV exists
591 which matches our name. If so, its major/minor will be
592 recorded.
593
594 """
595 self.attached = False
596 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
597 "--units=m", "--nosuffix",
598 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
599 "vg_extent_size,stripes", self.dev_path])
600 if result.failed:
601 logging.error("Can't find LV %s: %s, %s",
602 self.dev_path, result.fail_reason, result.output)
603 return False
604
605
606
607
608
609 out = result.stdout.splitlines()
610 if not out:
611
612 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
613 return False
614 out = out[-1].strip().rstrip(",")
615 out = out.split(",")
616 if len(out) != 5:
617 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
618 return False
619
620 status, major, minor, pe_size, stripes = out
621 if len(status) < 6:
622 logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
623 return False
624
625 try:
626 major = int(major)
627 minor = int(minor)
628 except (TypeError, ValueError), err:
629 logging.error("lvs major/minor cannot be parsed: %s", str(err))
630
631 try:
632 pe_size = int(float(pe_size))
633 except (TypeError, ValueError), err:
634 logging.error("Can't parse vg extent size: %s", err)
635 return False
636
637 try:
638 stripes = int(stripes)
639 except (TypeError, ValueError), err:
640 logging.error("Can't parse the number of stripes: %s", err)
641 return False
642
643 self.major = major
644 self.minor = minor
645 self.pe_size = pe_size
646 self.stripe_count = stripes
647 self._degraded = status[0] == "v"
648
649 self.attached = True
650 return True
651
653 """Assemble the device.
654
655 We always run `lvchange -ay` on the LV to ensure it's active before
656 use, as there were cases when xenvg was not active after boot
657 (also possibly after disk issues).
658
659 """
660 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
661 if result.failed:
662 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
663
665 """Shutdown the device.
666
667 This is a no-op for the LV device type, as we don't deactivate the
668 volumes on shutdown.
669
670 """
671 pass
672
674 """Returns the sync status of the device.
675
676 If this device is a mirroring device, this function returns the
677 status of the mirror.
678
679 For logical volumes, sync_percent and estimated_time are always
680 None (no recovery in progress, as we don't handle the mirrored LV
681 case). The is_degraded parameter is the inverse of the ldisk
682 parameter.
683
684 For the ldisk parameter, we check if the logical volume has the
685 'virtual' type, which means it's not backed by existing storage
686 anymore (read from it return I/O error). This happens after a
687 physical disk failure and subsequent 'vgreduce --removemissing' on
688 the volume group.
689
690 The status was already read in Attach, so we just return it.
691
692 @rtype: objects.BlockDevStatus
693
694 """
695 if self._degraded:
696 ldisk_status = constants.LDS_FAULTY
697 else:
698 ldisk_status = constants.LDS_OKAY
699
700 return objects.BlockDevStatus(dev_path=self.dev_path,
701 major=self.major,
702 minor=self.minor,
703 sync_percent=None,
704 estimated_time=None,
705 is_degraded=self._degraded,
706 ldisk_status=ldisk_status)
707
708 - def Open(self, force=False):
709 """Make the device ready for I/O.
710
711 This is a no-op for the LV device type.
712
713 """
714 pass
715
717 """Notifies that the device will no longer be used for I/O.
718
719 This is a no-op for the LV device type.
720
721 """
722 pass
723
725 """Create a snapshot copy of an lvm block device.
726
727 @returns: tuple (vg, lv)
728
729 """
730 snap_name = self._lv_name + ".snap"
731
732
733 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
734 _IgnoreError(snap.Remove)
735
736 vg_info = self.GetVGInfo([self._vg_name])
737 if not vg_info:
738 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
739 free_size, _, _ = vg_info[0]
740 if free_size < size:
741 _ThrowError("Not enough free space: required %s,"
742 " available %s", size, free_size)
743
744 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
745 "-n%s" % snap_name, self.dev_path])
746 if result.failed:
747 _ThrowError("command: %s error: %s - %s",
748 result.cmd, result.fail_reason, result.output)
749
750 return (self._vg_name, snap_name)
751
753 """Update metadata with info text.
754
755 """
756 BlockDev.SetInfo(self, text)
757
758
759 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
760 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
761
762
763 text = text[:128]
764
765 result = utils.RunCmd(["lvchange", "--addtag", text,
766 self.dev_path])
767 if result.failed:
768 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
769 result.output)
770
771 - def Grow(self, amount, dryrun):
772 """Grow the logical volume.
773
774 """
775 if self.pe_size is None or self.stripe_count is None:
776 if not self.Attach():
777 _ThrowError("Can't attach to LV during Grow()")
778 full_stripe_size = self.pe_size * self.stripe_count
779 rest = amount % full_stripe_size
780 if rest != 0:
781 amount += full_stripe_size - rest
782 cmd = ["lvextend", "-L", "+%dm" % amount]
783 if dryrun:
784 cmd.append("--test")
785
786
787
788
789 for alloc_policy in "contiguous", "cling", "normal":
790 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
791 if not result.failed:
792 return
793 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
794
797 """A DRBD status representation class.
798
799 Note that this doesn't support unconfigured devices (cs:Unconfigured).
800
801 """
802 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
803 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
804 "\s+ds:([^/]+)/(\S+)\s+.*$")
805 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
806
807
808 "(?:\s|M)"
809 "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
810
811 CS_UNCONFIGURED = "Unconfigured"
812 CS_STANDALONE = "StandAlone"
813 CS_WFCONNECTION = "WFConnection"
814 CS_WFREPORTPARAMS = "WFReportParams"
815 CS_CONNECTED = "Connected"
816 CS_STARTINGSYNCS = "StartingSyncS"
817 CS_STARTINGSYNCT = "StartingSyncT"
818 CS_WFBITMAPS = "WFBitMapS"
819 CS_WFBITMAPT = "WFBitMapT"
820 CS_WFSYNCUUID = "WFSyncUUID"
821 CS_SYNCSOURCE = "SyncSource"
822 CS_SYNCTARGET = "SyncTarget"
823 CS_PAUSEDSYNCS = "PausedSyncS"
824 CS_PAUSEDSYNCT = "PausedSyncT"
825 CSET_SYNC = frozenset([
826 CS_WFREPORTPARAMS,
827 CS_STARTINGSYNCS,
828 CS_STARTINGSYNCT,
829 CS_WFBITMAPS,
830 CS_WFBITMAPT,
831 CS_WFSYNCUUID,
832 CS_SYNCSOURCE,
833 CS_SYNCTARGET,
834 CS_PAUSEDSYNCS,
835 CS_PAUSEDSYNCT,
836 ])
837
838 DS_DISKLESS = "Diskless"
839 DS_ATTACHING = "Attaching"
840 DS_FAILED = "Failed"
841 DS_NEGOTIATING = "Negotiating"
842 DS_INCONSISTENT = "Inconsistent"
843 DS_OUTDATED = "Outdated"
844 DS_DUNKNOWN = "DUnknown"
845 DS_CONSISTENT = "Consistent"
846 DS_UPTODATE = "UpToDate"
847
848 RO_PRIMARY = "Primary"
849 RO_SECONDARY = "Secondary"
850 RO_UNKNOWN = "Unknown"
851
853 u = self.UNCONF_RE.match(procline)
854 if u:
855 self.cstatus = self.CS_UNCONFIGURED
856 self.lrole = self.rrole = self.ldisk = self.rdisk = None
857 else:
858 m = self.LINE_RE.match(procline)
859 if not m:
860 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
861 self.cstatus = m.group(1)
862 self.lrole = m.group(2)
863 self.rrole = m.group(3)
864 self.ldisk = m.group(4)
865 self.rdisk = m.group(5)
866
867
868
869 self.is_standalone = self.cstatus == self.CS_STANDALONE
870 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
871 self.is_connected = self.cstatus == self.CS_CONNECTED
872 self.is_primary = self.lrole == self.RO_PRIMARY
873 self.is_secondary = self.lrole == self.RO_SECONDARY
874 self.peer_primary = self.rrole == self.RO_PRIMARY
875 self.peer_secondary = self.rrole == self.RO_SECONDARY
876 self.both_primary = self.is_primary and self.peer_primary
877 self.both_secondary = self.is_secondary and self.peer_secondary
878
879 self.is_diskless = self.ldisk == self.DS_DISKLESS
880 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
881
882 self.is_in_resync = self.cstatus in self.CSET_SYNC
883 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
884
885 m = self.SYNC_RE.match(procline)
886 if m:
887 self.sync_percent = float(m.group(1))
888 hours = int(m.group(2))
889 minutes = int(m.group(3))
890 seconds = int(m.group(4))
891 self.est_time = hours * 3600 + minutes * 60 + seconds
892 else:
893
894
895
896
897 if self.is_in_resync:
898 self.sync_percent = 0
899 else:
900 self.sync_percent = None
901 self.est_time = None
902
905 """Base DRBD class.
906
907 This class contains a few bits of common functionality between the
908 0.7 and 8.x versions of DRBD.
909
910 """
911 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
912 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
913 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
914 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
915
916 _DRBD_MAJOR = 147
917 _ST_UNCONFIGURED = "Unconfigured"
918 _ST_WFCONNECTION = "WFConnection"
919 _ST_CONNECTED = "Connected"
920
921 _STATUS_FILE = "/proc/drbd"
922 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
923
924 @staticmethod
926 """Return data from /proc/drbd.
927
928 """
929 try:
930 data = utils.ReadFile(filename).splitlines()
931 except EnvironmentError, err:
932 if err.errno == errno.ENOENT:
933 _ThrowError("The file %s cannot be opened, check if the module"
934 " is loaded (%s)", filename, str(err))
935 else:
936 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
937 if not data:
938 _ThrowError("Can't read any data from %s", filename)
939 return data
940
941 @classmethod
943 """Transform the output of _GetProdData into a nicer form.
944
945 @return: a dictionary of minor: joined lines from /proc/drbd
946 for that minor
947
948 """
949 results = {}
950 old_minor = old_line = None
951 for line in data:
952 if not line:
953 continue
954 lresult = cls._VALID_LINE_RE.match(line)
955 if lresult is not None:
956 if old_minor is not None:
957 results[old_minor] = old_line
958 old_minor = int(lresult.group(1))
959 old_line = line
960 else:
961 if old_minor is not None:
962 old_line += " " + line.strip()
963
964 if old_minor is not None:
965 results[old_minor] = old_line
966 return results
967
968 @classmethod
970 """Return the DRBD version.
971
972 This will return a dict with keys:
973 - k_major
974 - k_minor
975 - k_point
976 - api
977 - proto
978 - proto2 (only on drbd > 8.2.X)
979
980 """
981 first_line = proc_data[0].strip()
982 version = cls._VERSION_RE.match(first_line)
983 if not version:
984 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
985 first_line)
986
987 values = version.groups()
988 retval = {"k_major": int(values[0]),
989 "k_minor": int(values[1]),
990 "k_point": int(values[2]),
991 "api": int(values[3]),
992 "proto": int(values[4]),
993 }
994 if values[5] is not None:
995 retval["proto2"] = values[5]
996
997 return retval
998
999 @staticmethod
1001 """Returns DRBD usermode_helper currently set.
1002
1003 """
1004 try:
1005 helper = utils.ReadFile(filename).splitlines()[0]
1006 except EnvironmentError, err:
1007 if err.errno == errno.ENOENT:
1008 _ThrowError("The file %s cannot be opened, check if the module"
1009 " is loaded (%s)", filename, str(err))
1010 else:
1011 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1012 if not helper:
1013 _ThrowError("Can't read any data from %s", filename)
1014 return helper
1015
1016 @staticmethod
1018 """Return the path to a drbd device for a given minor.
1019
1020 """
1021 return "/dev/drbd%d" % minor
1022
1023 @classmethod
1025 """Compute the list of used DRBD devices.
1026
1027 """
1028 data = cls._GetProcData()
1029
1030 used_devs = {}
1031 for line in data:
1032 match = cls._VALID_LINE_RE.match(line)
1033 if not match:
1034 continue
1035 minor = int(match.group(1))
1036 state = match.group(2)
1037 if state == cls._ST_UNCONFIGURED:
1038 continue
1039 used_devs[minor] = state, line
1040
1041 return used_devs
1042
1044 """Set our parameters based on the given minor.
1045
1046 This sets our minor variable and our dev_path.
1047
1048 """
1049 if minor is None:
1050 self.minor = self.dev_path = None
1051 self.attached = False
1052 else:
1053 self.minor = minor
1054 self.dev_path = self._DevPath(minor)
1055 self.attached = True
1056
1057 @staticmethod
1084
1086 """Rename a device.
1087
1088 This is not supported for drbd devices.
1089
1090 """
1091 raise errors.ProgrammerError("Can't rename a drbd device")
1092
1093
1094 -class DRBD8(BaseDRBD):
1095 """DRBD v8.x block device.
1096
1097 This implements the local host part of the DRBD device, i.e. it
1098 doesn't do anything to the supposed peer. If you need a fully
1099 connected DRBD pair, you need to use this class on both hosts.
1100
1101 The unique_id for the drbd device is a (local_ip, local_port,
1102 remote_ip, remote_port, local_minor, secret) tuple, and it must have
1103 two children: the data device and the meta_device. The meta device
1104 is checked for valid size and is zeroed on create.
1105
1106 """
1107 _MAX_MINORS = 255
1108 _PARSE_SHOW = None
1109
1110
1111 _NET_RECONFIG_TIMEOUT = 60
1112
1113
1114 _DISABLE_DISK_OPTION = "--no-disk-barrier"
1115 _DISABLE_DRAIN_OPTION = "--no-disk-drain"
1116 _DISABLE_FLUSH_OPTION = "--no-disk-flushes"
1117 _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"
1118
1119 - def __init__(self, unique_id, children, size, params):
1120 if children and children.count(None) > 0:
1121 children = []
1122 if len(children) not in (0, 2):
1123 raise ValueError("Invalid configuration data %s" % str(children))
1124 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1125 raise ValueError("Invalid configuration data %s" % str(unique_id))
1126 (self._lhost, self._lport,
1127 self._rhost, self._rport,
1128 self._aminor, self._secret) = unique_id
1129 if children:
1130 if not _CanReadDevice(children[1].dev_path):
1131 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1132 children = []
1133 super(DRBD8, self).__init__(unique_id, children, size, params)
1134 self.major = self._DRBD_MAJOR
1135 version = self._GetVersion(self._GetProcData())
1136 if version["k_major"] != 8:
1137 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1138 " usage: kernel is %s.%s, ganeti wants 8.x",
1139 version["k_major"], version["k_minor"])
1140
1141 if (self._lhost is not None and self._lhost == self._rhost and
1142 self._lport == self._rport):
1143 raise ValueError("Invalid configuration data, same local/remote %s" %
1144 (unique_id,))
1145 self.Attach()
1146
1147 @classmethod
1169
1170 @classmethod
1172 """Find an unused DRBD device.
1173
1174 This is specific to 8.x as the minors are allocated dynamically,
1175 so non-existing numbers up to a max minor count are actually free.
1176
1177 """
1178 data = cls._GetProcData()
1179
1180 highest = None
1181 for line in data:
1182 match = cls._UNUSED_LINE_RE.match(line)
1183 if match:
1184 return int(match.group(1))
1185 match = cls._VALID_LINE_RE.match(line)
1186 if match:
1187 minor = int(match.group(1))
1188 highest = max(highest, minor)
1189 if highest is None:
1190 return 0
1191 if highest >= cls._MAX_MINORS:
1192 logging.error("Error: no free drbd minors!")
1193 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1194 return highest + 1
1195
1196 @classmethod
1198 """Return a parser for `drbd show` output.
1199
1200 This will either create or return an already-created parser for the
1201 output of the command `drbd show`.
1202
1203 """
1204 if cls._PARSE_SHOW is not None:
1205 return cls._PARSE_SHOW
1206
1207
1208 lbrace = pyp.Literal("{").suppress()
1209 rbrace = pyp.Literal("}").suppress()
1210 lbracket = pyp.Literal("[").suppress()
1211 rbracket = pyp.Literal("]").suppress()
1212 semi = pyp.Literal(";").suppress()
1213 colon = pyp.Literal(":").suppress()
1214
1215 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1216
1217 comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1218 defa = pyp.Literal("_is_default").suppress()
1219 dbl_quote = pyp.Literal('"').suppress()
1220
1221 keyword = pyp.Word(pyp.alphanums + "-")
1222
1223
1224 value = pyp.Word(pyp.alphanums + "_-/.:")
1225 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1226 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1227 pyp.Word(pyp.nums + ".") + colon + number)
1228 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1229 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1230 pyp.Optional(rbracket) + colon + number)
1231
1232 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1233
1234 device_value = pyp.Literal("minor").suppress() + number
1235
1236
1237 stmt = (~rbrace + keyword + ~lbrace +
1238 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1239 device_value) +
1240 pyp.Optional(defa) + semi +
1241 pyp.Optional(pyp.restOfLine).suppress())
1242
1243
1244 section_name = pyp.Word(pyp.alphas + "_")
1245 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1246
1247 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1248 bnf.ignore(comment)
1249
1250 cls._PARSE_SHOW = bnf
1251
1252 return bnf
1253
1254 @classmethod
1256 """Return the `drbdsetup show` data for a minor.
1257
1258 """
1259 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1260 if result.failed:
1261 logging.error("Can't display the drbd config: %s - %s",
1262 result.fail_reason, result.output)
1263 return None
1264 return result.stdout
1265
1266 @classmethod
1268 """Parse details about a given DRBD minor.
1269
1270 This return, if available, the local backing device (as a path)
1271 and the local and remote (ip, port) information from a string
1272 containing the output of the `drbdsetup show` command as returned
1273 by _GetShowData.
1274
1275 """
1276 data = {}
1277 if not out:
1278 return data
1279
1280 bnf = cls._GetShowParser()
1281
1282
1283 try:
1284 results = bnf.parseString(out)
1285 except pyp.ParseException, err:
1286 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1287
1288
1289 for section in results:
1290 sname = section[0]
1291 if sname == "_this_host":
1292 for lst in section[1:]:
1293 if lst[0] == "disk":
1294 data["local_dev"] = lst[1]
1295 elif lst[0] == "meta-disk":
1296 data["meta_dev"] = lst[1]
1297 data["meta_index"] = lst[2]
1298 elif lst[0] == "address":
1299 data["local_addr"] = tuple(lst[1:])
1300 elif sname == "_remote_host":
1301 for lst in section[1:]:
1302 if lst[0] == "address":
1303 data["remote_addr"] = tuple(lst[1:])
1304 return data
1305
1307 """Test if our local config matches with an existing device.
1308
1309 The parameter should be as returned from `_GetDevInfo()`. This
1310 method tests if our local backing device is the same as the one in
1311 the info parameter, in effect testing if we look like the given
1312 device.
1313
1314 """
1315 if self._children:
1316 backend, meta = self._children
1317 else:
1318 backend = meta = None
1319
1320 if backend is not None:
1321 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1322 else:
1323 retval = ("local_dev" not in info)
1324
1325 if meta is not None:
1326 retval = retval and ("meta_dev" in info and
1327 info["meta_dev"] == meta.dev_path)
1328 retval = retval and ("meta_index" in info and
1329 info["meta_index"] == 0)
1330 else:
1331 retval = retval and ("meta_dev" not in info and
1332 "meta_index" not in info)
1333 return retval
1334
1336 """Test if our network config matches with an existing device.
1337
1338 The parameter should be as returned from `_GetDevInfo()`. This
1339 method tests if our network configuration is the same as the one
1340 in the info parameter, in effect testing if we look like the given
1341 device.
1342
1343 """
1344 if (((self._lhost is None and not ("local_addr" in info)) and
1345 (self._rhost is None and not ("remote_addr" in info)))):
1346 return True
1347
1348 if self._lhost is None:
1349 return False
1350
1351 if not ("local_addr" in info and
1352 "remote_addr" in info):
1353 return False
1354
1355 retval = (info["local_addr"] == (self._lhost, self._lport))
1356 retval = (retval and
1357 info["remote_addr"] == (self._rhost, self._rport))
1358 return retval
1359
1361 """Configure the local part of a DRBD device.
1362
1363 """
1364 args = ["drbdsetup", self._DevPath(minor), "disk",
1365 backend, meta, "0",
1366 "-e", "detach",
1367 "--create-device"]
1368 if size:
1369 args.extend(["-d", "%sm" % size])
1370
1371 version = self._GetVersion(self._GetProcData())
1372 vmaj = version["k_major"]
1373 vmin = version["k_minor"]
1374 vrel = version["k_point"]
1375
1376 barrier_args = \
1377 self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
1378 self.params[constants.LDP_BARRIERS],
1379 self.params[constants.LDP_NO_META_FLUSH])
1380 args.extend(barrier_args)
1381
1382 if self.params[constants.LDP_DISK_CUSTOM]:
1383 args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
1384
1385 result = utils.RunCmd(args)
1386 if result.failed:
1387 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1388
1389 @classmethod
1392 """Compute the DRBD command line parameters for disk barriers
1393
1394 Returns a list of the disk barrier parameters as requested via the
1395 disabled_barriers and disable_meta_flush arguments, and according to the
1396 supported ones in the DRBD version vmaj.vmin.vrel
1397
1398 If the desired option is unsupported, raises errors.BlockDeviceError.
1399
1400 """
1401 disabled_barriers_set = frozenset(disabled_barriers)
1402 if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
1403 raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
1404 " barriers" % disabled_barriers)
1405
1406 args = []
1407
1408
1409
1410 if not vmaj == 8 and vmin in (0, 2, 3):
1411 raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
1412 (vmaj, vmin, vrel))
1413
1414 def _AppendOrRaise(option, min_version):
1415 """Helper for DRBD options"""
1416 if min_version is not None and vrel >= min_version:
1417 args.append(option)
1418 else:
1419 raise errors.BlockDeviceError("Could not use the option %s as the"
1420 " DRBD version %d.%d.%d does not support"
1421 " it." % (option, vmaj, vmin, vrel))
1422
1423
1424
1425
1426 meta_flush_supported = disk_flush_supported = {
1427 0: 12,
1428 2: 7,
1429 3: 0,
1430 }
1431
1432 disk_drain_supported = {
1433 2: 7,
1434 3: 0,
1435 }
1436
1437 disk_barriers_supported = {
1438 3: 0,
1439 }
1440
1441
1442 if disable_meta_flush:
1443 _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
1444 meta_flush_supported.get(vmin, None))
1445
1446
1447 if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
1448 _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
1449 disk_flush_supported.get(vmin, None))
1450
1451
1452 if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
1453 _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
1454 disk_drain_supported.get(vmin, None))
1455
1456
1457 if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
1458 _AppendOrRaise(cls._DISABLE_DISK_OPTION,
1459 disk_barriers_supported.get(vmin, None))
1460
1461 return args
1462
1463 - def _AssembleNet(self, minor, net_info, protocol,
1464 dual_pri=False, hmac=None, secret=None):
1465 """Configure the network part of the device.
1466
1467 """
1468 lhost, lport, rhost, rport = net_info
1469 if None in net_info:
1470
1471
1472 self._ShutdownNet(minor)
1473 return
1474
1475
1476
1477
1478
1479
1480
1481 sync_errors = self._SetMinorSyncParams(minor, self.params)
1482 if sync_errors:
1483 _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1484 (minor, utils.CommaJoin(sync_errors)))
1485
1486 if netutils.IP6Address.IsValid(lhost):
1487 if not netutils.IP6Address.IsValid(rhost):
1488 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1489 (minor, lhost, rhost))
1490 family = "ipv6"
1491 elif netutils.IP4Address.IsValid(lhost):
1492 if not netutils.IP4Address.IsValid(rhost):
1493 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1494 (minor, lhost, rhost))
1495 family = "ipv4"
1496 else:
1497 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1498
1499 args = ["drbdsetup", self._DevPath(minor), "net",
1500 "%s:%s:%s" % (family, lhost, lport),
1501 "%s:%s:%s" % (family, rhost, rport), protocol,
1502 "-A", "discard-zero-changes",
1503 "-B", "consensus",
1504 "--create-device",
1505 ]
1506 if dual_pri:
1507 args.append("-m")
1508 if hmac and secret:
1509 args.extend(["-a", hmac, "-x", secret])
1510
1511 if self.params[constants.LDP_NET_CUSTOM]:
1512 args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
1513
1514 result = utils.RunCmd(args)
1515 if result.failed:
1516 _ThrowError("drbd%d: can't setup network: %s - %s",
1517 minor, result.fail_reason, result.output)
1518
1519 def _CheckNetworkConfig():
1520 info = self._GetDevInfo(self._GetShowData(minor))
1521 if not "local_addr" in info or not "remote_addr" in info:
1522 raise utils.RetryAgain()
1523
1524 if (info["local_addr"] != (lhost, lport) or
1525 info["remote_addr"] != (rhost, rport)):
1526 raise utils.RetryAgain()
1527
1528 try:
1529 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1530 except utils.RetryTimeout:
1531 _ThrowError("drbd%d: timeout while configuring network", minor)
1532
1534 """Add a disk to the DRBD device.
1535
1536 """
1537 if self.minor is None:
1538 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1539 self._aminor)
1540 if len(devices) != 2:
1541 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1542 info = self._GetDevInfo(self._GetShowData(self.minor))
1543 if "local_dev" in info:
1544 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1545 backend, meta = devices
1546 if backend.dev_path is None or meta.dev_path is None:
1547 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1548 backend.Open()
1549 meta.Open()
1550 self._CheckMetaSize(meta.dev_path)
1551 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1552
1553 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1554 self._children = devices
1555
1557 """Detach the drbd device from local storage.
1558
1559 """
1560 if self.minor is None:
1561 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1562 self._aminor)
1563
1564 info = self._GetDevInfo(self._GetShowData(self.minor))
1565 if "local_dev" not in info:
1566 return
1567 if len(self._children) != 2:
1568 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1569 self._children)
1570 if self._children.count(None) == 2:
1571 logging.warning("drbd%d: requested detach while detached", self.minor)
1572 return
1573 if len(devices) != 2:
1574 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1575 for child, dev in zip(self._children, devices):
1576 if dev != child.dev_path:
1577 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1578 " RemoveChildren", self.minor, dev, child.dev_path)
1579
1580 self._ShutdownLocal(self.minor)
1581 self._children = []
1582
1583 @classmethod
1585 """Set the parameters of the DRBD syncer.
1586
1587 This is the low-level implementation.
1588
1589 @type minor: int
1590 @param minor: the drbd minor whose settings we change
1591 @type params: dict
1592 @param params: LD level disk parameters related to the synchronization
1593 @rtype: list
1594 @return: a list of error messages
1595
1596 """
1597
1598 args = ["drbdsetup", cls._DevPath(minor), "syncer"]
1599 if params[constants.LDP_DYNAMIC_RESYNC]:
1600 version = cls._GetVersion(cls._GetProcData())
1601 vmin = version["k_minor"]
1602 vrel = version["k_point"]
1603
1604
1605
1606 if vmin != 3 or vrel < 9:
1607 msg = ("The current DRBD version (8.%d.%d) does not support the "
1608 "dynamic resync speed controller" % (vmin, vrel))
1609 logging.error(msg)
1610 return [msg]
1611
1612 if params[constants.LDP_PLAN_AHEAD] == 0:
1613 msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
1614 " controller at DRBD level. If you want to disable it, please"
1615 " set the dynamic-resync disk parameter to False.")
1616 logging.error(msg)
1617 return [msg]
1618
1619
1620 args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
1621 "--c-fill-target", params[constants.LDP_FILL_TARGET],
1622 "--c-delay-target", params[constants.LDP_DELAY_TARGET],
1623 "--c-max-rate", params[constants.LDP_MAX_RATE],
1624 "--c-min-rate", params[constants.LDP_MIN_RATE],
1625 ])
1626
1627 else:
1628 args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
1629
1630 args.append("--create-device")
1631 result = utils.RunCmd(args)
1632 if result.failed:
1633 msg = ("Can't change syncer rate: %s - %s" %
1634 (result.fail_reason, result.output))
1635 logging.error(msg)
1636 return [msg]
1637
1638 return []
1639
1641 """Set the synchronization parameters of the DRBD syncer.
1642
1643 @type params: dict
1644 @param params: LD level disk parameters related to the synchronization
1645 @rtype: list
1646 @return: a list of error messages, emitted both by the current node and by
1647 children. An empty list means no errors
1648
1649 """
1650 if self.minor is None:
1651 err = "Not attached during SetSyncParams"
1652 logging.info(err)
1653 return [err]
1654
1655 children_result = super(DRBD8, self).SetSyncParams(params)
1656 children_result.extend(self._SetMinorSyncParams(self.minor, params))
1657 return children_result
1658
1660 """Pauses or resumes the sync of a DRBD device.
1661
1662 @param pause: Wether to pause or resume
1663 @return: the success of the operation
1664
1665 """
1666 if self.minor is None:
1667 logging.info("Not attached during PauseSync")
1668 return False
1669
1670 children_result = super(DRBD8, self).PauseResumeSync(pause)
1671
1672 if pause:
1673 cmd = "pause-sync"
1674 else:
1675 cmd = "resume-sync"
1676
1677 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1678 if result.failed:
1679 logging.error("Can't %s: %s - %s", cmd,
1680 result.fail_reason, result.output)
1681 return not result.failed and children_result
1682
1684 """Return device data from /proc.
1685
1686 """
1687 if self.minor is None:
1688 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1689 proc_info = self._MassageProcData(self._GetProcData())
1690 if self.minor not in proc_info:
1691 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1692 return DRBD8Status(proc_info[self.minor])
1693
1695 """Returns the sync status of the device.
1696
1697
1698 If sync_percent is None, it means all is ok
1699 If estimated_time is None, it means we can't estimate
1700 the time needed, otherwise it's the time left in seconds.
1701
1702
1703 We set the is_degraded parameter to True on two conditions:
1704 network not connected or local disk missing.
1705
1706 We compute the ldisk parameter based on whether we have a local
1707 disk or not.
1708
1709 @rtype: objects.BlockDevStatus
1710
1711 """
1712 if self.minor is None and not self.Attach():
1713 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1714
1715 stats = self.GetProcStatus()
1716 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1717
1718 if stats.is_disk_uptodate:
1719 ldisk_status = constants.LDS_OKAY
1720 elif stats.is_diskless:
1721 ldisk_status = constants.LDS_FAULTY
1722 else:
1723 ldisk_status = constants.LDS_UNKNOWN
1724
1725 return objects.BlockDevStatus(dev_path=self.dev_path,
1726 major=self.major,
1727 minor=self.minor,
1728 sync_percent=stats.sync_percent,
1729 estimated_time=stats.est_time,
1730 is_degraded=is_degraded,
1731 ldisk_status=ldisk_status)
1732
1733 - def Open(self, force=False):
1734 """Make the local state primary.
1735
1736 If the 'force' parameter is given, the '-o' option is passed to
1737 drbdsetup. Since this is a potentially dangerous operation, the
1738 force flag should be only given after creation, when it actually
1739 is mandatory.
1740
1741 """
1742 if self.minor is None and not self.Attach():
1743 logging.error("DRBD cannot attach to a device during open")
1744 return False
1745 cmd = ["drbdsetup", self.dev_path, "primary"]
1746 if force:
1747 cmd.append("-o")
1748 result = utils.RunCmd(cmd)
1749 if result.failed:
1750 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1751 result.output)
1752
1754 """Make the local state secondary.
1755
1756 This will, of course, fail if the device is in use.
1757
1758 """
1759 if self.minor is None and not self.Attach():
1760 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1761 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1762 if result.failed:
1763 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1764 self.minor, result.output)
1765
1767 """Removes network configuration.
1768
1769 This method shutdowns the network side of the device.
1770
1771 The method will wait up to a hardcoded timeout for the device to
1772 go into standalone after the 'disconnect' command before
1773 re-configuring it, as sometimes it takes a while for the
1774 disconnect to actually propagate and thus we might issue a 'net'
1775 command while the device is still connected. If the device will
1776 still be attached to the network and we time out, we raise an
1777 exception.
1778
1779 """
1780 if self.minor is None:
1781 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1782
1783 if None in (self._lhost, self._lport, self._rhost, self._rport):
1784 _ThrowError("drbd%d: DRBD disk missing network info in"
1785 " DisconnectNet()", self.minor)
1786
1787 class _DisconnectStatus:
1788 def __init__(self, ever_disconnected):
1789 self.ever_disconnected = ever_disconnected
1790
1791 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1792
1793 def _WaitForDisconnect():
1794 if self.GetProcStatus().is_standalone:
1795 return
1796
1797
1798
1799
1800 dstatus.ever_disconnected = \
1801 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1802
1803 raise utils.RetryAgain()
1804
1805
1806 start_time = time.time()
1807
1808 try:
1809
1810 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1811 self._NET_RECONFIG_TIMEOUT)
1812 except utils.RetryTimeout:
1813 if dstatus.ever_disconnected:
1814 msg = ("drbd%d: device did not react to the"
1815 " 'disconnect' command in a timely manner")
1816 else:
1817 msg = "drbd%d: can't shutdown network, even after multiple retries"
1818
1819 _ThrowError(msg, self.minor)
1820
1821 reconfig_time = time.time() - start_time
1822 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1823 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1824 self.minor, reconfig_time)
1825
1827 """Reconnects the network.
1828
1829 This method connects the network side of the device with a
1830 specified multi-master flag. The device needs to be 'Standalone'
1831 but have valid network configuration data.
1832
1833 Args:
1834 - multimaster: init the network in dual-primary mode
1835
1836 """
1837 if self.minor is None:
1838 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1839
1840 if None in (self._lhost, self._lport, self._rhost, self._rport):
1841 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1842
1843 status = self.GetProcStatus()
1844
1845 if not status.is_standalone:
1846 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1847
1848 self._AssembleNet(self.minor,
1849 (self._lhost, self._lport, self._rhost, self._rport),
1850 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1851 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1852
1854 """Check if our minor is configured.
1855
1856 This doesn't do any device configurations - it only checks if the
1857 minor is in a state different from Unconfigured.
1858
1859 Note that this function will not change the state of the system in
1860 any way (except in case of side-effects caused by reading from
1861 /proc).
1862
1863 """
1864 used_devs = self.GetUsedDevs()
1865 if self._aminor in used_devs:
1866 minor = self._aminor
1867 else:
1868 minor = None
1869
1870 self._SetFromMinor(minor)
1871 return minor is not None
1872
1874 """Assemble the drbd.
1875
1876 Method:
1877 - if we have a configured device, we try to ensure that it matches
1878 our config
1879 - if not, we create it from zero
1880 - anyway, set the device parameters
1881
1882 """
1883 super(DRBD8, self).Assemble()
1884
1885 self.Attach()
1886 if self.minor is None:
1887
1888 self._FastAssemble()
1889 else:
1890
1891
1892 self._SlowAssemble()
1893
1894 sync_errors = self.SetSyncParams(self.params)
1895 if sync_errors:
1896 _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1897 (self.minor, utils.CommaJoin(sync_errors)))
1898
1900 """Assembles the DRBD device from a (partially) configured device.
1901
1902 In case of partially attached (local device matches but no network
1903 setup), we perform the network attach. If successful, we re-test
1904 the attach if can return success.
1905
1906 """
1907
1908
1909 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1910 for minor in (self._aminor,):
1911 info = self._GetDevInfo(self._GetShowData(minor))
1912 match_l = self._MatchesLocal(info)
1913 match_r = self._MatchesNet(info)
1914
1915 if match_l and match_r:
1916
1917 break
1918
1919 if match_l and not match_r and "local_addr" not in info:
1920
1921 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1922 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1923 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1924 break
1925 else:
1926 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1927 " show' disagrees", minor)
1928
1929 if match_r and "local_dev" not in info:
1930
1931 self._AssembleLocal(minor, self._children[0].dev_path,
1932 self._children[1].dev_path, self.size)
1933 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1934 break
1935 else:
1936 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1937 " show' disagrees", minor)
1938
1939
1940
1941
1942
1943 if (match_l and "local_dev" in info and
1944 not match_r and "local_addr" in info):
1945
1946
1947
1948
1949 try:
1950 self._ShutdownNet(minor)
1951 except errors.BlockDeviceError, err:
1952 _ThrowError("drbd%d: device has correct local storage, wrong"
1953 " remote peer and is unable to disconnect in order"
1954 " to attach to the correct peer: %s", minor, str(err))
1955
1956
1957
1958 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1959 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1960 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1961 break
1962 else:
1963 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1964 " show' disagrees", minor)
1965
1966 else:
1967 minor = None
1968
1969 self._SetFromMinor(minor)
1970 if minor is None:
1971 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1972 self._aminor)
1973
1975 """Assemble the drbd device from zero.
1976
1977 This is run when in Assemble we detect our minor is unused.
1978
1979 """
1980 minor = self._aminor
1981 if self._children and self._children[0] and self._children[1]:
1982 self._AssembleLocal(minor, self._children[0].dev_path,
1983 self._children[1].dev_path, self.size)
1984 if self._lhost and self._lport and self._rhost and self._rport:
1985 self._AssembleNet(minor,
1986 (self._lhost, self._lport, self._rhost, self._rport),
1987 constants.DRBD_NET_PROTOCOL,
1988 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1989 self._SetFromMinor(minor)
1990
1991 @classmethod
1993 """Detach from the local device.
1994
1995 I/Os will continue to be served from the remote device. If we
1996 don't have a remote device, this operation will fail.
1997
1998 """
1999 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2000 if result.failed:
2001 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
2002
2003 @classmethod
2005 """Disconnect from the remote peer.
2006
2007 This fails if we don't have a local device.
2008
2009 """
2010 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2011 if result.failed:
2012 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
2013
2014 @classmethod
2016 """Deactivate the device.
2017
2018 This will, of course, fail if the device is in use.
2019
2020 """
2021 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2022 if result.failed:
2023 _ThrowError("drbd%d: can't shutdown drbd device: %s",
2024 minor, result.output)
2025
2027 """Shutdown the DRBD device.
2028
2029 """
2030 if self.minor is None and not self.Attach():
2031 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
2032 return
2033 minor = self.minor
2034 self.minor = None
2035 self.dev_path = None
2036 self._ShutdownAll(minor)
2037
2039 """Stub remove for DRBD devices.
2040
2041 """
2042 self.Shutdown()
2043
2044 @classmethod
2045 - def Create(cls, unique_id, children, size, params):
2046 """Create a new DRBD8 device.
2047
2048 Since DRBD devices are not created per se, just assembled, this
2049 function only initializes the metadata.
2050
2051 """
2052 if len(children) != 2:
2053 raise errors.ProgrammerError("Invalid setup for the drbd device")
2054
2055 aminor = unique_id[4]
2056 proc_info = cls._MassageProcData(cls._GetProcData())
2057 if aminor in proc_info:
2058 status = DRBD8Status(proc_info[aminor])
2059 in_use = status.is_in_use
2060 else:
2061 in_use = False
2062 if in_use:
2063 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
2064 meta = children[1]
2065 meta.Assemble()
2066 if not meta.Attach():
2067 _ThrowError("drbd%d: can't attach to meta device '%s'",
2068 aminor, meta)
2069 cls._CheckMetaSize(meta.dev_path)
2070 cls._InitMeta(aminor, meta.dev_path)
2071 return cls(unique_id, children, size, params)
2072
2073 - def Grow(self, amount, dryrun):
2074 """Resize the DRBD device and its backing storage.
2075
2076 """
2077 if self.minor is None:
2078 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
2079 if len(self._children) != 2 or None in self._children:
2080 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
2081 self._children[0].Grow(amount, dryrun)
2082 if dryrun:
2083
2084 return
2085 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
2086 "%dm" % (self.size + amount)])
2087 if result.failed:
2088 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
2089
2092 """File device.
2093
2094 This class represents the a file storage backend device.
2095
2096 The unique_id for the file device is a (file_driver, file_path) tuple.
2097
2098 """
2099 - def __init__(self, unique_id, children, size, params):
2100 """Initalizes a file device backend.
2101
2102 """
2103 if children:
2104 raise errors.BlockDeviceError("Invalid setup for file device")
2105 super(FileStorage, self).__init__(unique_id, children, size, params)
2106 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2107 raise ValueError("Invalid configuration data %s" % str(unique_id))
2108 self.driver = unique_id[0]
2109 self.dev_path = unique_id[1]
2110 self.Attach()
2111
2113 """Assemble the device.
2114
2115 Checks whether the file device exists, raises BlockDeviceError otherwise.
2116
2117 """
2118 if not os.path.exists(self.dev_path):
2119 _ThrowError("File device '%s' does not exist" % self.dev_path)
2120
2122 """Shutdown the device.
2123
2124 This is a no-op for the file type, as we don't deactivate
2125 the file on shutdown.
2126
2127 """
2128 pass
2129
2130 - def Open(self, force=False):
2131 """Make the device ready for I/O.
2132
2133 This is a no-op for the file type.
2134
2135 """
2136 pass
2137
2139 """Notifies that the device will no longer be used for I/O.
2140
2141 This is a no-op for the file type.
2142
2143 """
2144 pass
2145
2147 """Remove the file backing the block device.
2148
2149 @rtype: boolean
2150 @return: True if the removal was successful
2151
2152 """
2153 try:
2154 os.remove(self.dev_path)
2155 except OSError, err:
2156 if err.errno != errno.ENOENT:
2157 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2158
2160 """Renames the file.
2161
2162 """
2163
2164 _ThrowError("Rename is not supported for file-based storage")
2165
2166 - def Grow(self, amount, dryrun):
2167 """Grow the file
2168
2169 @param amount: the amount (in mebibytes) to grow with
2170
2171 """
2172
2173 self.Assemble()
2174 current_size = self.GetActualSize()
2175 new_size = current_size + amount * 1024 * 1024
2176 assert new_size > current_size, "Cannot Grow with a negative amount"
2177
2178 if dryrun:
2179 return
2180 try:
2181 f = open(self.dev_path, "a+")
2182 f.truncate(new_size)
2183 f.close()
2184 except EnvironmentError, err:
2185 _ThrowError("Error in file growth: %", str(err))
2186
2188 """Attach to an existing file.
2189
2190 Check if this file already exists.
2191
2192 @rtype: boolean
2193 @return: True if file exists
2194
2195 """
2196 self.attached = os.path.exists(self.dev_path)
2197 return self.attached
2198
2200 """Return the actual disk size.
2201
2202 @note: the device needs to be active when this is called
2203
2204 """
2205 assert self.attached, "BlockDevice not attached in GetActualSize()"
2206 try:
2207 st = os.stat(self.dev_path)
2208 return st.st_size
2209 except OSError, err:
2210 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2211
2212 @classmethod
2213 - def Create(cls, unique_id, children, size, params):
2214 """Create a new file.
2215
2216 @param size: the size of file in MiB
2217
2218 @rtype: L{bdev.FileStorage}
2219 @return: an instance of FileStorage
2220
2221 """
2222 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2223 raise ValueError("Invalid configuration data %s" % str(unique_id))
2224 dev_path = unique_id[1]
2225 try:
2226 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2227 f = os.fdopen(fd, "w")
2228 f.truncate(size * 1024 * 1024)
2229 f.close()
2230 except EnvironmentError, err:
2231 if err.errno == errno.EEXIST:
2232 _ThrowError("File already existing: %s", dev_path)
2233 _ThrowError("Error in file creation: %", str(err))
2234
2235 return FileStorage(unique_id, children, size, params)
2236
2239 """A block device with persistent node
2240
2241 May be either directly attached, or exposed through DM (e.g. dm-multipath).
2242 udev helpers are probably required to give persistent, human-friendly
2243 names.
2244
2245 For the time being, pathnames are required to lie under /dev.
2246
2247 """
2248 - def __init__(self, unique_id, children, size, params):
2249 """Attaches to a static block device.
2250
2251 The unique_id is a path under /dev.
2252
2253 """
2254 super(PersistentBlockDevice, self).__init__(unique_id, children, size,
2255 params)
2256 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2257 raise ValueError("Invalid configuration data %s" % str(unique_id))
2258 self.dev_path = unique_id[1]
2259 if not os.path.realpath(self.dev_path).startswith("/dev/"):
2260 raise ValueError("Full path '%s' lies outside /dev" %
2261 os.path.realpath(self.dev_path))
2262
2263
2264
2265
2266 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2267 raise ValueError("Got persistent block device of invalid type: %s" %
2268 unique_id[0])
2269
2270 self.major = self.minor = None
2271 self.Attach()
2272
2273 @classmethod
2274 - def Create(cls, unique_id, children, size, params):
2275 """Create a new device
2276
2277 This is a noop, we only return a PersistentBlockDevice instance
2278
2279 """
2280 return PersistentBlockDevice(unique_id, children, 0, params)
2281
2283 """Remove a device
2284
2285 This is a noop
2286
2287 """
2288 pass
2289
2291 """Rename this device.
2292
2293 """
2294 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2295
2297 """Attach to an existing block device.
2298
2299
2300 """
2301 self.attached = False
2302 try:
2303 st = os.stat(self.dev_path)
2304 except OSError, err:
2305 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2306 return False
2307
2308 if not stat.S_ISBLK(st.st_mode):
2309 logging.error("%s is not a block device", self.dev_path)
2310 return False
2311
2312 self.major = os.major(st.st_rdev)
2313 self.minor = os.minor(st.st_rdev)
2314 self.attached = True
2315
2316 return True
2317
2319 """Assemble the device.
2320
2321 """
2322 pass
2323
2325 """Shutdown the device.
2326
2327 """
2328 pass
2329
2330 - def Open(self, force=False):
2331 """Make the device ready for I/O.
2332
2333 """
2334 pass
2335
2337 """Notifies that the device will no longer be used for I/O.
2338
2339 """
2340 pass
2341
2342 - def Grow(self, amount, dryrun):
2343 """Grow the logical volume.
2344
2345 """
2346 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2347
2350 """A RADOS Block Device (rbd).
2351
2352 This class implements the RADOS Block Device for the backend. You need
2353 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
2354 this to be functional.
2355
2356 """
2357 - def __init__(self, unique_id, children, size, params):
2358 """Attaches to an rbd device.
2359
2360 """
2361 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
2362 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2363 raise ValueError("Invalid configuration data %s" % str(unique_id))
2364
2365 self.driver, self.rbd_name = unique_id
2366
2367 self.major = self.minor = None
2368 self.Attach()
2369
2370 @classmethod
2371 - def Create(cls, unique_id, children, size, params):
2372 """Create a new rbd device.
2373
2374 Provision a new rbd volume inside a RADOS pool.
2375
2376 """
2377 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2378 raise errors.ProgrammerError("Invalid configuration data %s" %
2379 str(unique_id))
2380 rbd_pool = params[constants.LDP_POOL]
2381 rbd_name = unique_id[1]
2382
2383
2384 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
2385 rbd_name, "--size", "%s" % size]
2386 result = utils.RunCmd(cmd)
2387 if result.failed:
2388 _ThrowError("rbd creation failed (%s): %s",
2389 result.fail_reason, result.output)
2390
2391 return RADOSBlockDevice(unique_id, children, size, params)
2392
2394 """Remove the rbd device.
2395
2396 """
2397 rbd_pool = self.params[constants.LDP_POOL]
2398 rbd_name = self.unique_id[1]
2399
2400 if not self.minor and not self.Attach():
2401
2402 return
2403
2404
2405 self.Shutdown()
2406
2407
2408 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
2409 result = utils.RunCmd(cmd)
2410 if result.failed:
2411 _ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
2412 result.fail_reason, result.output)
2413
2415 """Rename this device.
2416
2417 """
2418 pass
2419
2421 """Attach to an existing rbd device.
2422
2423 This method maps the rbd volume that matches our name with
2424 an rbd device and then attaches to this device.
2425
2426 """
2427 self.attached = False
2428
2429
2430 self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
2431
2432 try:
2433 st = os.stat(self.dev_path)
2434 except OSError, err:
2435 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2436 return False
2437
2438 if not stat.S_ISBLK(st.st_mode):
2439 logging.error("%s is not a block device", self.dev_path)
2440 return False
2441
2442 self.major = os.major(st.st_rdev)
2443 self.minor = os.minor(st.st_rdev)
2444 self.attached = True
2445
2446 return True
2447
2449 """Maps existing rbd volumes to block devices.
2450
2451 This method should be idempotent if the mapping already exists.
2452
2453 @rtype: string
2454 @return: the block device path that corresponds to the volume
2455
2456 """
2457 pool = self.params[constants.LDP_POOL]
2458 name = unique_id[1]
2459
2460
2461 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2462 result = utils.RunCmd(showmap_cmd)
2463 if result.failed:
2464 _ThrowError("rbd showmapped failed (%s): %s",
2465 result.fail_reason, result.output)
2466
2467 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2468
2469 if rbd_dev:
2470
2471 return rbd_dev
2472
2473
2474 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
2475 result = utils.RunCmd(map_cmd)
2476 if result.failed:
2477 _ThrowError("rbd map failed (%s): %s",
2478 result.fail_reason, result.output)
2479
2480
2481 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2482 result = utils.RunCmd(showmap_cmd)
2483 if result.failed:
2484 _ThrowError("rbd map succeeded, but showmapped failed (%s): %s",
2485 result.fail_reason, result.output)
2486
2487 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2488
2489 if not rbd_dev:
2490 _ThrowError("rbd map succeeded, but could not find the rbd block"
2491 " device in output of showmapped, for volume: %s", name)
2492
2493
2494 return rbd_dev
2495
2496 @staticmethod
2498 """Parse the output of `rbd showmapped'.
2499
2500 This method parses the output of `rbd showmapped' and returns
2501 the rbd block device path (e.g. /dev/rbd0) that matches the
2502 given rbd volume.
2503
2504 @type output: string
2505 @param output: the whole output of `rbd showmapped'
2506 @type volume_name: string
2507 @param volume_name: the name of the volume whose device we search for
2508 @rtype: string or None
2509 @return: block device path if the volume is mapped, else None
2510
2511 """
2512 allfields = 5
2513 volumefield = 2
2514 devicefield = 4
2515
2516 field_sep = "\t"
2517
2518 lines = output.splitlines()
2519 splitted_lines = map(lambda l: l.split(field_sep), lines)
2520
2521
2522 if not splitted_lines:
2523 _ThrowError("rbd showmapped returned empty output")
2524
2525
2526 field_cnt = len(splitted_lines[0])
2527 if field_cnt != allfields:
2528 _ThrowError("Cannot parse rbd showmapped output because its format"
2529 " seems to have changed; expected %s fields, found %s",
2530 allfields, field_cnt)
2531
2532 matched_lines = \
2533 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
2534 splitted_lines)
2535
2536 if len(matched_lines) > 1:
2537 _ThrowError("The rbd volume %s is mapped more than once."
2538 " This shouldn't happen, try to unmap the extra"
2539 " devices manually.", volume_name)
2540
2541 if matched_lines:
2542
2543 rbd_dev = matched_lines[0][devicefield]
2544 return rbd_dev
2545
2546
2547 return None
2548
2550 """Assemble the device.
2551
2552 """
2553 pass
2554
2556 """Shutdown the device.
2557
2558 """
2559 if not self.minor and not self.Attach():
2560
2561 return
2562
2563
2564 self._UnmapVolumeFromBlockdev(self.unique_id)
2565
2566 self.minor = None
2567 self.dev_path = None
2568
2570 """Unmaps the rbd device from the Volume it is mapped.
2571
2572 Unmaps the rbd device from the Volume it was previously mapped to.
2573 This method should be idempotent if the Volume isn't mapped.
2574
2575 """
2576 pool = self.params[constants.LDP_POOL]
2577 name = unique_id[1]
2578
2579
2580 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2581 result = utils.RunCmd(showmap_cmd)
2582 if result.failed:
2583 _ThrowError("rbd showmapped failed [during unmap](%s): %s",
2584 result.fail_reason, result.output)
2585
2586 rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2587
2588 if rbd_dev:
2589
2590 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
2591 result = utils.RunCmd(unmap_cmd)
2592 if result.failed:
2593 _ThrowError("rbd unmap failed (%s): %s",
2594 result.fail_reason, result.output)
2595
2596 - def Open(self, force=False):
2597 """Make the device ready for I/O.
2598
2599 """
2600 pass
2601
2603 """Notifies that the device will no longer be used for I/O.
2604
2605 """
2606 pass
2607
2608 - def Grow(self, amount, dryrun):
2609 """Grow the Volume.
2610
2611 @type amount: integer
2612 @param amount: the amount (in mebibytes) to grow with
2613 @type dryrun: boolean
2614 @param dryrun: whether to execute the operation in simulation mode
2615 only, without actually increasing the size
2616
2617 """
2618 if not self.Attach():
2619 _ThrowError("Can't attach to rbd device during Grow()")
2620
2621 if dryrun:
2622
2623
2624
2625 return
2626
2627 rbd_pool = self.params[constants.LDP_POOL]
2628 rbd_name = self.unique_id[1]
2629 new_size = self.size + amount
2630
2631
2632 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
2633 rbd_name, "--size", "%s" % new_size]
2634 result = utils.RunCmd(cmd)
2635 if result.failed:
2636 _ThrowError("rbd resize failed (%s): %s",
2637 result.fail_reason, result.output)
2638
2639
2640 DEV_MAP = {
2641 constants.LD_LV: LogicalVolume,
2642 constants.LD_DRBD8: DRBD8,
2643 constants.LD_BLOCKDEV: PersistentBlockDevice,
2644 constants.LD_RBD: RADOSBlockDevice,
2645 }
2646
2647 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2648 DEV_MAP[constants.LD_FILE] = FileStorage
2654
2657 """Verifies if all disk parameters are set.
2658
2659 """
2660 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
2661 if missing:
2662 raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
2663 missing)
2664
2667 """Search for an existing, assembled device.
2668
2669 This will succeed only if the device exists and is assembled, but it
2670 does not do any actions in order to activate the device.
2671
2672 @type disk: L{objects.Disk}
2673 @param disk: the disk object to find
2674 @type children: list of L{bdev.BlockDev}
2675 @param children: the list of block devices that are children of the device
2676 represented by the disk parameter
2677
2678 """
2679 _VerifyDiskType(disk.dev_type)
2680 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2681 disk.params)
2682 if not device.attached:
2683 return None
2684 return device
2685
2688 """Try to attach or assemble an existing device.
2689
2690 This will attach to assemble the device, as needed, to bring it
2691 fully up. It must be safe to run on already-assembled devices.
2692
2693 @type disk: L{objects.Disk}
2694 @param disk: the disk object to assemble
2695 @type children: list of L{bdev.BlockDev}
2696 @param children: the list of block devices that are children of the device
2697 represented by the disk parameter
2698
2699 """
2700 _VerifyDiskType(disk.dev_type)
2701 _VerifyDiskParams(disk)
2702 device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2703 disk.params)
2704 device.Assemble()
2705 return device
2706
2707
2708 -def Create(disk, children):
2709 """Create a device.
2710
2711 @type disk: L{objects.Disk}
2712 @param disk: the disk object to create
2713 @type children: list of L{bdev.BlockDev}
2714 @param children: the list of block devices that are children of the device
2715 represented by the disk parameter
2716
2717 """
2718 _VerifyDiskType(disk.dev_type)
2719 _VerifyDiskParams(disk)
2720 device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
2721 disk.params)
2722 return device
2723