OS API: support for multiple versions in an OS
[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) != 6:
808       raise ValueError("Invalid configuration data %s" % str(unique_id))
809     (self._lhost, self._lport,
810      self._rhost, self._rport,
811      self._aminor, self._secret) = unique_id
812     if (self._lhost is not None and self._lhost == self._rhost and
813         self._lport == self._rport):
814       raise ValueError("Invalid configuration data, same local/remote %s" %
815                        (unique_id,))
816     self.Attach()
817
818   @classmethod
819   def _InitMeta(cls, minor, dev_path):
820     """Initialize a meta device.
821
822     This will not work if the given minor is in use.
823
824     """
825     result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
826                            "v08", dev_path, "0", "create-md"])
827     if result.failed:
828       raise errors.BlockDeviceError("Can't initialize meta device: %s" %
829                                     result.output)
830
831   @classmethod
832   def _FindUnusedMinor(cls):
833     """Find an unused DRBD device.
834
835     This is specific to 8.x as the minors are allocated dynamically,
836     so non-existing numbers up to a max minor count are actually free.
837
838     """
839     data = cls._GetProcData()
840
841     unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
842     used_line = re.compile("^ *([0-9]+): cs:")
843     highest = None
844     for line in data:
845       match = unused_line.match(line)
846       if match:
847         return int(match.group(1))
848       match = used_line.match(line)
849       if match:
850         minor = int(match.group(1))
851         highest = max(highest, minor)
852     if highest is None: # there are no minors in use at all
853       return 0
854     if highest >= cls._MAX_MINORS:
855       logging.error("Error: no free drbd minors!")
856       raise errors.BlockDeviceError("Can't find a free DRBD minor")
857     return highest + 1
858
859   @classmethod
860   def _IsValidMeta(cls, meta_device):
861     """Check if the given meta device looks like a valid one.
862
863     """
864     minor = cls._FindUnusedMinor()
865     minor_path = cls._DevPath(minor)
866     result = utils.RunCmd(["drbdmeta", minor_path,
867                            "v08", meta_device, "0",
868                            "dstate"])
869     if result.failed:
870       logging.error("Invalid meta device %s: %s", meta_device, result.output)
871       return False
872     return True
873
874   @classmethod
875   def _GetShowParser(cls):
876     """Return a parser for `drbd show` output.
877
878     This will either create or return an already-create parser for the
879     output of the command `drbd show`.
880
881     """
882     if cls._PARSE_SHOW is not None:
883       return cls._PARSE_SHOW
884
885     # pyparsing setup
886     lbrace = pyp.Literal("{").suppress()
887     rbrace = pyp.Literal("}").suppress()
888     semi = pyp.Literal(";").suppress()
889     # this also converts the value to an int
890     number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
891
892     comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
893     defa = pyp.Literal("_is_default").suppress()
894     dbl_quote = pyp.Literal('"').suppress()
895
896     keyword = pyp.Word(pyp.alphanums + '-')
897
898     # value types
899     value = pyp.Word(pyp.alphanums + '_-/.:')
900     quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
901     addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
902                  number)
903     # meta device, extended syntax
904     meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
905                   number + pyp.Word(']').suppress())
906
907     # a statement
908     stmt = (~rbrace + keyword + ~lbrace +
909             pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
910             pyp.Optional(defa) + semi +
911             pyp.Optional(pyp.restOfLine).suppress())
912
913     # an entire section
914     section_name = pyp.Word(pyp.alphas + '_')
915     section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
916
917     bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
918     bnf.ignore(comment)
919
920     cls._PARSE_SHOW = bnf
921
922     return bnf
923
924   @classmethod
925   def _GetShowData(cls, minor):
926     """Return the `drbdsetup show` data for a minor.
927
928     """
929     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
930     if result.failed:
931       logging.error("Can't display the drbd config: %s - %s",
932                     result.fail_reason, result.output)
933       return None
934     return result.stdout
935
936   @classmethod
937   def _GetDevInfo(cls, out):
938     """Parse details about a given DRBD minor.
939
940     This return, if available, the local backing device (as a path)
941     and the local and remote (ip, port) information from a string
942     containing the output of the `drbdsetup show` command as returned
943     by _GetShowData.
944
945     """
946     data = {}
947     if not out:
948       return data
949
950     bnf = cls._GetShowParser()
951     # run pyparse
952
953     try:
954       results = bnf.parseString(out)
955     except pyp.ParseException, err:
956       raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
957                                     str(err))
958
959     # and massage the results into our desired format
960     for section in results:
961       sname = section[0]
962       if sname == "_this_host":
963         for lst in section[1:]:
964           if lst[0] == "disk":
965             data["local_dev"] = lst[1]
966           elif lst[0] == "meta-disk":
967             data["meta_dev"] = lst[1]
968             data["meta_index"] = lst[2]
969           elif lst[0] == "address":
970             data["local_addr"] = tuple(lst[1:])
971       elif sname == "_remote_host":
972         for lst in section[1:]:
973           if lst[0] == "address":
974             data["remote_addr"] = tuple(lst[1:])
975     return data
976
977   def _MatchesLocal(self, info):
978     """Test if our local config matches with an existing device.
979
980     The parameter should be as returned from `_GetDevInfo()`. This
981     method tests if our local backing device is the same as the one in
982     the info parameter, in effect testing if we look like the given
983     device.
984
985     """
986     if self._children:
987       backend, meta = self._children
988     else:
989       backend = meta = None
990
991     if backend is not None:
992       retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
993     else:
994       retval = ("local_dev" not in info)
995
996     if meta is not None:
997       retval = retval and ("meta_dev" in info and
998                            info["meta_dev"] == meta.dev_path)
999       retval = retval and ("meta_index" in info and
1000                            info["meta_index"] == 0)
1001     else:
1002       retval = retval and ("meta_dev" not in info and
1003                            "meta_index" not in info)
1004     return retval
1005
1006   def _MatchesNet(self, info):
1007     """Test if our network config matches with an existing device.
1008
1009     The parameter should be as returned from `_GetDevInfo()`. This
1010     method tests if our network configuration is the same as the one
1011     in the info parameter, in effect testing if we look like the given
1012     device.
1013
1014     """
1015     if (((self._lhost is None and not ("local_addr" in info)) and
1016          (self._rhost is None and not ("remote_addr" in info)))):
1017       return True
1018
1019     if self._lhost is None:
1020       return False
1021
1022     if not ("local_addr" in info and
1023             "remote_addr" in info):
1024       return False
1025
1026     retval = (info["local_addr"] == (self._lhost, self._lport))
1027     retval = (retval and
1028               info["remote_addr"] == (self._rhost, self._rport))
1029     return retval
1030
1031   @classmethod
1032   def _AssembleLocal(cls, minor, backend, meta):
1033     """Configure the local part of a DRBD device.
1034
1035     This is the first thing that must be done on an unconfigured DRBD
1036     device. And it must be done only once.
1037
1038     """
1039     if not cls._IsValidMeta(meta):
1040       return False
1041     args = ["drbdsetup", cls._DevPath(minor), "disk",
1042             backend, meta, "0", "-e", "detach", "--create-device"]
1043     result = utils.RunCmd(args)
1044     if result.failed:
1045       logging.error("Can't attach local disk: %s", result.output)
1046     return not result.failed
1047
1048   @classmethod
1049   def _AssembleNet(cls, minor, net_info, protocol,
1050                    dual_pri=False, hmac=None, secret=None):
1051     """Configure the network part of the device.
1052
1053     """
1054     lhost, lport, rhost, rport = net_info
1055     if None in net_info:
1056       # we don't want network connection and actually want to make
1057       # sure its shutdown
1058       return cls._ShutdownNet(minor)
1059
1060     args = ["drbdsetup", cls._DevPath(minor), "net",
1061             "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1062             "-A", "discard-zero-changes",
1063             "-B", "consensus",
1064             "--create-device",
1065             ]
1066     if dual_pri:
1067       args.append("-m")
1068     if hmac and secret:
1069       args.extend(["-a", hmac, "-x", secret])
1070     result = utils.RunCmd(args)
1071     if result.failed:
1072       logging.error("Can't setup network for dbrd device: %s - %s",
1073                     result.fail_reason, result.output)
1074       return False
1075
1076     timeout = time.time() + 10
1077     ok = False
1078     while time.time() < timeout:
1079       info = cls._GetDevInfo(cls._GetShowData(minor))
1080       if not "local_addr" in info or not "remote_addr" in info:
1081         time.sleep(1)
1082         continue
1083       if (info["local_addr"] != (lhost, lport) or
1084           info["remote_addr"] != (rhost, rport)):
1085         time.sleep(1)
1086         continue
1087       ok = True
1088       break
1089     if not ok:
1090       logging.error("Timeout while configuring network")
1091       return False
1092     return True
1093
1094   def AddChildren(self, devices):
1095     """Add a disk to the DRBD device.
1096
1097     """
1098     if self.minor is None:
1099       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1100     if len(devices) != 2:
1101       raise errors.BlockDeviceError("Need two devices for AddChildren")
1102     info = self._GetDevInfo(self._GetShowData(self.minor))
1103     if "local_dev" in info:
1104       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1105     backend, meta = devices
1106     if backend.dev_path is None or meta.dev_path is None:
1107       raise errors.BlockDeviceError("Children not ready during AddChildren")
1108     backend.Open()
1109     meta.Open()
1110     if not self._CheckMetaSize(meta.dev_path):
1111       raise errors.BlockDeviceError("Invalid meta device size")
1112     self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1113     if not self._IsValidMeta(meta.dev_path):
1114       raise errors.BlockDeviceError("Cannot initalize meta device")
1115
1116     if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1117       raise errors.BlockDeviceError("Can't attach to local storage")
1118     self._children = devices
1119
1120   def RemoveChildren(self, devices):
1121     """Detach the drbd device from local storage.
1122
1123     """
1124     if self.minor is None:
1125       raise errors.BlockDeviceError("Can't attach to drbd8 during"
1126                                     " RemoveChildren")
1127     # early return if we don't actually have backing storage
1128     info = self._GetDevInfo(self._GetShowData(self.minor))
1129     if "local_dev" not in info:
1130       return
1131     if len(self._children) != 2:
1132       raise errors.BlockDeviceError("We don't have two children: %s" %
1133                                     self._children)
1134     if self._children.count(None) == 2: # we don't actually have children :)
1135       logging.error("Requested detach while detached")
1136       return
1137     if len(devices) != 2:
1138       raise errors.BlockDeviceError("We need two children in RemoveChildren")
1139     for child, dev in zip(self._children, devices):
1140       if dev != child.dev_path:
1141         raise errors.BlockDeviceError("Mismatch in local storage"
1142                                       " (%s != %s) in RemoveChildren" %
1143                                       (dev, child.dev_path))
1144
1145     if not self._ShutdownLocal(self.minor):
1146       raise errors.BlockDeviceError("Can't detach from local storage")
1147     self._children = []
1148
1149   def SetSyncSpeed(self, kbytes):
1150     """Set the speed of the DRBD syncer.
1151
1152     """
1153     children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1154     if self.minor is None:
1155       logging.info("Instance not attached to a device")
1156       return False
1157     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1158                            kbytes])
1159     if result.failed:
1160       logging.error("Can't change syncer rate: %s - %s",
1161                     result.fail_reason, result.output)
1162     return not result.failed and children_result
1163
1164   def GetProcStatus(self):
1165     """Return device data from /proc.
1166
1167     """
1168     if self.minor is None:
1169       raise errors.BlockDeviceError("GetStats() called while not attached")
1170     proc_info = self._MassageProcData(self._GetProcData())
1171     if self.minor not in proc_info:
1172       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1173                                     self.minor)
1174     return DRBD8Status(proc_info[self.minor])
1175
1176   def GetSyncStatus(self):
1177     """Returns the sync status of the device.
1178
1179     Returns:
1180      (sync_percent, estimated_time, is_degraded)
1181
1182     If sync_percent is None, it means all is ok
1183     If estimated_time is None, it means we can't esimate
1184     the time needed, otherwise it's the time left in seconds.
1185
1186
1187     We set the is_degraded parameter to True on two conditions:
1188     network not connected or local disk missing.
1189
1190     We compute the ldisk parameter based on wheter we have a local
1191     disk or not.
1192
1193     """
1194     if self.minor is None and not self.Attach():
1195       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1196     stats = self.GetProcStatus()
1197     ldisk = not stats.is_disk_uptodate
1198     is_degraded = not stats.is_connected
1199     return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1200
1201   def Open(self, force=False):
1202     """Make the local state primary.
1203
1204     If the 'force' parameter is given, the '-o' option is passed to
1205     drbdsetup. Since this is a potentially dangerous operation, the
1206     force flag should be only given after creation, when it actually
1207     is mandatory.
1208
1209     """
1210     if self.minor is None and not self.Attach():
1211       logging.error("DRBD cannot attach to a device during open")
1212       return False
1213     cmd = ["drbdsetup", self.dev_path, "primary"]
1214     if force:
1215       cmd.append("-o")
1216     result = utils.RunCmd(cmd)
1217     if result.failed:
1218       msg = ("Can't make drbd device primary: %s" % result.output)
1219       logging.error(msg)
1220       raise errors.BlockDeviceError(msg)
1221
1222   def Close(self):
1223     """Make the local state secondary.
1224
1225     This will, of course, fail if the device is in use.
1226
1227     """
1228     if self.minor is None and not self.Attach():
1229       logging.info("Instance not attached to a device")
1230       raise errors.BlockDeviceError("Can't find device")
1231     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1232     if result.failed:
1233       msg = ("Can't switch drbd device to"
1234              " secondary: %s" % result.output)
1235       logging.error(msg)
1236       raise errors.BlockDeviceError(msg)
1237
1238   def Attach(self):
1239     """Find a DRBD device which matches our config and attach to it.
1240
1241     In case of partially attached (local device matches but no network
1242     setup), we perform the network attach. If successful, we re-test
1243     the attach if can return success.
1244
1245     """
1246     for minor in (self._aminor,):
1247       info = self._GetDevInfo(self._GetShowData(minor))
1248       match_l = self._MatchesLocal(info)
1249       match_r = self._MatchesNet(info)
1250       if match_l and match_r:
1251         break
1252       if match_l and not match_r and "local_addr" not in info:
1253         res_r = self._AssembleNet(minor,
1254                                   (self._lhost, self._lport,
1255                                    self._rhost, self._rport),
1256                                   constants.DRBD_NET_PROTOCOL,
1257                                   hmac=constants.DRBD_HMAC_ALG,
1258                                   secret=self._secret
1259                                   )
1260         if res_r:
1261           if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1262             break
1263       # the weakest case: we find something that is only net attached
1264       # even though we were passed some children at init time
1265       if match_r and "local_dev" not in info:
1266         break
1267
1268       # this case must be considered only if we actually have local
1269       # storage, i.e. not in diskless mode, because all diskless
1270       # devices are equal from the point of view of local
1271       # configuration
1272       if (match_l and "local_dev" in info and
1273           not match_r and "local_addr" in info):
1274         # strange case - the device network part points to somewhere
1275         # else, even though its local storage is ours; as we own the
1276         # drbd space, we try to disconnect from the remote peer and
1277         # reconnect to our correct one
1278         if not self._ShutdownNet(minor):
1279           raise errors.BlockDeviceError("Device has correct local storage,"
1280                                         " wrong remote peer and is unable to"
1281                                         " disconnect in order to attach to"
1282                                         " the correct peer")
1283         # note: _AssembleNet also handles the case when we don't want
1284         # local storage (i.e. one or more of the _[lr](host|port) is
1285         # None)
1286         if (self._AssembleNet(minor, (self._lhost, self._lport,
1287                                       self._rhost, self._rport),
1288                               constants.DRBD_NET_PROTOCOL,
1289                               hmac=constants.DRBD_HMAC_ALG,
1290                               secret=self._secret) and
1291             self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1292           break
1293
1294     else:
1295       minor = None
1296
1297     self._SetFromMinor(minor)
1298     return minor is not None
1299
1300   def Assemble(self):
1301     """Assemble the drbd.
1302
1303     Method:
1304       - if we have a local backing device, we bind to it by:
1305         - checking the list of used drbd devices
1306         - check if the local minor use of any of them is our own device
1307         - if yes, abort?
1308         - if not, bind
1309       - if we have a local/remote net info:
1310         - redo the local backing device step for the remote device
1311         - check if any drbd device is using the local port,
1312           if yes abort
1313         - check if any remote drbd device is using the remote
1314           port, if yes abort (for now)
1315         - bind our net port
1316         - bind the remote net port
1317
1318     """
1319     self.Attach()
1320     if self.minor is not None:
1321       logging.info("Already assembled")
1322       return True
1323
1324     result = super(DRBD8, self).Assemble()
1325     if not result:
1326       return result
1327
1328     # TODO: maybe completely tear-down the minor (drbdsetup ... down)
1329     # before attaching our own?
1330     minor = self._aminor
1331     need_localdev_teardown = False
1332     if self._children and self._children[0] and self._children[1]:
1333       result = self._AssembleLocal(minor, self._children[0].dev_path,
1334                                    self._children[1].dev_path)
1335       if not result:
1336         return False
1337       need_localdev_teardown = True
1338     if self._lhost and self._lport and self._rhost and self._rport:
1339       result = self._AssembleNet(minor,
1340                                  (self._lhost, self._lport,
1341                                   self._rhost, self._rport),
1342                                  constants.DRBD_NET_PROTOCOL,
1343                                  hmac=constants.DRBD_HMAC_ALG,
1344                                  secret=self._secret)
1345       if not result:
1346         if need_localdev_teardown:
1347           # we will ignore failures from this
1348           logging.error("net setup failed, tearing down local device")
1349           self._ShutdownAll(minor)
1350         return False
1351     self._SetFromMinor(minor)
1352     return True
1353
1354   @classmethod
1355   def _ShutdownLocal(cls, minor):
1356     """Detach from the local device.
1357
1358     I/Os will continue to be served from the remote device. If we
1359     don't have a remote device, this operation will fail.
1360
1361     """
1362     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1363     if result.failed:
1364       logging.error("Can't detach local device: %s", result.output)
1365     return not result.failed
1366
1367   @classmethod
1368   def _ShutdownNet(cls, minor):
1369     """Disconnect from the remote peer.
1370
1371     This fails if we don't have a local device.
1372
1373     """
1374     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1375     if result.failed:
1376       logging.error("Can't shutdown network: %s", result.output)
1377     return not result.failed
1378
1379   @classmethod
1380   def _ShutdownAll(cls, minor):
1381     """Deactivate the device.
1382
1383     This will, of course, fail if the device is in use.
1384
1385     """
1386     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1387     if result.failed:
1388       logging.error("Can't shutdown drbd device: %s", result.output)
1389     return not result.failed
1390
1391   def Shutdown(self):
1392     """Shutdown the DRBD device.
1393
1394     """
1395     if self.minor is None and not self.Attach():
1396       logging.info("DRBD device not attached to a device during Shutdown")
1397       return True
1398     if not self._ShutdownAll(self.minor):
1399       return False
1400     self.minor = None
1401     self.dev_path = None
1402     return True
1403
1404   def Remove(self):
1405     """Stub remove for DRBD devices.
1406
1407     """
1408     return self.Shutdown()
1409
1410   @classmethod
1411   def Create(cls, unique_id, children, size):
1412     """Create a new DRBD8 device.
1413
1414     Since DRBD devices are not created per se, just assembled, this
1415     function only initializes the metadata.
1416
1417     """
1418     if len(children) != 2:
1419       raise errors.ProgrammerError("Invalid setup for the drbd device")
1420     meta = children[1]
1421     meta.Assemble()
1422     if not meta.Attach():
1423       raise errors.BlockDeviceError("Can't attach to meta device")
1424     if not cls._CheckMetaSize(meta.dev_path):
1425       raise errors.BlockDeviceError("Invalid meta device size")
1426     cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1427     if not cls._IsValidMeta(meta.dev_path):
1428       raise errors.BlockDeviceError("Cannot initalize meta device")
1429     return cls(unique_id, children)
1430
1431   def Grow(self, amount):
1432     """Resize the DRBD device and its backing storage.
1433
1434     """
1435     if self.minor is None:
1436       raise errors.ProgrammerError("drbd8: Grow called while not attached")
1437     if len(self._children) != 2 or None in self._children:
1438       raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1439     self._children[0].Grow(amount)
1440     result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1441     if result.failed:
1442       raise errors.BlockDeviceError("resize failed for %s: %s" %
1443                                     (self.dev_path, result.output))
1444     return
1445
1446
1447 class FileStorage(BlockDev):
1448   """File device.
1449
1450   This class represents the a file storage backend device.
1451
1452   The unique_id for the file device is a (file_driver, file_path) tuple.
1453
1454   """
1455   def __init__(self, unique_id, children):
1456     """Initalizes a file device backend.
1457
1458     """
1459     if children:
1460       raise errors.BlockDeviceError("Invalid setup for file device")
1461     super(FileStorage, self).__init__(unique_id, children)
1462     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1463       raise ValueError("Invalid configuration data %s" % str(unique_id))
1464     self.driver = unique_id[0]
1465     self.dev_path = unique_id[1]
1466
1467   def Assemble(self):
1468     """Assemble the device.
1469
1470     Checks whether the file device exists, raises BlockDeviceError otherwise.
1471
1472     """
1473     if not os.path.exists(self.dev_path):
1474       raise errors.BlockDeviceError("File device '%s' does not exist." %
1475                                     self.dev_path)
1476     return True
1477
1478   def Shutdown(self):
1479     """Shutdown the device.
1480
1481     This is a no-op for the file type, as we don't deacivate
1482     the file on shutdown.
1483
1484     """
1485     return True
1486
1487   def Open(self, force=False):
1488     """Make the device ready for I/O.
1489
1490     This is a no-op for the file type.
1491
1492     """
1493     pass
1494
1495   def Close(self):
1496     """Notifies that the device will no longer be used for I/O.
1497
1498     This is a no-op for the file type.
1499
1500     """
1501     pass
1502
1503   def Remove(self):
1504     """Remove the file backing the block device.
1505
1506     Returns:
1507       boolean indicating wheter removal of file was successful or not.
1508
1509     """
1510     if not os.path.exists(self.dev_path):
1511       return True
1512     try:
1513       os.remove(self.dev_path)
1514       return True
1515     except OSError, err:
1516       logging.error("Can't remove file '%s': %s", self.dev_path, err)
1517       return False
1518
1519   def Attach(self):
1520     """Attach to an existing file.
1521
1522     Check if this file already exists.
1523
1524     Returns:
1525       boolean indicating if file exists or not.
1526
1527     """
1528     if os.path.exists(self.dev_path):
1529       return True
1530     return False
1531
1532   @classmethod
1533   def Create(cls, unique_id, children, size):
1534     """Create a new file.
1535
1536     Args:
1537       children:
1538       size: integer size of file in MiB
1539
1540     Returns:
1541       A ganeti.bdev.FileStorage object.
1542
1543     """
1544     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1545       raise ValueError("Invalid configuration data %s" % str(unique_id))
1546     dev_path = unique_id[1]
1547     try:
1548       f = open(dev_path, 'w')
1549     except IOError, err:
1550       raise errors.BlockDeviceError("Could not create '%'" % err)
1551     else:
1552       f.truncate(size * 1024 * 1024)
1553       f.close()
1554
1555     return FileStorage(unique_id, children)
1556
1557
1558 DEV_MAP = {
1559   constants.LD_LV: LogicalVolume,
1560   constants.LD_DRBD8: DRBD8,
1561   constants.LD_FILE: FileStorage,
1562   }
1563
1564
1565 def FindDevice(dev_type, unique_id, children):
1566   """Search for an existing, assembled device.
1567
1568   This will succeed only if the device exists and is assembled, but it
1569   does not do any actions in order to activate the device.
1570
1571   """
1572   if dev_type not in DEV_MAP:
1573     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1574   device = DEV_MAP[dev_type](unique_id, children)
1575   if not device.attached:
1576     return None
1577   return  device
1578
1579
1580 def AttachOrAssemble(dev_type, unique_id, children):
1581   """Try to attach or assemble an existing device.
1582
1583   This will attach to an existing assembled device or will assemble
1584   the device, as needed, to bring it fully up.
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](unique_id, children)
1590   if not device.attached:
1591     device.Assemble()
1592     if not device.attached:
1593       raise errors.BlockDeviceError("Can't find a valid block device for"
1594                                     " %s/%s/%s" %
1595                                     (dev_type, unique_id, children))
1596   return device
1597
1598
1599 def Create(dev_type, unique_id, children, size):
1600   """Create a device.
1601
1602   """
1603   if dev_type not in DEV_MAP:
1604     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1605   device = DEV_MAP[dev_type].Create(unique_id, children, size)
1606   return device