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