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