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