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