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