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