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