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