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