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