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 stat
28 import pyparsing as pyp
29 import os
30 import logging
31
32 from ganeti import utils
33 from ganeti import errors
34 from ganeti import constants
35 from ganeti import objects
36 from ganeti import compat
37 from ganeti import netutils
38
39
40
41 _DEVICE_READ_SIZE = 128 * 1024
45 """Executes the given function, ignoring BlockDeviceErrors.
46
47 This is used in order to simplify the execution of cleanup or
48 rollback functions.
49
50 @rtype: boolean
51 @return: True when fn didn't raise an exception, False otherwise
52
53 """
54 try:
55 fn(*args, **kwargs)
56 return True
57 except errors.BlockDeviceError, err:
58 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
59 return False
60
63 """Log an error to the node daemon and the raise an exception.
64
65 @type msg: string
66 @param msg: the text of the exception
67 @raise errors.BlockDeviceError
68
69 """
70 if args:
71 msg = msg % args
72 logging.error(msg)
73 raise errors.BlockDeviceError(msg)
74
77 """Check if we can read from the given device.
78
79 This tries to read the first 128k of the device.
80
81 """
82 try:
83 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
84 return True
85 except EnvironmentError:
86 logging.warning("Can't read from device %s", path, exc_info=True)
87 return False
88
91 """Block device abstract class.
92
93 A block device can be in the following states:
94 - not existing on the system, and by `Create()` it goes into:
95 - existing but not setup/not active, and by `Assemble()` goes into:
96 - active read-write and by `Open()` it goes into
97 - online (=used, or ready for use)
98
99 A device can also be online but read-only, however we are not using
100 the readonly state (LV has it, if needed in the future) and we are
101 usually looking at this like at a stack, so it's easier to
102 conceptualise the transition from not-existing to online and back
103 like a linear one.
104
105 The many different states of the device are due to the fact that we
106 need to cover many device types:
107 - logical volumes are created, lvchange -a y $lv, and used
108 - drbd devices are attached to a local disk/remote peer and made primary
109
110 A block device is identified by three items:
111 - the /dev path of the device (dynamic)
112 - a unique ID of the device (static)
113 - it's major/minor pair (dynamic)
114
115 Not all devices implement both the first two as distinct items. LVM
116 logical volumes have their unique ID (the pair volume group, logical
117 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
118 the /dev path is again dynamic and the unique id is the pair (host1,
119 dev1), (host2, dev2).
120
121 You can get to a device in two ways:
122 - creating the (real) device, which returns you
123 an attached instance (lvcreate)
124 - attaching of a python instance to an existing (real) device
125
126 The second point, the attachement to a device, is different
127 depending on whether the device is assembled or not. At init() time,
128 we search for a device with the same unique_id as us. If found,
129 good. It also means that the device is already assembled. If not,
130 after assembly we'll have our correct major/minor.
131
132 """
133 - def __init__(self, unique_id, children, size):
134 self._children = children
135 self.dev_path = None
136 self.unique_id = unique_id
137 self.major = None
138 self.minor = None
139 self.attached = False
140 self.size = size
141
143 """Assemble the device from its components.
144
145 Implementations of this method by child classes must ensure that:
146 - after the device has been assembled, it knows its major/minor
147 numbers; this allows other devices (usually parents) to probe
148 correctly for their children
149 - calling this method on an existing, in-use device is safe
150 - if the device is already configured (and in an OK state),
151 this method is idempotent
152
153 """
154 pass
155
157 """Find a device which matches our config and attach to it.
158
159 """
160 raise NotImplementedError
161
163 """Notifies that the device will no longer be used for I/O.
164
165 """
166 raise NotImplementedError
167
168 @classmethod
169 - def Create(cls, unique_id, children, size):
170 """Create the device.
171
172 If the device cannot be created, it will return None
173 instead. Error messages go to the logging system.
174
175 Note that for some devices, the unique_id is used, and for other,
176 the children. The idea is that these two, taken together, are
177 enough for both creation and assembly (later).
178
179 """
180 raise NotImplementedError
181
183 """Remove this device.
184
185 This makes sense only for some of the device types: LV and file
186 storage. Also note that if the device can't attach, the removal
187 can't be completed.
188
189 """
190 raise NotImplementedError
191
193 """Rename this device.
194
195 This may or may not make sense for a given device type.
196
197 """
198 raise NotImplementedError
199
200 - def Open(self, force=False):
201 """Make the device ready for use.
202
203 This makes the device ready for I/O. For now, just the DRBD
204 devices need this.
205
206 The force parameter signifies that if the device has any kind of
207 --force thing, it should be used, we know what we are doing.
208
209 """
210 raise NotImplementedError
211
213 """Shut down the device, freeing its children.
214
215 This undoes the `Assemble()` work, except for the child
216 assembling; as such, the children on the device are still
217 assembled after this call.
218
219 """
220 raise NotImplementedError
221
223 """Adjust the sync speed of the mirror.
224
225 In case this is not a mirroring device, this is no-op.
226
227 """
228 result = True
229 if self._children:
230 for child in self._children:
231 result = result and child.SetSyncSpeed(speed)
232 return result
233
235 """Pause/Resume the sync of the mirror.
236
237 In case this is not a mirroring device, this is no-op.
238
239 @param pause: Wheater to pause or resume
240
241 """
242 result = True
243 if self._children:
244 for child in self._children:
245 result = result and child.PauseResumeSync(pause)
246 return result
247
249 """Returns the sync status of the device.
250
251 If this device is a mirroring device, this function returns the
252 status of the mirror.
253
254 If sync_percent is None, it means the device is not syncing.
255
256 If estimated_time is None, it means we can't estimate
257 the time needed, otherwise it's the time left in seconds.
258
259 If is_degraded is True, it means the device is missing
260 redundancy. This is usually a sign that something went wrong in
261 the device setup, if sync_percent is None.
262
263 The ldisk parameter represents the degradation of the local
264 data. This is only valid for some devices, the rest will always
265 return False (not degraded).
266
267 @rtype: objects.BlockDevStatus
268
269 """
270 return objects.BlockDevStatus(dev_path=self.dev_path,
271 major=self.major,
272 minor=self.minor,
273 sync_percent=None,
274 estimated_time=None,
275 is_degraded=False,
276 ldisk_status=constants.LDS_OKAY)
277
279 """Calculate the mirror status recursively for our children.
280
281 The return value is the same as for `GetSyncStatus()` except the
282 minimum percent and maximum time are calculated across our
283 children.
284
285 @rtype: objects.BlockDevStatus
286
287 """
288 status = self.GetSyncStatus()
289
290 min_percent = status.sync_percent
291 max_time = status.estimated_time
292 is_degraded = status.is_degraded
293 ldisk_status = status.ldisk_status
294
295 if self._children:
296 for child in self._children:
297 child_status = child.GetSyncStatus()
298
299 if min_percent is None:
300 min_percent = child_status.sync_percent
301 elif child_status.sync_percent is not None:
302 min_percent = min(min_percent, child_status.sync_percent)
303
304 if max_time is None:
305 max_time = child_status.estimated_time
306 elif child_status.estimated_time is not None:
307 max_time = max(max_time, child_status.estimated_time)
308
309 is_degraded = is_degraded or child_status.is_degraded
310
311 if ldisk_status is None:
312 ldisk_status = child_status.ldisk_status
313 elif child_status.ldisk_status is not None:
314 ldisk_status = max(ldisk_status, child_status.ldisk_status)
315
316 return objects.BlockDevStatus(dev_path=self.dev_path,
317 major=self.major,
318 minor=self.minor,
319 sync_percent=min_percent,
320 estimated_time=max_time,
321 is_degraded=is_degraded,
322 ldisk_status=ldisk_status)
323
325 """Update metadata with info text.
326
327 Only supported for some device types.
328
329 """
330 for child in self._children:
331 child.SetInfo(text)
332
333 - def Grow(self, amount, dryrun):
334 """Grow the block device.
335
336 @type amount: integer
337 @param amount: the amount (in mebibytes) to grow with
338 @type dryrun: boolean
339 @param dryrun: whether to execute the operation in simulation mode
340 only, without actually increasing the size
341
342 """
343 raise NotImplementedError
344
346 """Return the actual disk size.
347
348 @note: the device needs to be active when this is called
349
350 """
351 assert self.attached, "BlockDevice not attached in GetActualSize()"
352 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
353 if result.failed:
354 _ThrowError("blockdev failed (%s): %s",
355 result.fail_reason, result.output)
356 try:
357 sz = int(result.output.strip())
358 except (ValueError, TypeError), err:
359 _ThrowError("Failed to parse blockdev output: %s", str(err))
360 return sz
361
363 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
364 (self.__class__, self.unique_id, self._children,
365 self.major, self.minor, self.dev_path))
366
369 """Logical Volume block device.
370
371 """
372 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
373 _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
374 _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
375
376 - def __init__(self, unique_id, children, size):
377 """Attaches to a LV device.
378
379 The unique_id is a tuple (vg_name, lv_name)
380
381 """
382 super(LogicalVolume, self).__init__(unique_id, children, size)
383 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
384 raise ValueError("Invalid configuration data %s" % str(unique_id))
385 self._vg_name, self._lv_name = unique_id
386 self._ValidateName(self._vg_name)
387 self._ValidateName(self._lv_name)
388 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
389 self._degraded = True
390 self.major = self.minor = self.pe_size = self.stripe_count = None
391 self.Attach()
392
393 @classmethod
394 - def Create(cls, unique_id, children, size):
395 """Create a new logical volume.
396
397 """
398 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
399 raise errors.ProgrammerError("Invalid configuration data %s" %
400 str(unique_id))
401 vg_name, lv_name = unique_id
402 cls._ValidateName(vg_name)
403 cls._ValidateName(lv_name)
404 pvs_info = cls.GetPVInfo([vg_name])
405 if not pvs_info:
406 _ThrowError("Can't compute PV info for vg %s", vg_name)
407 pvs_info.sort()
408 pvs_info.reverse()
409
410 pvlist = [pv[1] for pv in pvs_info]
411 if compat.any(":" in v for v in pvlist):
412 _ThrowError("Some of your PVs have the invalid character ':' in their"
413 " name, this is not supported - please filter them out"
414 " in lvm.conf using either 'filter' or 'preferred_names'")
415 free_size = sum([pv[0] for pv in pvs_info])
416 current_pvs = len(pvlist)
417 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
418
419
420
421 if free_size < size:
422 _ThrowError("Not enough free space: required %s,"
423 " available %s", size, free_size)
424 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
425
426
427
428
429 for stripes_arg in range(stripes, 0, -1):
430 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
431 if not result.failed:
432 break
433 if result.failed:
434 _ThrowError("LV create failed (%s): %s",
435 result.fail_reason, result.output)
436 return LogicalVolume(unique_id, children, size)
437
438 @staticmethod
440 """Returns LVM Volumen infos using lvm_cmd
441
442 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
443 @param fields: Fields to return
444 @return: A list of dicts each with the parsed fields
445
446 """
447 if not fields:
448 raise errors.ProgrammerError("No fields specified")
449
450 sep = "|"
451 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
452 "--separator=%s" % sep, "-o%s" % ",".join(fields)]
453
454 result = utils.RunCmd(cmd)
455 if result.failed:
456 raise errors.CommandError("Can't get the volume information: %s - %s" %
457 (result.fail_reason, result.output))
458
459 data = []
460 for line in result.stdout.splitlines():
461 splitted_fields = line.strip().split(sep)
462
463 if len(fields) != len(splitted_fields):
464 raise errors.CommandError("Can't parse %s output: line '%s'" %
465 (lvm_cmd, line))
466
467 data.append(splitted_fields)
468
469 return data
470
471 @classmethod
472 - def GetPVInfo(cls, vg_names, filter_allocatable=True):
473 """Get the free space info for PVs in a volume group.
474
475 @param vg_names: list of volume group names, if empty all will be returned
476 @param filter_allocatable: whether to skip over unallocatable PVs
477
478 @rtype: list
479 @return: list of tuples (free_space, name) with free_space in mebibytes
480
481 """
482 try:
483 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
484 "pv_attr"])
485 except errors.GenericError, err:
486 logging.error("Can't get PV information: %s", err)
487 return None
488
489 data = []
490 for pv_name, vg_name, pv_free, pv_attr in info:
491
492 if filter_allocatable and pv_attr[0] != "a":
493 continue
494
495 if vg_names and vg_name not in vg_names:
496 continue
497 data.append((float(pv_free), pv_name, vg_name))
498
499 return data
500
501 @classmethod
502 - def GetVGInfo(cls, vg_names, filter_readonly=True):
503 """Get the free space info for specific VGs.
504
505 @param vg_names: list of volume group names, if empty all will be returned
506 @param filter_readonly: whether to skip over readonly VGs
507
508 @rtype: list
509 @return: list of tuples (free_space, total_size, name) with free_space in
510 MiB
511
512 """
513 try:
514 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
515 "vg_size"])
516 except errors.GenericError, err:
517 logging.error("Can't get VG information: %s", err)
518 return None
519
520 data = []
521 for vg_name, vg_free, vg_attr, vg_size in info:
522
523 if filter_readonly and vg_attr[0] == "r":
524 continue
525
526 if vg_names and vg_name not in vg_names:
527 continue
528 data.append((float(vg_free), float(vg_size), vg_name))
529
530 return data
531
532 @classmethod
534 """Validates that a given name is valid as VG or LV name.
535
536 The list of valid characters and restricted names is taken out of
537 the lvm(8) manpage, with the simplification that we enforce both
538 VG and LV restrictions on the names.
539
540 """
541 if (not cls._VALID_NAME_RE.match(name) or
542 name in cls._INVALID_NAMES or
543 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
544 _ThrowError("Invalid LVM name '%s'", name)
545
547 """Remove this logical volume.
548
549 """
550 if not self.minor and not self.Attach():
551
552 return
553 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
554 (self._vg_name, self._lv_name)])
555 if result.failed:
556 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
557
559 """Rename this logical volume.
560
561 """
562 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
563 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
564 new_vg, new_name = new_id
565 if new_vg != self._vg_name:
566 raise errors.ProgrammerError("Can't move a logical volume across"
567 " volume groups (from %s to to %s)" %
568 (self._vg_name, new_vg))
569 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
570 if result.failed:
571 _ThrowError("Failed to rename the logical volume: %s", result.output)
572 self._lv_name = new_name
573 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
574
576 """Attach to an existing LV.
577
578 This method will try to see if an existing and active LV exists
579 which matches our name. If so, its major/minor will be
580 recorded.
581
582 """
583 self.attached = False
584 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
585 "--units=m", "--nosuffix",
586 "-olv_attr,lv_kernel_major,lv_kernel_minor,"
587 "vg_extent_size,stripes", self.dev_path])
588 if result.failed:
589 logging.error("Can't find LV %s: %s, %s",
590 self.dev_path, result.fail_reason, result.output)
591 return False
592
593
594
595
596
597 out = result.stdout.splitlines()
598 if not out:
599
600 logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
601 return False
602 out = out[-1].strip().rstrip(",")
603 out = out.split(",")
604 if len(out) != 5:
605 logging.error("Can't parse LVS output, len(%s) != 5", str(out))
606 return False
607
608 status, major, minor, pe_size, stripes = out
609 if len(status) < 6:
610 logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
611 return False
612
613 try:
614 major = int(major)
615 minor = int(minor)
616 except (TypeError, ValueError), err:
617 logging.error("lvs major/minor cannot be parsed: %s", str(err))
618
619 try:
620 pe_size = int(float(pe_size))
621 except (TypeError, ValueError), err:
622 logging.error("Can't parse vg extent size: %s", err)
623 return False
624
625 try:
626 stripes = int(stripes)
627 except (TypeError, ValueError), err:
628 logging.error("Can't parse the number of stripes: %s", err)
629 return False
630
631 self.major = major
632 self.minor = minor
633 self.pe_size = pe_size
634 self.stripe_count = stripes
635 self._degraded = status[0] == "v"
636
637 self.attached = True
638 return True
639
641 """Assemble the device.
642
643 We always run `lvchange -ay` on the LV to ensure it's active before
644 use, as there were cases when xenvg was not active after boot
645 (also possibly after disk issues).
646
647 """
648 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
649 if result.failed:
650 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
651
653 """Shutdown the device.
654
655 This is a no-op for the LV device type, as we don't deactivate the
656 volumes on shutdown.
657
658 """
659 pass
660
662 """Returns the sync status of the device.
663
664 If this device is a mirroring device, this function returns the
665 status of the mirror.
666
667 For logical volumes, sync_percent and estimated_time are always
668 None (no recovery in progress, as we don't handle the mirrored LV
669 case). The is_degraded parameter is the inverse of the ldisk
670 parameter.
671
672 For the ldisk parameter, we check if the logical volume has the
673 'virtual' type, which means it's not backed by existing storage
674 anymore (read from it return I/O error). This happens after a
675 physical disk failure and subsequent 'vgreduce --removemissing' on
676 the volume group.
677
678 The status was already read in Attach, so we just return it.
679
680 @rtype: objects.BlockDevStatus
681
682 """
683 if self._degraded:
684 ldisk_status = constants.LDS_FAULTY
685 else:
686 ldisk_status = constants.LDS_OKAY
687
688 return objects.BlockDevStatus(dev_path=self.dev_path,
689 major=self.major,
690 minor=self.minor,
691 sync_percent=None,
692 estimated_time=None,
693 is_degraded=self._degraded,
694 ldisk_status=ldisk_status)
695
696 - def Open(self, force=False):
697 """Make the device ready for I/O.
698
699 This is a no-op for the LV device type.
700
701 """
702 pass
703
705 """Notifies that the device will no longer be used for I/O.
706
707 This is a no-op for the LV device type.
708
709 """
710 pass
711
713 """Create a snapshot copy of an lvm block device.
714
715 @returns: tuple (vg, lv)
716
717 """
718 snap_name = self._lv_name + ".snap"
719
720
721 snap = LogicalVolume((self._vg_name, snap_name), None, size)
722 _IgnoreError(snap.Remove)
723
724 vg_info = self.GetVGInfo([self._vg_name])
725 if not vg_info:
726 _ThrowError("Can't compute VG info for vg %s", self._vg_name)
727 free_size, _, _ = vg_info[0]
728 if free_size < size:
729 _ThrowError("Not enough free space: required %s,"
730 " available %s", size, free_size)
731
732 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
733 "-n%s" % snap_name, self.dev_path])
734 if result.failed:
735 _ThrowError("command: %s error: %s - %s",
736 result.cmd, result.fail_reason, result.output)
737
738 return (self._vg_name, snap_name)
739
741 """Update metadata with info text.
742
743 """
744 BlockDev.SetInfo(self, text)
745
746
747 text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
748 text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
749
750
751 text = text[:128]
752
753 result = utils.RunCmd(["lvchange", "--addtag", text,
754 self.dev_path])
755 if result.failed:
756 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
757 result.output)
758
759 - def Grow(self, amount, dryrun):
760 """Grow the logical volume.
761
762 """
763 if self.pe_size is None or self.stripe_count is None:
764 if not self.Attach():
765 _ThrowError("Can't attach to LV during Grow()")
766 full_stripe_size = self.pe_size * self.stripe_count
767 rest = amount % full_stripe_size
768 if rest != 0:
769 amount += full_stripe_size - rest
770 cmd = ["lvextend", "-L", "+%dm" % amount]
771 if dryrun:
772 cmd.append("--test")
773
774
775
776
777 for alloc_policy in "contiguous", "cling", "normal":
778 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
779 if not result.failed:
780 return
781 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
782
785 """A DRBD status representation class.
786
787 Note that this doesn't support unconfigured devices (cs:Unconfigured).
788
789 """
790 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
791 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
792 "\s+ds:([^/]+)/(\S+)\s+.*$")
793 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
794
795
796 "(?:\s|M)"
797 "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
798
799 CS_UNCONFIGURED = "Unconfigured"
800 CS_STANDALONE = "StandAlone"
801 CS_WFCONNECTION = "WFConnection"
802 CS_WFREPORTPARAMS = "WFReportParams"
803 CS_CONNECTED = "Connected"
804 CS_STARTINGSYNCS = "StartingSyncS"
805 CS_STARTINGSYNCT = "StartingSyncT"
806 CS_WFBITMAPS = "WFBitMapS"
807 CS_WFBITMAPT = "WFBitMapT"
808 CS_WFSYNCUUID = "WFSyncUUID"
809 CS_SYNCSOURCE = "SyncSource"
810 CS_SYNCTARGET = "SyncTarget"
811 CS_PAUSEDSYNCS = "PausedSyncS"
812 CS_PAUSEDSYNCT = "PausedSyncT"
813 CSET_SYNC = frozenset([
814 CS_WFREPORTPARAMS,
815 CS_STARTINGSYNCS,
816 CS_STARTINGSYNCT,
817 CS_WFBITMAPS,
818 CS_WFBITMAPT,
819 CS_WFSYNCUUID,
820 CS_SYNCSOURCE,
821 CS_SYNCTARGET,
822 CS_PAUSEDSYNCS,
823 CS_PAUSEDSYNCT,
824 ])
825
826 DS_DISKLESS = "Diskless"
827 DS_ATTACHING = "Attaching"
828 DS_FAILED = "Failed"
829 DS_NEGOTIATING = "Negotiating"
830 DS_INCONSISTENT = "Inconsistent"
831 DS_OUTDATED = "Outdated"
832 DS_DUNKNOWN = "DUnknown"
833 DS_CONSISTENT = "Consistent"
834 DS_UPTODATE = "UpToDate"
835
836 RO_PRIMARY = "Primary"
837 RO_SECONDARY = "Secondary"
838 RO_UNKNOWN = "Unknown"
839
841 u = self.UNCONF_RE.match(procline)
842 if u:
843 self.cstatus = self.CS_UNCONFIGURED
844 self.lrole = self.rrole = self.ldisk = self.rdisk = None
845 else:
846 m = self.LINE_RE.match(procline)
847 if not m:
848 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
849 self.cstatus = m.group(1)
850 self.lrole = m.group(2)
851 self.rrole = m.group(3)
852 self.ldisk = m.group(4)
853 self.rdisk = m.group(5)
854
855
856
857 self.is_standalone = self.cstatus == self.CS_STANDALONE
858 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
859 self.is_connected = self.cstatus == self.CS_CONNECTED
860 self.is_primary = self.lrole == self.RO_PRIMARY
861 self.is_secondary = self.lrole == self.RO_SECONDARY
862 self.peer_primary = self.rrole == self.RO_PRIMARY
863 self.peer_secondary = self.rrole == self.RO_SECONDARY
864 self.both_primary = self.is_primary and self.peer_primary
865 self.both_secondary = self.is_secondary and self.peer_secondary
866
867 self.is_diskless = self.ldisk == self.DS_DISKLESS
868 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
869
870 self.is_in_resync = self.cstatus in self.CSET_SYNC
871 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
872
873 m = self.SYNC_RE.match(procline)
874 if m:
875 self.sync_percent = float(m.group(1))
876 hours = int(m.group(2))
877 minutes = int(m.group(3))
878 seconds = int(m.group(4))
879 self.est_time = hours * 3600 + minutes * 60 + seconds
880 else:
881
882
883
884
885 if self.is_in_resync:
886 self.sync_percent = 0
887 else:
888 self.sync_percent = None
889 self.est_time = None
890
893 """Base DRBD class.
894
895 This class contains a few bits of common functionality between the
896 0.7 and 8.x versions of DRBD.
897
898 """
899 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
900 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
901 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
902 _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
903
904 _DRBD_MAJOR = 147
905 _ST_UNCONFIGURED = "Unconfigured"
906 _ST_WFCONNECTION = "WFConnection"
907 _ST_CONNECTED = "Connected"
908
909 _STATUS_FILE = "/proc/drbd"
910 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
911
912 @staticmethod
914 """Return data from /proc/drbd.
915
916 """
917 try:
918 data = utils.ReadFile(filename).splitlines()
919 except EnvironmentError, err:
920 if err.errno == errno.ENOENT:
921 _ThrowError("The file %s cannot be opened, check if the module"
922 " is loaded (%s)", filename, str(err))
923 else:
924 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
925 if not data:
926 _ThrowError("Can't read any data from %s", filename)
927 return data
928
929 @classmethod
931 """Transform the output of _GetProdData into a nicer form.
932
933 @return: a dictionary of minor: joined lines from /proc/drbd
934 for that minor
935
936 """
937 results = {}
938 old_minor = old_line = None
939 for line in data:
940 if not line:
941 continue
942 lresult = cls._VALID_LINE_RE.match(line)
943 if lresult is not None:
944 if old_minor is not None:
945 results[old_minor] = old_line
946 old_minor = int(lresult.group(1))
947 old_line = line
948 else:
949 if old_minor is not None:
950 old_line += " " + line.strip()
951
952 if old_minor is not None:
953 results[old_minor] = old_line
954 return results
955
956 @classmethod
958 """Return the DRBD version.
959
960 This will return a dict with keys:
961 - k_major
962 - k_minor
963 - k_point
964 - api
965 - proto
966 - proto2 (only on drbd > 8.2.X)
967
968 """
969 first_line = proc_data[0].strip()
970 version = cls._VERSION_RE.match(first_line)
971 if not version:
972 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
973 first_line)
974
975 values = version.groups()
976 retval = {"k_major": int(values[0]),
977 "k_minor": int(values[1]),
978 "k_point": int(values[2]),
979 "api": int(values[3]),
980 "proto": int(values[4]),
981 }
982 if values[5] is not None:
983 retval["proto2"] = values[5]
984
985 return retval
986
987 @staticmethod
989 """Returns DRBD usermode_helper currently set.
990
991 """
992 try:
993 helper = utils.ReadFile(filename).splitlines()[0]
994 except EnvironmentError, err:
995 if err.errno == errno.ENOENT:
996 _ThrowError("The file %s cannot be opened, check if the module"
997 " is loaded (%s)", filename, str(err))
998 else:
999 _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1000 if not helper:
1001 _ThrowError("Can't read any data from %s", filename)
1002 return helper
1003
1004 @staticmethod
1006 """Return the path to a drbd device for a given minor.
1007
1008 """
1009 return "/dev/drbd%d" % minor
1010
1011 @classmethod
1013 """Compute the list of used DRBD devices.
1014
1015 """
1016 data = cls._GetProcData()
1017
1018 used_devs = {}
1019 for line in data:
1020 match = cls._VALID_LINE_RE.match(line)
1021 if not match:
1022 continue
1023 minor = int(match.group(1))
1024 state = match.group(2)
1025 if state == cls._ST_UNCONFIGURED:
1026 continue
1027 used_devs[minor] = state, line
1028
1029 return used_devs
1030
1032 """Set our parameters based on the given minor.
1033
1034 This sets our minor variable and our dev_path.
1035
1036 """
1037 if minor is None:
1038 self.minor = self.dev_path = None
1039 self.attached = False
1040 else:
1041 self.minor = minor
1042 self.dev_path = self._DevPath(minor)
1043 self.attached = True
1044
1045 @staticmethod
1072
1074 """Rename a device.
1075
1076 This is not supported for drbd devices.
1077
1078 """
1079 raise errors.ProgrammerError("Can't rename a drbd device")
1080
1081
1082 -class DRBD8(BaseDRBD):
1083 """DRBD v8.x block device.
1084
1085 This implements the local host part of the DRBD device, i.e. it
1086 doesn't do anything to the supposed peer. If you need a fully
1087 connected DRBD pair, you need to use this class on both hosts.
1088
1089 The unique_id for the drbd device is the (local_ip, local_port,
1090 remote_ip, remote_port) tuple, and it must have two children: the
1091 data device and the meta_device. The meta device is checked for
1092 valid size and is zeroed on create.
1093
1094 """
1095 _MAX_MINORS = 255
1096 _PARSE_SHOW = None
1097
1098
1099 _NET_RECONFIG_TIMEOUT = 60
1100
1101 - def __init__(self, unique_id, children, size):
1102 if children and children.count(None) > 0:
1103 children = []
1104 if len(children) not in (0, 2):
1105 raise ValueError("Invalid configuration data %s" % str(children))
1106 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1107 raise ValueError("Invalid configuration data %s" % str(unique_id))
1108 (self._lhost, self._lport,
1109 self._rhost, self._rport,
1110 self._aminor, self._secret) = unique_id
1111 if children:
1112 if not _CanReadDevice(children[1].dev_path):
1113 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1114 children = []
1115 super(DRBD8, self).__init__(unique_id, children, size)
1116 self.major = self._DRBD_MAJOR
1117 version = self._GetVersion(self._GetProcData())
1118 if version["k_major"] != 8:
1119 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1120 " usage: kernel is %s.%s, ganeti wants 8.x",
1121 version["k_major"], version["k_minor"])
1122
1123 if (self._lhost is not None and self._lhost == self._rhost and
1124 self._lport == self._rport):
1125 raise ValueError("Invalid configuration data, same local/remote %s" %
1126 (unique_id,))
1127 self.Attach()
1128
1129 @classmethod
1151
1152 @classmethod
1154 """Find an unused DRBD device.
1155
1156 This is specific to 8.x as the minors are allocated dynamically,
1157 so non-existing numbers up to a max minor count are actually free.
1158
1159 """
1160 data = cls._GetProcData()
1161
1162 highest = None
1163 for line in data:
1164 match = cls._UNUSED_LINE_RE.match(line)
1165 if match:
1166 return int(match.group(1))
1167 match = cls._VALID_LINE_RE.match(line)
1168 if match:
1169 minor = int(match.group(1))
1170 highest = max(highest, minor)
1171 if highest is None:
1172 return 0
1173 if highest >= cls._MAX_MINORS:
1174 logging.error("Error: no free drbd minors!")
1175 raise errors.BlockDeviceError("Can't find a free DRBD minor")
1176 return highest + 1
1177
1178 @classmethod
1180 """Return a parser for `drbd show` output.
1181
1182 This will either create or return an already-create parser for the
1183 output of the command `drbd show`.
1184
1185 """
1186 if cls._PARSE_SHOW is not None:
1187 return cls._PARSE_SHOW
1188
1189
1190 lbrace = pyp.Literal("{").suppress()
1191 rbrace = pyp.Literal("}").suppress()
1192 lbracket = pyp.Literal("[").suppress()
1193 rbracket = pyp.Literal("]").suppress()
1194 semi = pyp.Literal(";").suppress()
1195 colon = pyp.Literal(":").suppress()
1196
1197 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1198
1199 comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1200 defa = pyp.Literal("_is_default").suppress()
1201 dbl_quote = pyp.Literal('"').suppress()
1202
1203 keyword = pyp.Word(pyp.alphanums + '-')
1204
1205
1206 value = pyp.Word(pyp.alphanums + '_-/.:')
1207 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1208 ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1209 pyp.Word(pyp.nums + ".") + colon + number)
1210 ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1211 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1212 pyp.Optional(rbracket) + colon + number)
1213
1214 meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1215
1216 device_value = pyp.Literal("minor").suppress() + number
1217
1218
1219 stmt = (~rbrace + keyword + ~lbrace +
1220 pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1221 device_value) +
1222 pyp.Optional(defa) + semi +
1223 pyp.Optional(pyp.restOfLine).suppress())
1224
1225
1226 section_name = pyp.Word(pyp.alphas + "_")
1227 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1228
1229 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1230 bnf.ignore(comment)
1231
1232 cls._PARSE_SHOW = bnf
1233
1234 return bnf
1235
1236 @classmethod
1238 """Return the `drbdsetup show` data for a minor.
1239
1240 """
1241 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1242 if result.failed:
1243 logging.error("Can't display the drbd config: %s - %s",
1244 result.fail_reason, result.output)
1245 return None
1246 return result.stdout
1247
1248 @classmethod
1250 """Parse details about a given DRBD minor.
1251
1252 This return, if available, the local backing device (as a path)
1253 and the local and remote (ip, port) information from a string
1254 containing the output of the `drbdsetup show` command as returned
1255 by _GetShowData.
1256
1257 """
1258 data = {}
1259 if not out:
1260 return data
1261
1262 bnf = cls._GetShowParser()
1263
1264
1265 try:
1266 results = bnf.parseString(out)
1267 except pyp.ParseException, err:
1268 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1269
1270
1271 for section in results:
1272 sname = section[0]
1273 if sname == "_this_host":
1274 for lst in section[1:]:
1275 if lst[0] == "disk":
1276 data["local_dev"] = lst[1]
1277 elif lst[0] == "meta-disk":
1278 data["meta_dev"] = lst[1]
1279 data["meta_index"] = lst[2]
1280 elif lst[0] == "address":
1281 data["local_addr"] = tuple(lst[1:])
1282 elif sname == "_remote_host":
1283 for lst in section[1:]:
1284 if lst[0] == "address":
1285 data["remote_addr"] = tuple(lst[1:])
1286 return data
1287
1289 """Test if our local config matches with an existing device.
1290
1291 The parameter should be as returned from `_GetDevInfo()`. This
1292 method tests if our local backing device is the same as the one in
1293 the info parameter, in effect testing if we look like the given
1294 device.
1295
1296 """
1297 if self._children:
1298 backend, meta = self._children
1299 else:
1300 backend = meta = None
1301
1302 if backend is not None:
1303 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1304 else:
1305 retval = ("local_dev" not in info)
1306
1307 if meta is not None:
1308 retval = retval and ("meta_dev" in info and
1309 info["meta_dev"] == meta.dev_path)
1310 retval = retval and ("meta_index" in info and
1311 info["meta_index"] == 0)
1312 else:
1313 retval = retval and ("meta_dev" not in info and
1314 "meta_index" not in info)
1315 return retval
1316
1318 """Test if our network config matches with an existing device.
1319
1320 The parameter should be as returned from `_GetDevInfo()`. This
1321 method tests if our network configuration is the same as the one
1322 in the info parameter, in effect testing if we look like the given
1323 device.
1324
1325 """
1326 if (((self._lhost is None and not ("local_addr" in info)) and
1327 (self._rhost is None and not ("remote_addr" in info)))):
1328 return True
1329
1330 if self._lhost is None:
1331 return False
1332
1333 if not ("local_addr" in info and
1334 "remote_addr" in info):
1335 return False
1336
1337 retval = (info["local_addr"] == (self._lhost, self._lport))
1338 retval = (retval and
1339 info["remote_addr"] == (self._rhost, self._rport))
1340 return retval
1341
1342 @classmethod
1344 """Configure the local part of a DRBD device.
1345
1346 """
1347 args = ["drbdsetup", cls._DevPath(minor), "disk",
1348 backend, meta, "0",
1349 "-e", "detach",
1350 "--create-device"]
1351 if size:
1352 args.extend(["-d", "%sm" % size])
1353 if not constants.DRBD_BARRIERS:
1354 version = cls._GetVersion(cls._GetProcData())
1355
1356
1357
1358
1359 vmaj = version["k_major"]
1360 vmin = version["k_minor"]
1361 vrel = version["k_point"]
1362 assert vmaj == 8
1363 if vmin == 0:
1364 if vrel >= 12:
1365 args.extend(["-i", "-m"])
1366 elif vmin == 2:
1367 if vrel >= 7:
1368 args.extend(["-i", "-m"])
1369 elif vmaj >= 3:
1370 args.extend(["-i", "-a", "m"])
1371 result = utils.RunCmd(args)
1372 if result.failed:
1373 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1374
1375 @classmethod
1376 - def _AssembleNet(cls, minor, net_info, protocol,
1377 dual_pri=False, hmac=None, secret=None):
1378 """Configure the network part of the device.
1379
1380 """
1381 lhost, lport, rhost, rport = net_info
1382 if None in net_info:
1383
1384
1385 cls._ShutdownNet(minor)
1386 return
1387
1388
1389
1390
1391
1392
1393
1394 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1395
1396 if netutils.IP6Address.IsValid(lhost):
1397 if not netutils.IP6Address.IsValid(rhost):
1398 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1399 (minor, lhost, rhost))
1400 family = "ipv6"
1401 elif netutils.IP4Address.IsValid(lhost):
1402 if not netutils.IP4Address.IsValid(rhost):
1403 _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1404 (minor, lhost, rhost))
1405 family = "ipv4"
1406 else:
1407 _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1408
1409 args = ["drbdsetup", cls._DevPath(minor), "net",
1410 "%s:%s:%s" % (family, lhost, lport),
1411 "%s:%s:%s" % (family, rhost, rport), protocol,
1412 "-A", "discard-zero-changes",
1413 "-B", "consensus",
1414 "--create-device",
1415 ]
1416 if dual_pri:
1417 args.append("-m")
1418 if hmac and secret:
1419 args.extend(["-a", hmac, "-x", secret])
1420 result = utils.RunCmd(args)
1421 if result.failed:
1422 _ThrowError("drbd%d: can't setup network: %s - %s",
1423 minor, result.fail_reason, result.output)
1424
1425 def _CheckNetworkConfig():
1426 info = cls._GetDevInfo(cls._GetShowData(minor))
1427 if not "local_addr" in info or not "remote_addr" in info:
1428 raise utils.RetryAgain()
1429
1430 if (info["local_addr"] != (lhost, lport) or
1431 info["remote_addr"] != (rhost, rport)):
1432 raise utils.RetryAgain()
1433
1434 try:
1435 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1436 except utils.RetryTimeout:
1437 _ThrowError("drbd%d: timeout while configuring network", minor)
1438
1440 """Add a disk to the DRBD device.
1441
1442 """
1443 if self.minor is None:
1444 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1445 self._aminor)
1446 if len(devices) != 2:
1447 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1448 info = self._GetDevInfo(self._GetShowData(self.minor))
1449 if "local_dev" in info:
1450 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1451 backend, meta = devices
1452 if backend.dev_path is None or meta.dev_path is None:
1453 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1454 backend.Open()
1455 meta.Open()
1456 self._CheckMetaSize(meta.dev_path)
1457 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1458
1459 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1460 self._children = devices
1461
1463 """Detach the drbd device from local storage.
1464
1465 """
1466 if self.minor is None:
1467 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1468 self._aminor)
1469
1470 info = self._GetDevInfo(self._GetShowData(self.minor))
1471 if "local_dev" not in info:
1472 return
1473 if len(self._children) != 2:
1474 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1475 self._children)
1476 if self._children.count(None) == 2:
1477 logging.warning("drbd%d: requested detach while detached", self.minor)
1478 return
1479 if len(devices) != 2:
1480 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1481 for child, dev in zip(self._children, devices):
1482 if dev != child.dev_path:
1483 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1484 " RemoveChildren", self.minor, dev, child.dev_path)
1485
1486 self._ShutdownLocal(self.minor)
1487 self._children = []
1488
1489 @classmethod
1491 """Set the speed of the DRBD syncer.
1492
1493 This is the low-level implementation.
1494
1495 @type minor: int
1496 @param minor: the drbd minor whose settings we change
1497 @type kbytes: int
1498 @param kbytes: the speed in kbytes/second
1499 @rtype: boolean
1500 @return: the success of the operation
1501
1502 """
1503 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1504 "-r", "%d" % kbytes, "--create-device"])
1505 if result.failed:
1506 logging.error("Can't change syncer rate: %s - %s",
1507 result.fail_reason, result.output)
1508 return not result.failed
1509
1511 """Set the speed of the DRBD syncer.
1512
1513 @type kbytes: int
1514 @param kbytes: the speed in kbytes/second
1515 @rtype: boolean
1516 @return: the success of the operation
1517
1518 """
1519 if self.minor is None:
1520 logging.info("Not attached during SetSyncSpeed")
1521 return False
1522 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1523 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1524
1526 """Pauses or resumes the sync of a DRBD device.
1527
1528 @param pause: Wether to pause or resume
1529 @return: the success of the operation
1530
1531 """
1532 if self.minor is None:
1533 logging.info("Not attached during PauseSync")
1534 return False
1535
1536 children_result = super(DRBD8, self).PauseResumeSync(pause)
1537
1538 if pause:
1539 cmd = "pause-sync"
1540 else:
1541 cmd = "resume-sync"
1542
1543 result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1544 if result.failed:
1545 logging.error("Can't %s: %s - %s", cmd,
1546 result.fail_reason, result.output)
1547 return not result.failed and children_result
1548
1550 """Return device data from /proc.
1551
1552 """
1553 if self.minor is None:
1554 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1555 proc_info = self._MassageProcData(self._GetProcData())
1556 if self.minor not in proc_info:
1557 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1558 return DRBD8Status(proc_info[self.minor])
1559
1561 """Returns the sync status of the device.
1562
1563
1564 If sync_percent is None, it means all is ok
1565 If estimated_time is None, it means we can't estimate
1566 the time needed, otherwise it's the time left in seconds.
1567
1568
1569 We set the is_degraded parameter to True on two conditions:
1570 network not connected or local disk missing.
1571
1572 We compute the ldisk parameter based on whether we have a local
1573 disk or not.
1574
1575 @rtype: objects.BlockDevStatus
1576
1577 """
1578 if self.minor is None and not self.Attach():
1579 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1580
1581 stats = self.GetProcStatus()
1582 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1583
1584 if stats.is_disk_uptodate:
1585 ldisk_status = constants.LDS_OKAY
1586 elif stats.is_diskless:
1587 ldisk_status = constants.LDS_FAULTY
1588 else:
1589 ldisk_status = constants.LDS_UNKNOWN
1590
1591 return objects.BlockDevStatus(dev_path=self.dev_path,
1592 major=self.major,
1593 minor=self.minor,
1594 sync_percent=stats.sync_percent,
1595 estimated_time=stats.est_time,
1596 is_degraded=is_degraded,
1597 ldisk_status=ldisk_status)
1598
1599 - def Open(self, force=False):
1600 """Make the local state primary.
1601
1602 If the 'force' parameter is given, the '-o' option is passed to
1603 drbdsetup. Since this is a potentially dangerous operation, the
1604 force flag should be only given after creation, when it actually
1605 is mandatory.
1606
1607 """
1608 if self.minor is None and not self.Attach():
1609 logging.error("DRBD cannot attach to a device during open")
1610 return False
1611 cmd = ["drbdsetup", self.dev_path, "primary"]
1612 if force:
1613 cmd.append("-o")
1614 result = utils.RunCmd(cmd)
1615 if result.failed:
1616 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1617 result.output)
1618
1620 """Make the local state secondary.
1621
1622 This will, of course, fail if the device is in use.
1623
1624 """
1625 if self.minor is None and not self.Attach():
1626 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1627 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1628 if result.failed:
1629 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1630 self.minor, result.output)
1631
1633 """Removes network configuration.
1634
1635 This method shutdowns the network side of the device.
1636
1637 The method will wait up to a hardcoded timeout for the device to
1638 go into standalone after the 'disconnect' command before
1639 re-configuring it, as sometimes it takes a while for the
1640 disconnect to actually propagate and thus we might issue a 'net'
1641 command while the device is still connected. If the device will
1642 still be attached to the network and we time out, we raise an
1643 exception.
1644
1645 """
1646 if self.minor is None:
1647 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1648
1649 if None in (self._lhost, self._lport, self._rhost, self._rport):
1650 _ThrowError("drbd%d: DRBD disk missing network info in"
1651 " DisconnectNet()", self.minor)
1652
1653 class _DisconnectStatus:
1654 def __init__(self, ever_disconnected):
1655 self.ever_disconnected = ever_disconnected
1656
1657 dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1658
1659 def _WaitForDisconnect():
1660 if self.GetProcStatus().is_standalone:
1661 return
1662
1663
1664
1665
1666 dstatus.ever_disconnected = \
1667 _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1668
1669 raise utils.RetryAgain()
1670
1671
1672 start_time = time.time()
1673
1674 try:
1675
1676 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1677 self._NET_RECONFIG_TIMEOUT)
1678 except utils.RetryTimeout:
1679 if dstatus.ever_disconnected:
1680 msg = ("drbd%d: device did not react to the"
1681 " 'disconnect' command in a timely manner")
1682 else:
1683 msg = "drbd%d: can't shutdown network, even after multiple retries"
1684
1685 _ThrowError(msg, self.minor)
1686
1687 reconfig_time = time.time() - start_time
1688 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1689 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1690 self.minor, reconfig_time)
1691
1693 """Reconnects the network.
1694
1695 This method connects the network side of the device with a
1696 specified multi-master flag. The device needs to be 'Standalone'
1697 but have valid network configuration data.
1698
1699 Args:
1700 - multimaster: init the network in dual-primary mode
1701
1702 """
1703 if self.minor is None:
1704 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1705
1706 if None in (self._lhost, self._lport, self._rhost, self._rport):
1707 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1708
1709 status = self.GetProcStatus()
1710
1711 if not status.is_standalone:
1712 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1713
1714 self._AssembleNet(self.minor,
1715 (self._lhost, self._lport, self._rhost, self._rport),
1716 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1717 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1718
1720 """Check if our minor is configured.
1721
1722 This doesn't do any device configurations - it only checks if the
1723 minor is in a state different from Unconfigured.
1724
1725 Note that this function will not change the state of the system in
1726 any way (except in case of side-effects caused by reading from
1727 /proc).
1728
1729 """
1730 used_devs = self.GetUsedDevs()
1731 if self._aminor in used_devs:
1732 minor = self._aminor
1733 else:
1734 minor = None
1735
1736 self._SetFromMinor(minor)
1737 return minor is not None
1738
1740 """Assemble the drbd.
1741
1742 Method:
1743 - if we have a configured device, we try to ensure that it matches
1744 our config
1745 - if not, we create it from zero
1746
1747 """
1748 super(DRBD8, self).Assemble()
1749
1750 self.Attach()
1751 if self.minor is None:
1752
1753 self._FastAssemble()
1754 else:
1755
1756
1757 self._SlowAssemble()
1758
1760 """Assembles the DRBD device from a (partially) configured device.
1761
1762 In case of partially attached (local device matches but no network
1763 setup), we perform the network attach. If successful, we re-test
1764 the attach if can return success.
1765
1766 """
1767
1768
1769 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1770 for minor in (self._aminor,):
1771 info = self._GetDevInfo(self._GetShowData(minor))
1772 match_l = self._MatchesLocal(info)
1773 match_r = self._MatchesNet(info)
1774
1775 if match_l and match_r:
1776
1777 break
1778
1779 if match_l and not match_r and "local_addr" not in info:
1780
1781 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1782 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1783 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1784 break
1785 else:
1786 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1787 " show' disagrees", minor)
1788
1789 if match_r and "local_dev" not in info:
1790
1791 self._AssembleLocal(minor, self._children[0].dev_path,
1792 self._children[1].dev_path, self.size)
1793 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1794 break
1795 else:
1796 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1797 " show' disagrees", minor)
1798
1799
1800
1801
1802
1803 if (match_l and "local_dev" in info and
1804 not match_r and "local_addr" in info):
1805
1806
1807
1808
1809 try:
1810 self._ShutdownNet(minor)
1811 except errors.BlockDeviceError, err:
1812 _ThrowError("drbd%d: device has correct local storage, wrong"
1813 " remote peer and is unable to disconnect in order"
1814 " to attach to the correct peer: %s", minor, str(err))
1815
1816
1817
1818 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1819 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1820 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1821 break
1822 else:
1823 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1824 " show' disagrees", minor)
1825
1826 else:
1827 minor = None
1828
1829 self._SetFromMinor(minor)
1830 if minor is None:
1831 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1832 self._aminor)
1833
1835 """Assemble the drbd device from zero.
1836
1837 This is run when in Assemble we detect our minor is unused.
1838
1839 """
1840 minor = self._aminor
1841 if self._children and self._children[0] and self._children[1]:
1842 self._AssembleLocal(minor, self._children[0].dev_path,
1843 self._children[1].dev_path, self.size)
1844 if self._lhost and self._lport and self._rhost and self._rport:
1845 self._AssembleNet(minor,
1846 (self._lhost, self._lport, self._rhost, self._rport),
1847 constants.DRBD_NET_PROTOCOL,
1848 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1849 self._SetFromMinor(minor)
1850
1851 @classmethod
1853 """Detach from the local device.
1854
1855 I/Os will continue to be served from the remote device. If we
1856 don't have a remote device, this operation will fail.
1857
1858 """
1859 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1860 if result.failed:
1861 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1862
1863 @classmethod
1865 """Disconnect from the remote peer.
1866
1867 This fails if we don't have a local device.
1868
1869 """
1870 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1871 if result.failed:
1872 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1873
1874 @classmethod
1876 """Deactivate the device.
1877
1878 This will, of course, fail if the device is in use.
1879
1880 """
1881 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1882 if result.failed:
1883 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1884 minor, result.output)
1885
1887 """Shutdown the DRBD device.
1888
1889 """
1890 if self.minor is None and not self.Attach():
1891 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1892 return
1893 minor = self.minor
1894 self.minor = None
1895 self.dev_path = None
1896 self._ShutdownAll(minor)
1897
1899 """Stub remove for DRBD devices.
1900
1901 """
1902 self.Shutdown()
1903
1904 @classmethod
1905 - def Create(cls, unique_id, children, size):
1906 """Create a new DRBD8 device.
1907
1908 Since DRBD devices are not created per se, just assembled, this
1909 function only initializes the metadata.
1910
1911 """
1912 if len(children) != 2:
1913 raise errors.ProgrammerError("Invalid setup for the drbd device")
1914
1915 aminor = unique_id[4]
1916 proc_info = cls._MassageProcData(cls._GetProcData())
1917 if aminor in proc_info:
1918 status = DRBD8Status(proc_info[aminor])
1919 in_use = status.is_in_use
1920 else:
1921 in_use = False
1922 if in_use:
1923 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1924 meta = children[1]
1925 meta.Assemble()
1926 if not meta.Attach():
1927 _ThrowError("drbd%d: can't attach to meta device '%s'",
1928 aminor, meta)
1929 cls._CheckMetaSize(meta.dev_path)
1930 cls._InitMeta(aminor, meta.dev_path)
1931 return cls(unique_id, children, size)
1932
1933 - def Grow(self, amount, dryrun):
1934 """Resize the DRBD device and its backing storage.
1935
1936 """
1937 if self.minor is None:
1938 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1939 if len(self._children) != 2 or None in self._children:
1940 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1941 self._children[0].Grow(amount, dryrun)
1942 if dryrun:
1943
1944 return
1945 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1946 "%dm" % (self.size + amount)])
1947 if result.failed:
1948 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1949
1952 """File device.
1953
1954 This class represents the a file storage backend device.
1955
1956 The unique_id for the file device is a (file_driver, file_path) tuple.
1957
1958 """
1959 - def __init__(self, unique_id, children, size):
1960 """Initalizes a file device backend.
1961
1962 """
1963 if children:
1964 raise errors.BlockDeviceError("Invalid setup for file device")
1965 super(FileStorage, self).__init__(unique_id, children, size)
1966 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1967 raise ValueError("Invalid configuration data %s" % str(unique_id))
1968 self.driver = unique_id[0]
1969 self.dev_path = unique_id[1]
1970 self.Attach()
1971
1973 """Assemble the device.
1974
1975 Checks whether the file device exists, raises BlockDeviceError otherwise.
1976
1977 """
1978 if not os.path.exists(self.dev_path):
1979 _ThrowError("File device '%s' does not exist" % self.dev_path)
1980
1982 """Shutdown the device.
1983
1984 This is a no-op for the file type, as we don't deactivate
1985 the file on shutdown.
1986
1987 """
1988 pass
1989
1990 - def Open(self, force=False):
1991 """Make the device ready for I/O.
1992
1993 This is a no-op for the file type.
1994
1995 """
1996 pass
1997
1999 """Notifies that the device will no longer be used for I/O.
2000
2001 This is a no-op for the file type.
2002
2003 """
2004 pass
2005
2007 """Remove the file backing the block device.
2008
2009 @rtype: boolean
2010 @return: True if the removal was successful
2011
2012 """
2013 try:
2014 os.remove(self.dev_path)
2015 except OSError, err:
2016 if err.errno != errno.ENOENT:
2017 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2018
2020 """Renames the file.
2021
2022 """
2023
2024 _ThrowError("Rename is not supported for file-based storage")
2025
2026 - def Grow(self, amount, dryrun):
2027 """Grow the file
2028
2029 @param amount: the amount (in mebibytes) to grow with
2030
2031 """
2032
2033 self.Assemble()
2034 current_size = self.GetActualSize()
2035 new_size = current_size + amount * 1024 * 1024
2036 assert new_size > current_size, "Cannot Grow with a negative amount"
2037
2038 if dryrun:
2039 return
2040 try:
2041 f = open(self.dev_path, "a+")
2042 f.truncate(new_size)
2043 f.close()
2044 except EnvironmentError, err:
2045 _ThrowError("Error in file growth: %", str(err))
2046
2048 """Attach to an existing file.
2049
2050 Check if this file already exists.
2051
2052 @rtype: boolean
2053 @return: True if file exists
2054
2055 """
2056 self.attached = os.path.exists(self.dev_path)
2057 return self.attached
2058
2060 """Return the actual disk size.
2061
2062 @note: the device needs to be active when this is called
2063
2064 """
2065 assert self.attached, "BlockDevice not attached in GetActualSize()"
2066 try:
2067 st = os.stat(self.dev_path)
2068 return st.st_size
2069 except OSError, err:
2070 _ThrowError("Can't stat %s: %s", self.dev_path, err)
2071
2072 @classmethod
2073 - def Create(cls, unique_id, children, size):
2074 """Create a new file.
2075
2076 @param size: the size of file in MiB
2077
2078 @rtype: L{bdev.FileStorage}
2079 @return: an instance of FileStorage
2080
2081 """
2082 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2083 raise ValueError("Invalid configuration data %s" % str(unique_id))
2084 dev_path = unique_id[1]
2085 try:
2086 fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2087 f = os.fdopen(fd, "w")
2088 f.truncate(size * 1024 * 1024)
2089 f.close()
2090 except EnvironmentError, err:
2091 if err.errno == errno.EEXIST:
2092 _ThrowError("File already existing: %s", dev_path)
2093 _ThrowError("Error in file creation: %", str(err))
2094
2095 return FileStorage(unique_id, children, size)
2096
2099 """A block device with persistent node
2100
2101 May be either directly attached, or exposed through DM (e.g. dm-multipath).
2102 udev helpers are probably required to give persistent, human-friendly
2103 names.
2104
2105 For the time being, pathnames are required to lie under /dev.
2106
2107 """
2108 - def __init__(self, unique_id, children, size):
2109 """Attaches to a static block device.
2110
2111 The unique_id is a path under /dev.
2112
2113 """
2114 super(PersistentBlockDevice, self).__init__(unique_id, children, size)
2115 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2116 raise ValueError("Invalid configuration data %s" % str(unique_id))
2117 self.dev_path = unique_id[1]
2118 if not os.path.realpath(self.dev_path).startswith("/dev/"):
2119 raise ValueError("Full path '%s' lies outside /dev" %
2120 os.path.realpath(self.dev_path))
2121
2122
2123
2124
2125 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2126 raise ValueError("Got persistent block device of invalid type: %s" %
2127 unique_id[0])
2128
2129 self.major = self.minor = None
2130 self.Attach()
2131
2132 @classmethod
2133 - def Create(cls, unique_id, children, size):
2134 """Create a new device
2135
2136 This is a noop, we only return a PersistentBlockDevice instance
2137
2138 """
2139 return PersistentBlockDevice(unique_id, children, 0)
2140
2142 """Remove a device
2143
2144 This is a noop
2145
2146 """
2147 pass
2148
2150 """Rename this device.
2151
2152 """
2153 _ThrowError("Rename is not supported for PersistentBlockDev storage")
2154
2156 """Attach to an existing block device.
2157
2158
2159 """
2160 self.attached = False
2161 try:
2162 st = os.stat(self.dev_path)
2163 except OSError, err:
2164 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2165 return False
2166
2167 if not stat.S_ISBLK(st.st_mode):
2168 logging.error("%s is not a block device", self.dev_path)
2169 return False
2170
2171 self.major = os.major(st.st_rdev)
2172 self.minor = os.minor(st.st_rdev)
2173 self.attached = True
2174
2175 return True
2176
2178 """Assemble the device.
2179
2180 """
2181 pass
2182
2184 """Shutdown the device.
2185
2186 """
2187 pass
2188
2189 - def Open(self, force=False):
2190 """Make the device ready for I/O.
2191
2192 """
2193 pass
2194
2196 """Notifies that the device will no longer be used for I/O.
2197
2198 """
2199 pass
2200
2201 - def Grow(self, amount, dryrun):
2202 """Grow the logical volume.
2203
2204 """
2205 _ThrowError("Grow is not supported for PersistentBlockDev storage")
2206
2207
2208 DEV_MAP = {
2209 constants.LD_LV: LogicalVolume,
2210 constants.LD_DRBD8: DRBD8,
2211 constants.LD_BLOCKDEV: PersistentBlockDevice,
2212 }
2213
2214 if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2215 DEV_MAP[constants.LD_FILE] = FileStorage
2216
2217
2218 -def FindDevice(dev_type, unique_id, children, size):
2219 """Search for an existing, assembled device.
2220
2221 This will succeed only if the device exists and is assembled, but it
2222 does not do any actions in order to activate the device.
2223
2224 """
2225 if dev_type not in DEV_MAP:
2226 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2227 device = DEV_MAP[dev_type](unique_id, children, size)
2228 if not device.attached:
2229 return None
2230 return device
2231
2232
2233 -def Assemble(dev_type, unique_id, children, size):
2234 """Try to attach or assemble an existing device.
2235
2236 This will attach to assemble the device, as needed, to bring it
2237 fully up. It must be safe to run on already-assembled devices.
2238
2239 """
2240 if dev_type not in DEV_MAP:
2241 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2242 device = DEV_MAP[dev_type](unique_id, children, size)
2243 device.Assemble()
2244 return device
2245
2246
2247 -def Create(dev_type, unique_id, children, size):
2248 """Create a device.
2249
2250 """
2251 if dev_type not in DEV_MAP:
2252 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2253 device = DEV_MAP[dev_type].Create(unique_id, children, size)
2254 return device
2255