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