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