DRBD: Fix another bug in diskless activation
[ganeti-local] / lib / bdev.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Block device abstraction"""
23
24 import re
25 import time
26 import errno
27 import pyparsing as pyp
28 import os
29
30 from ganeti import utils
31 from ganeti import logger
32 from ganeti import errors
33 from ganeti import constants
34
35
36 class BlockDev(object):
37   """Block device abstract class.
38
39   A block device can be in the following states:
40     - not existing on the system, and by `Create()` it goes into:
41     - existing but not setup/not active, and by `Assemble()` goes into:
42     - active read-write and by `Open()` it goes into
43     - online (=used, or ready for use)
44
45   A device can also be online but read-only, however we are not using
46   the readonly state (MD and LV have it, if needed in the future)
47   and we are usually looking at this like at a stack, so it's easier
48   to 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     - md arrays are created or assembled and used
55     - drbd devices are attached to a local disk/remote peer and made primary
56
57   A block device is identified by three items:
58     - the /dev path of the device (dynamic)
59     - a unique ID of the device (static)
60     - it's major/minor pair (dynamic)
61
62   Not all devices implement both the first two as distinct items. LVM
63   logical volumes have their unique ID (the pair volume group, logical
64   volume name) in a 1-to-1 relation to the dev path. For MD devices,
65   the /dev path is dynamic and the unique ID is the UUID generated at
66   array creation plus the slave list. For DRBD devices, the /dev path
67   is again dynamic and the unique id is the pair (host1, dev1),
68   (host2, dev2).
69
70   You can get to a device in two ways:
71     - creating the (real) device, which returns you
72       an attached instance (lvcreate, mdadm --create)
73     - attaching of a python instance to an existing (real) device
74
75   The second point, the attachement to a device, is different
76   depending on whether the device is assembled or not. At init() time,
77   we search for a device with the same unique_id as us. If found,
78   good. It also means that the device is already assembled. If not,
79   after assembly we'll have our correct major/minor.
80
81   """
82   def __init__(self, unique_id, children):
83     self._children = children
84     self.dev_path = None
85     self.unique_id = unique_id
86     self.major = None
87     self.minor = None
88
89   def Assemble(self):
90     """Assemble the device from its components.
91
92     If this is a plain block device (e.g. LVM) than assemble does
93     nothing, as the LVM has no children and we don't put logical
94     volumes offline.
95
96     One guarantee is that after the device has been assembled, it
97     knows its major/minor numbers. This allows other devices (usually
98     parents) to probe correctly for their children.
99
100     """
101     status = True
102     for child in self._children:
103       if not isinstance(child, BlockDev):
104         raise TypeError("Invalid child passed of type '%s'" % type(child))
105       if not status:
106         break
107       status = status and child.Assemble()
108       if not status:
109         break
110
111       try:
112         child.Open()
113       except errors.BlockDeviceError:
114         for child in self._children:
115           child.Shutdown()
116         raise
117
118     if not status:
119       for child in self._children:
120         child.Shutdown()
121     return status
122
123   def Attach(self):
124     """Find a device which matches our config and attach to it.
125
126     """
127     raise NotImplementedError
128
129   def Close(self):
130     """Notifies that the device will no longer be used for I/O.
131
132     """
133     raise NotImplementedError
134
135   @classmethod
136   def Create(cls, unique_id, children, size):
137     """Create the device.
138
139     If the device cannot be created, it will return None
140     instead. Error messages go to the logging system.
141
142     Note that for some devices, the unique_id is used, and for other,
143     the children. The idea is that these two, taken together, are
144     enough for both creation and assembly (later).
145
146     """
147     raise NotImplementedError
148
149   def Remove(self):
150     """Remove this device.
151
152     This makes sense only for some of the device types: LV and to a
153     lesser degree, md devices. Also note that if the device can't
154     attach, the removal can't be completed.
155
156     """
157     raise NotImplementedError
158
159   def Rename(self, new_id):
160     """Rename this device.
161
162     This may or may not make sense for a given device type.
163
164     """
165     raise NotImplementedError
166
167   def Open(self, force=False):
168     """Make the device ready for use.
169
170     This makes the device ready for I/O. For now, just the DRBD
171     devices need this.
172
173     The force parameter signifies that if the device has any kind of
174     --force thing, it should be used, we know what we are doing.
175
176     """
177     raise NotImplementedError
178
179   def Shutdown(self):
180     """Shut down the device, freeing its children.
181
182     This undoes the `Assemble()` work, except for the child
183     assembling; as such, the children on the device are still
184     assembled after this call.
185
186     """
187     raise NotImplementedError
188
189   def SetSyncSpeed(self, speed):
190     """Adjust the sync speed of the mirror.
191
192     In case this is not a mirroring device, this is no-op.
193
194     """
195     result = True
196     if self._children:
197       for child in self._children:
198         result = result and child.SetSyncSpeed(speed)
199     return result
200
201   def GetSyncStatus(self):
202     """Returns the sync status of the device.
203
204     If this device is a mirroring device, this function returns the
205     status of the mirror.
206
207     Returns:
208      (sync_percent, estimated_time, is_degraded, ldisk)
209
210     If sync_percent is None, it means the device is not syncing.
211
212     If estimated_time is None, it means we can't estimate
213     the time needed, otherwise it's the time left in seconds.
214
215     If is_degraded is True, it means the device is missing
216     redundancy. This is usually a sign that something went wrong in
217     the device setup, if sync_percent is None.
218
219     The ldisk parameter represents the degradation of the local
220     data. This is only valid for some devices, the rest will always
221     return False (not degraded).
222
223     """
224     return None, None, False, False
225
226
227   def CombinedSyncStatus(self):
228     """Calculate the mirror status recursively for our children.
229
230     The return value is the same as for `GetSyncStatus()` except the
231     minimum percent and maximum time are calculated across our
232     children.
233
234     """
235     min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
236     if self._children:
237       for child in self._children:
238         c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
239         if min_percent is None:
240           min_percent = c_percent
241         elif c_percent is not None:
242           min_percent = min(min_percent, c_percent)
243         if max_time is None:
244           max_time = c_time
245         elif c_time is not None:
246           max_time = max(max_time, c_time)
247         is_degraded = is_degraded or c_degraded
248         ldisk = ldisk or c_ldisk
249     return min_percent, max_time, is_degraded, ldisk
250
251
252   def SetInfo(self, text):
253     """Update metadata with info text.
254
255     Only supported for some device types.
256
257     """
258     for child in self._children:
259       child.SetInfo(text)
260
261
262   def __repr__(self):
263     return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
264             (self.__class__, self.unique_id, self._children,
265              self.major, self.minor, self.dev_path))
266
267
268 class LogicalVolume(BlockDev):
269   """Logical Volume block device.
270
271   """
272   def __init__(self, unique_id, children):
273     """Attaches to a LV device.
274
275     The unique_id is a tuple (vg_name, lv_name)
276
277     """
278     super(LogicalVolume, self).__init__(unique_id, children)
279     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
280       raise ValueError("Invalid configuration data %s" % str(unique_id))
281     self._vg_name, self._lv_name = unique_id
282     self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
283     self.Attach()
284
285   @classmethod
286   def Create(cls, unique_id, children, size):
287     """Create a new logical volume.
288
289     """
290     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
291       raise ValueError("Invalid configuration data %s" % str(unique_id))
292     vg_name, lv_name = unique_id
293     pvs_info = cls.GetPVInfo(vg_name)
294     if not pvs_info:
295       raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
296                                     vg_name)
297     pvs_info.sort()
298     pvs_info.reverse()
299
300     pvlist = [ pv[1] for pv in pvs_info ]
301     free_size = sum([ pv[0] for pv in pvs_info ])
302
303     # The size constraint should have been checked from the master before
304     # calling the create function.
305     if free_size < size:
306       raise errors.BlockDeviceError("Not enough free space: required %s,"
307                                     " available %s" % (size, free_size))
308     result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
309                            vg_name] + pvlist)
310     if result.failed:
311       raise errors.BlockDeviceError(result.fail_reason)
312     return LogicalVolume(unique_id, children)
313
314   @staticmethod
315   def GetPVInfo(vg_name):
316     """Get the free space info for PVs in a volume group.
317
318     Args:
319       vg_name: the volume group name
320
321     Returns:
322       list of (free_space, name) with free_space in mebibytes
323
324     """
325     command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
326                "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
327                "--separator=:"]
328     result = utils.RunCmd(command)
329     if result.failed:
330       logger.Error("Can't get the PV information: %s" % result.fail_reason)
331       return None
332     data = []
333     for line in result.stdout.splitlines():
334       fields = line.strip().split(':')
335       if len(fields) != 4:
336         logger.Error("Can't parse pvs output: line '%s'" % line)
337         return None
338       # skip over pvs from another vg or ones which are not allocatable
339       if fields[1] != vg_name or fields[3][0] != 'a':
340         continue
341       data.append((float(fields[2]), fields[0]))
342
343     return data
344
345   def Remove(self):
346     """Remove this logical volume.
347
348     """
349     if not self.minor and not self.Attach():
350       # the LV does not exist
351       return True
352     result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
353                            (self._vg_name, self._lv_name)])
354     if result.failed:
355       logger.Error("Can't lvremove: %s" % result.fail_reason)
356
357     return not result.failed
358
359   def Rename(self, new_id):
360     """Rename this logical volume.
361
362     """
363     if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
364       raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
365     new_vg, new_name = new_id
366     if new_vg != self._vg_name:
367       raise errors.ProgrammerError("Can't move a logical volume across"
368                                    " volume groups (from %s to to %s)" %
369                                    (self._vg_name, new_vg))
370     result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
371     if result.failed:
372       raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
373                                     result.output)
374     self._lv_name = new_name
375     self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
376
377   def Attach(self):
378     """Attach to an existing LV.
379
380     This method will try to see if an existing and active LV exists
381     which matches our name. If so, its major/minor will be
382     recorded.
383
384     """
385     result = utils.RunCmd(["lvdisplay", self.dev_path])
386     if result.failed:
387       logger.Error("Can't find LV %s: %s, %s" %
388                    (self.dev_path, result.fail_reason, result.output))
389       return False
390     match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
391     for line in result.stdout.splitlines():
392       match_result = match.match(line)
393       if match_result:
394         self.major = int(match_result.group(1))
395         self.minor = int(match_result.group(2))
396         return True
397     return False
398
399   def Assemble(self):
400     """Assemble the device.
401
402     We alway run `lvchange -ay` on the LV to ensure it's active before
403     use, as there were cases when xenvg was not active after boot
404     (also possibly after disk issues).
405
406     """
407     result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
408     if result.failed:
409       logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
410     return not result.failed
411
412   def Shutdown(self):
413     """Shutdown the device.
414
415     This is a no-op for the LV device type, as we don't deactivate the
416     volumes on shutdown.
417
418     """
419     return True
420
421   def GetSyncStatus(self):
422     """Returns the sync status of the device.
423
424     If this device is a mirroring device, this function returns the
425     status of the mirror.
426
427     Returns:
428      (sync_percent, estimated_time, is_degraded, ldisk)
429
430     For logical volumes, sync_percent and estimated_time are always
431     None (no recovery in progress, as we don't handle the mirrored LV
432     case). The is_degraded parameter is the inverse of the ldisk
433     parameter.
434
435     For the ldisk parameter, we check if the logical volume has the
436     'virtual' type, which means it's not backed by existing storage
437     anymore (read from it return I/O error). This happens after a
438     physical disk failure and subsequent 'vgreduce --removemissing' on
439     the volume group.
440
441     """
442     result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
443     if result.failed:
444       logger.Error("Can't display lv: %s" % result.fail_reason)
445       return None, None, True, True
446     out = result.stdout.strip()
447     # format: type/permissions/alloc/fixed_minor/state/open
448     if len(out) != 6:
449       logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
450       return None, None, True, True
451     ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
452                           # backing storage
453     return None, None, ldisk, ldisk
454
455   def Open(self, force=False):
456     """Make the device ready for I/O.
457
458     This is a no-op for the LV device type.
459
460     """
461     pass
462
463   def Close(self):
464     """Notifies that the device will no longer be used for I/O.
465
466     This is a no-op for the LV device type.
467
468     """
469     pass
470
471   def Snapshot(self, size):
472     """Create a snapshot copy of an lvm block device.
473
474     """
475     snap_name = self._lv_name + ".snap"
476
477     # remove existing snapshot if found
478     snap = LogicalVolume((self._vg_name, snap_name), None)
479     snap.Remove()
480
481     pvs_info = self.GetPVInfo(self._vg_name)
482     if not pvs_info:
483       raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
484                                     self._vg_name)
485     pvs_info.sort()
486     pvs_info.reverse()
487     free_size, pv_name = pvs_info[0]
488     if free_size < size:
489       raise errors.BlockDeviceError("Not enough free space: required %s,"
490                                     " available %s" % (size, free_size))
491
492     result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
493                            "-n%s" % snap_name, self.dev_path])
494     if result.failed:
495       raise errors.BlockDeviceError("command: %s error: %s" %
496                                     (result.cmd, result.fail_reason))
497
498     return snap_name
499
500   def SetInfo(self, text):
501     """Update metadata with info text.
502
503     """
504     BlockDev.SetInfo(self, text)
505
506     # Replace invalid characters
507     text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
508     text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
509
510     # Only up to 128 characters are allowed
511     text = text[:128]
512
513     result = utils.RunCmd(["lvchange", "--addtag", text,
514                            self.dev_path])
515     if result.failed:
516       raise errors.BlockDeviceError("Command: %s error: %s" %
517                                     (result.cmd, result.fail_reason))
518
519
520 class MDRaid1(BlockDev):
521   """raid1 device implemented via md.
522
523   """
524   def __init__(self, unique_id, children):
525     super(MDRaid1, self).__init__(unique_id, children)
526     self.major = 9
527     self.Attach()
528
529   def Attach(self):
530     """Find an array which matches our config and attach to it.
531
532     This tries to find a MD array which has the same UUID as our own.
533
534     """
535     minor = self._FindMDByUUID(self.unique_id)
536     if minor is not None:
537       self._SetFromMinor(minor)
538     else:
539       self.minor = None
540       self.dev_path = None
541
542     return (minor is not None)
543
544   @staticmethod
545   def _GetUsedDevs():
546     """Compute the list of in-use MD devices.
547
548     It doesn't matter if the used device have other raid level, just
549     that they are in use.
550
551     """
552     mdstat = open("/proc/mdstat", "r")
553     data = mdstat.readlines()
554     mdstat.close()
555
556     used_md = {}
557     valid_line = re.compile("^md([0-9]+) : .*$")
558     for line in data:
559       match = valid_line.match(line)
560       if match:
561         md_no = int(match.group(1))
562         used_md[md_no] = line
563
564     return used_md
565
566   @staticmethod
567   def _GetDevInfo(minor):
568     """Get info about a MD device.
569
570     Currently only uuid is returned.
571
572     """
573     result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
574     if result.failed:
575       logger.Error("Can't display md: %s" % result.fail_reason)
576       return None
577     retval = {}
578     for line in result.stdout.splitlines():
579       line = line.strip()
580       kv = line.split(" : ", 1)
581       if kv:
582         if kv[0] == "UUID":
583           retval["uuid"] = kv[1].split()[0]
584         elif kv[0] == "State":
585           retval["state"] = kv[1].split(", ")
586     return retval
587
588   @staticmethod
589   def _FindUnusedMinor():
590     """Compute an unused MD minor.
591
592     This code assumes that there are 256 minors only.
593
594     """
595     used_md = MDRaid1._GetUsedDevs()
596     i = 0
597     while i < 256:
598       if i not in used_md:
599         break
600       i += 1
601     if i == 256:
602       logger.Error("Critical: Out of md minor numbers.")
603       raise errors.BlockDeviceError("Can't find a free MD minor")
604     return i
605
606   @classmethod
607   def _FindMDByUUID(cls, uuid):
608     """Find the minor of an MD array with a given UUID.
609
610     """
611     md_list = cls._GetUsedDevs()
612     for minor in md_list:
613       info = cls._GetDevInfo(minor)
614       if info and info["uuid"] == uuid:
615         return minor
616     return None
617
618   @staticmethod
619   def _ZeroSuperblock(dev_path):
620     """Zero the possible locations for an MD superblock.
621
622     The zero-ing can't be done via ``mdadm --zero-superblock`` as that
623     fails in versions 2.x with the same error code as non-writable
624     device.
625
626     The superblocks are located at (negative values are relative to
627     the end of the block device):
628       - -128k to end for version 0.90 superblock
629       - -8k to -12k for version 1.0 superblock (included in the above)
630       - 0k to 4k for version 1.1 superblock
631       - 4k to 8k for version 1.2 superblock
632
633     To cover all situations, the zero-ing will be:
634       - 0k to 128k
635       - -128k to end
636
637     As such, the minimum device size must be 128k, otherwise we'll get
638     I/O errors.
639
640     Note that this function depends on the fact that one can open,
641     read and write block devices normally.
642
643     """
644     overwrite_size = 128 * 1024
645     empty_buf = '\0' * overwrite_size
646     fd = open(dev_path, "r+")
647     try:
648       fd.seek(0, 0)
649       p1 = fd.tell()
650       fd.write(empty_buf)
651       p2 = fd.tell()
652       logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
653       fd.seek(-overwrite_size, 2)
654       p1 = fd.tell()
655       fd.write(empty_buf)
656       p2 = fd.tell()
657       logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
658     finally:
659       fd.close()
660
661   @classmethod
662   def Create(cls, unique_id, children, size):
663     """Create a new MD raid1 array.
664
665     """
666     if not isinstance(children, (tuple, list)):
667       raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
668                        str(children))
669     for i in children:
670       if not isinstance(i, BlockDev):
671         raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
672     for i in children:
673       try:
674         cls._ZeroSuperblock(i.dev_path)
675       except EnvironmentError, err:
676         logger.Error("Can't zero superblock for %s: %s" %
677                      (i.dev_path, str(err)))
678         return None
679     minor = cls._FindUnusedMinor()
680     result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
681                            "--auto=yes", "--force", "-l1",
682                            "-n%d" % len(children)] +
683                           [dev.dev_path for dev in children])
684
685     if result.failed:
686       logger.Error("Can't create md: %s: %s" % (result.fail_reason,
687                                                 result.output))
688       return None
689     info = cls._GetDevInfo(minor)
690     if not info or not "uuid" in info:
691       logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
692       return None
693     return MDRaid1(info["uuid"], children)
694
695   def Remove(self):
696     """Stub remove function for MD RAID 1 arrays.
697
698     We don't remove the superblock right now. Mark a to do.
699
700     """
701     #TODO: maybe zero superblock on child devices?
702     return self.Shutdown()
703
704   def Rename(self, new_id):
705     """Rename a device.
706
707     This is not supported for md raid1 devices.
708
709     """
710     raise errors.ProgrammerError("Can't rename a md raid1 device")
711
712   def AddChildren(self, devices):
713     """Add new member(s) to the md raid1.
714
715     """
716     if self.minor is None and not self.Attach():
717       raise errors.BlockDeviceError("Can't attach to device")
718
719     args = ["mdadm", "-a", self.dev_path]
720     for dev in devices:
721       if dev.dev_path is None:
722         raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
723       dev.Open()
724       args.append(dev.dev_path)
725     result = utils.RunCmd(args)
726     if result.failed:
727       raise errors.BlockDeviceError("Failed to add new device to array: %s" %
728                                     result.output)
729     new_len = len(self._children) + len(devices)
730     result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
731     if result.failed:
732       raise errors.BlockDeviceError("Can't grow md array: %s" %
733                                     result.output)
734     self._children.extend(devices)
735
736   def RemoveChildren(self, devices):
737     """Remove member(s) from the md raid1.
738
739     """
740     if self.minor is None and not self.Attach():
741       raise errors.BlockDeviceError("Can't attach to device")
742     new_len = len(self._children) - len(devices)
743     if new_len < 1:
744       raise errors.BlockDeviceError("Can't reduce to less than one child")
745     args = ["mdadm", "-f", self.dev_path]
746     orig_devs = []
747     for dev in devices:
748       args.append(dev)
749       for c in self._children:
750         if c.dev_path == dev:
751           orig_devs.append(c)
752           break
753       else:
754         raise errors.BlockDeviceError("Can't find device '%s' for removal" %
755                                       dev)
756     result = utils.RunCmd(args)
757     if result.failed:
758       raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
759                                     result.output)
760
761     # it seems here we need a short delay for MD to update its
762     # superblocks
763     time.sleep(0.5)
764     args[1] = "-r"
765     result = utils.RunCmd(args)
766     if result.failed:
767       raise errors.BlockDeviceError("Failed to remove device(s) from array:"
768                                     " %s" % result.output)
769     result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
770                            "-n", new_len])
771     if result.failed:
772       raise errors.BlockDeviceError("Can't shrink md array: %s" %
773                                     result.output)
774     for dev in orig_devs:
775       self._children.remove(dev)
776
777   def _SetFromMinor(self, minor):
778     """Set our parameters based on the given minor.
779
780     This sets our minor variable and our dev_path.
781
782     """
783     self.minor = minor
784     self.dev_path = "/dev/md%d" % minor
785
786   def Assemble(self):
787     """Assemble the MD device.
788
789     At this point we should have:
790       - list of children devices
791       - uuid
792
793     """
794     result = super(MDRaid1, self).Assemble()
795     if not result:
796       return result
797     md_list = self._GetUsedDevs()
798     for minor in md_list:
799       info = self._GetDevInfo(minor)
800       if info and info["uuid"] == self.unique_id:
801         self._SetFromMinor(minor)
802         logger.Info("MD array %s already started" % str(self))
803         return True
804     free_minor = self._FindUnusedMinor()
805     result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
806                            self.unique_id, "/dev/md%d" % free_minor] +
807                           [bdev.dev_path for bdev in self._children])
808     if result.failed:
809       logger.Error("Can't assemble MD array: %s: %s" %
810                    (result.fail_reason, result.output))
811       self.minor = None
812     else:
813       self.minor = free_minor
814     return not result.failed
815
816   def Shutdown(self):
817     """Tear down the MD array.
818
819     This does a 'mdadm --stop' so after this command, the array is no
820     longer available.
821
822     """
823     if self.minor is None and not self.Attach():
824       logger.Info("MD object not attached to a device")
825       return True
826
827     result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
828     if result.failed:
829       logger.Error("Can't stop MD array: %s" % result.fail_reason)
830       return False
831     self.minor = None
832     self.dev_path = None
833     return True
834
835   def SetSyncSpeed(self, kbytes):
836     """Set the maximum sync speed for the MD array.
837
838     """
839     result = super(MDRaid1, self).SetSyncSpeed(kbytes)
840     if self.minor is None:
841       logger.Error("MD array not attached to a device")
842       return False
843     f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
844     try:
845       f.write("%d" % kbytes)
846     finally:
847       f.close()
848     f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
849     try:
850       f.write("%d" % (kbytes/2))
851     finally:
852       f.close()
853     return result
854
855   def GetSyncStatus(self):
856     """Returns the sync status of the device.
857
858     Returns:
859      (sync_percent, estimated_time, is_degraded, ldisk)
860
861     If sync_percent is None, it means all is ok
862     If estimated_time is None, it means we can't esimate
863     the time needed, otherwise it's the time left in seconds.
864
865     The ldisk parameter is always true for MD devices.
866
867     """
868     if self.minor is None and not self.Attach():
869       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
870     dev_info = self._GetDevInfo(self.minor)
871     is_clean = ("state" in dev_info and
872                 len(dev_info["state"]) == 1 and
873                 dev_info["state"][0] in ("clean", "active"))
874     sys_path = "/sys/block/md%s/md/" % self.minor
875     f = file(sys_path + "sync_action")
876     sync_status = f.readline().strip()
877     f.close()
878     if sync_status == "idle":
879       return None, None, not is_clean, False
880     f = file(sys_path + "sync_completed")
881     sync_completed = f.readline().strip().split(" / ")
882     f.close()
883     if len(sync_completed) != 2:
884       return 0, None, not is_clean, False
885     sync_done, sync_total = [float(i) for i in sync_completed]
886     sync_percent = 100.0*sync_done/sync_total
887     f = file(sys_path + "sync_speed")
888     sync_speed_k = int(f.readline().strip())
889     if sync_speed_k == 0:
890       time_est = None
891     else:
892       time_est = (sync_total - sync_done) / 2 / sync_speed_k
893     return sync_percent, time_est, not is_clean, False
894
895   def Open(self, force=False):
896     """Make the device ready for I/O.
897
898     This is a no-op for the MDRaid1 device type, although we could use
899     the 2.6.18's new array_state thing.
900
901     """
902     pass
903
904   def Close(self):
905     """Notifies that the device will no longer be used for I/O.
906
907     This is a no-op for the MDRaid1 device type, but see comment for
908     `Open()`.
909
910     """
911     pass
912
913
914 class BaseDRBD(BlockDev):
915   """Base DRBD class.
916
917   This class contains a few bits of common functionality between the
918   0.7 and 8.x versions of DRBD.
919
920   """
921   _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
922                            r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
923
924   _DRBD_MAJOR = 147
925   _ST_UNCONFIGURED = "Unconfigured"
926   _ST_WFCONNECTION = "WFConnection"
927   _ST_CONNECTED = "Connected"
928
929   @staticmethod
930   def _GetProcData():
931     """Return data from /proc/drbd.
932
933     """
934     stat = open("/proc/drbd", "r")
935     try:
936       data = stat.read().splitlines()
937     finally:
938       stat.close()
939     if not data:
940       raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
941     return data
942
943   @staticmethod
944   def _MassageProcData(data):
945     """Transform the output of _GetProdData into a nicer form.
946
947     Returns:
948       a dictionary of minor: joined lines from /proc/drbd for that minor
949
950     """
951     lmatch = re.compile("^ *([0-9]+):.*$")
952     results = {}
953     old_minor = old_line = None
954     for line in data:
955       lresult = lmatch.match(line)
956       if lresult is not None:
957         if old_minor is not None:
958           results[old_minor] = old_line
959         old_minor = int(lresult.group(1))
960         old_line = line
961       else:
962         if old_minor is not None:
963           old_line += " " + line.strip()
964     # add last line
965     if old_minor is not None:
966       results[old_minor] = old_line
967     return results
968
969   @classmethod
970   def _GetVersion(cls):
971     """Return the DRBD version.
972
973     This will return a dict with keys:
974       k_major,
975       k_minor,
976       k_point,
977       api,
978       proto,
979       proto2 (only on drbd > 8.2.X)
980
981     """
982     proc_data = cls._GetProcData()
983     first_line = proc_data[0].strip()
984     version = cls._VERSION_RE.match(first_line)
985     if not version:
986       raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
987                                     first_line)
988
989     values = version.groups()
990     retval = {'k_major': int(values[0]),
991               'k_minor': int(values[1]),
992               'k_point': int(values[2]),
993               'api': int(values[3]),
994               'proto': int(values[4]),
995              }
996     if values[5] is not None:
997       retval['proto2'] = values[5]
998
999     return retval
1000
1001   @staticmethod
1002   def _DevPath(minor):
1003     """Return the path to a drbd device for a given minor.
1004
1005     """
1006     return "/dev/drbd%d" % minor
1007
1008   @classmethod
1009   def _GetUsedDevs(cls):
1010     """Compute the list of used DRBD devices.
1011
1012     """
1013     data = cls._GetProcData()
1014
1015     used_devs = {}
1016     valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1017     for line in data:
1018       match = valid_line.match(line)
1019       if not match:
1020         continue
1021       minor = int(match.group(1))
1022       state = match.group(2)
1023       if state == cls._ST_UNCONFIGURED:
1024         continue
1025       used_devs[minor] = state, line
1026
1027     return used_devs
1028
1029   def _SetFromMinor(self, minor):
1030     """Set our parameters based on the given minor.
1031
1032     This sets our minor variable and our dev_path.
1033
1034     """
1035     if minor is None:
1036       self.minor = self.dev_path = None
1037     else:
1038       self.minor = minor
1039       self.dev_path = self._DevPath(minor)
1040
1041   @staticmethod
1042   def _CheckMetaSize(meta_device):
1043     """Check if the given meta device looks like a valid one.
1044
1045     This currently only check the size, which must be around
1046     128MiB.
1047
1048     """
1049     result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1050     if result.failed:
1051       logger.Error("Failed to get device size: %s" % result.fail_reason)
1052       return False
1053     try:
1054       sectors = int(result.stdout)
1055     except ValueError:
1056       logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1057       return False
1058     bytes = sectors * 512
1059     if bytes < 128 * 1024 * 1024: # less than 128MiB
1060       logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1061       return False
1062     if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1063       logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1064       return False
1065     return True
1066
1067   def Rename(self, new_id):
1068     """Rename a device.
1069
1070     This is not supported for drbd devices.
1071
1072     """
1073     raise errors.ProgrammerError("Can't rename a drbd device")
1074
1075
1076 class DRBDev(BaseDRBD):
1077   """DRBD block device.
1078
1079   This implements the local host part of the DRBD device, i.e. it
1080   doesn't do anything to the supposed peer. If you need a fully
1081   connected DRBD pair, you need to use this class on both hosts.
1082
1083   The unique_id for the drbd device is the (local_ip, local_port,
1084   remote_ip, remote_port) tuple, and it must have two children: the
1085   data device and the meta_device. The meta device is checked for
1086   valid size and is zeroed on create.
1087
1088   """
1089   def __init__(self, unique_id, children):
1090     super(DRBDev, self).__init__(unique_id, children)
1091     self.major = self._DRBD_MAJOR
1092     version = self._GetVersion()
1093     if version['k_major'] != 0 and version['k_minor'] != 7:
1094       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1095                                     " requested ganeti usage: kernel is"
1096                                     " %s.%s, ganeti wants 0.7" %
1097                                     (version['k_major'], version['k_minor']))
1098     if len(children) != 2:
1099       raise ValueError("Invalid configuration data %s" % str(children))
1100     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1101       raise ValueError("Invalid configuration data %s" % str(unique_id))
1102     self._lhost, self._lport, self._rhost, self._rport = unique_id
1103     self.Attach()
1104
1105   @classmethod
1106   def _FindUnusedMinor(cls):
1107     """Find an unused DRBD device.
1108
1109     """
1110     data = cls._GetProcData()
1111
1112     valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1113     for line in data:
1114       match = valid_line.match(line)
1115       if match:
1116         return int(match.group(1))
1117     logger.Error("Error: no free drbd minors!")
1118     raise errors.BlockDeviceError("Can't find a free DRBD minor")
1119
1120   @classmethod
1121   def _GetDevInfo(cls, minor):
1122     """Get details about a given DRBD minor.
1123
1124     This return, if available, the local backing device in (major,
1125     minor) formant and the local and remote (ip, port) information.
1126
1127     """
1128     data = {}
1129     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1130     if result.failed:
1131       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1132       return data
1133     out = result.stdout
1134     if out == "Not configured\n":
1135       return data
1136     for line in out.splitlines():
1137       if "local_dev" not in data:
1138         match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1139         if match:
1140           data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1141           continue
1142       if "meta_dev" not in data:
1143         match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1144         if match:
1145           if match.group(2) is not None and match.group(3) is not None:
1146             # matched on the major/minor
1147             data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1148           else:
1149             # matched on the "internal" string
1150             data["meta_dev"] = match.group(1)
1151             # in this case, no meta_index is in the output
1152             data["meta_index"] = -1
1153           continue
1154       if "meta_index" not in data:
1155         match = re.match("^Meta index: ([0-9]+).*$", line)
1156         if match:
1157           data["meta_index"] = int(match.group(1))
1158           continue
1159       if "local_addr" not in data:
1160         match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1161         if match:
1162           data["local_addr"] = (match.group(1), int(match.group(2)))
1163           continue
1164       if "remote_addr" not in data:
1165         match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1166         if match:
1167           data["remote_addr"] = (match.group(1), int(match.group(2)))
1168           continue
1169     return data
1170
1171   def _MatchesLocal(self, info):
1172     """Test if our local config matches with an existing device.
1173
1174     The parameter should be as returned from `_GetDevInfo()`. This
1175     method tests if our local backing device is the same as the one in
1176     the info parameter, in effect testing if we look like the given
1177     device.
1178
1179     """
1180     if not ("local_dev" in info and "meta_dev" in info and
1181             "meta_index" in info):
1182       return False
1183
1184     backend = self._children[0]
1185     if backend is not None:
1186       retval = (info["local_dev"] == (backend.major, backend.minor))
1187     else:
1188       retval = (info["local_dev"] == (0, 0))
1189     meta = self._children[1]
1190     if meta is not None:
1191       retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1192       retval = retval and (info["meta_index"] == 0)
1193     else:
1194       retval = retval and (info["meta_dev"] == "internal" and
1195                            info["meta_index"] == -1)
1196     return retval
1197
1198   def _MatchesNet(self, info):
1199     """Test if our network config matches with an existing device.
1200
1201     The parameter should be as returned from `_GetDevInfo()`. This
1202     method tests if our network configuration is the same as the one
1203     in the info parameter, in effect testing if we look like the given
1204     device.
1205
1206     """
1207     if (((self._lhost is None and not ("local_addr" in info)) and
1208          (self._rhost is None and not ("remote_addr" in info)))):
1209       return True
1210
1211     if self._lhost is None:
1212       return False
1213
1214     if not ("local_addr" in info and
1215             "remote_addr" in info):
1216       return False
1217
1218     retval = (info["local_addr"] == (self._lhost, self._lport))
1219     retval = (retval and
1220               info["remote_addr"] == (self._rhost, self._rport))
1221     return retval
1222
1223   @classmethod
1224   def _AssembleLocal(cls, minor, backend, meta):
1225     """Configure the local part of a DRBD device.
1226
1227     This is the first thing that must be done on an unconfigured DRBD
1228     device. And it must be done only once.
1229
1230     """
1231     if not cls._CheckMetaSize(meta):
1232       return False
1233     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1234                            backend, meta, "0", "-e", "detach"])
1235     if result.failed:
1236       logger.Error("Can't attach local disk: %s" % result.output)
1237     return not result.failed
1238
1239   @classmethod
1240   def _ShutdownLocal(cls, minor):
1241     """Detach from the local device.
1242
1243     I/Os will continue to be served from the remote device. If we
1244     don't have a remote device, this operation will fail.
1245
1246     """
1247     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1248     if result.failed:
1249       logger.Error("Can't detach local device: %s" % result.output)
1250     return not result.failed
1251
1252   @staticmethod
1253   def _ShutdownAll(minor):
1254     """Deactivate the device.
1255
1256     This will, of course, fail if the device is in use.
1257
1258     """
1259     result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1260     if result.failed:
1261       logger.Error("Can't shutdown drbd device: %s" % result.output)
1262     return not result.failed
1263
1264   @classmethod
1265   def _AssembleNet(cls, minor, net_info, protocol):
1266     """Configure the network part of the device.
1267
1268     This operation can be, in theory, done multiple times, but there
1269     have been cases (in lab testing) in which the network part of the
1270     device had become stuck and couldn't be shut down because activity
1271     from the new peer (also stuck) triggered a timer re-init and
1272     needed remote peer interface shutdown in order to clear. So please
1273     don't change online the net config.
1274
1275     """
1276     lhost, lport, rhost, rport = net_info
1277     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1278                            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1279                            protocol])
1280     if result.failed:
1281       logger.Error("Can't setup network for dbrd device: %s" %
1282                    result.fail_reason)
1283       return False
1284
1285     timeout = time.time() + 10
1286     ok = False
1287     while time.time() < timeout:
1288       info = cls._GetDevInfo(minor)
1289       if not "local_addr" in info or not "remote_addr" in info:
1290         time.sleep(1)
1291         continue
1292       if (info["local_addr"] != (lhost, lport) or
1293           info["remote_addr"] != (rhost, rport)):
1294         time.sleep(1)
1295         continue
1296       ok = True
1297       break
1298     if not ok:
1299       logger.Error("Timeout while configuring network")
1300       return False
1301     return True
1302
1303   @classmethod
1304   def _ShutdownNet(cls, minor):
1305     """Disconnect from the remote peer.
1306
1307     This fails if we don't have a local device.
1308
1309     """
1310     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1311     if result.failed:
1312       logger.Error("Can't shutdown network: %s" % result.output)
1313     return not result.failed
1314
1315   def Assemble(self):
1316     """Assemble the drbd.
1317
1318     Method:
1319       - if we have a local backing device, we bind to it by:
1320         - checking the list of used drbd devices
1321         - check if the local minor use of any of them is our own device
1322         - if yes, abort?
1323         - if not, bind
1324       - if we have a local/remote net info:
1325         - redo the local backing device step for the remote device
1326         - check if any drbd device is using the local port,
1327           if yes abort
1328         - check if any remote drbd device is using the remote
1329           port, if yes abort (for now)
1330         - bind our net port
1331         - bind the remote net port
1332
1333     """
1334     self.Attach()
1335     if self.minor is not None:
1336       logger.Info("Already assembled")
1337       return True
1338
1339     result = super(DRBDev, self).Assemble()
1340     if not result:
1341       return result
1342
1343     minor = self._FindUnusedMinor()
1344     need_localdev_teardown = False
1345     if self._children[0]:
1346       result = self._AssembleLocal(minor, self._children[0].dev_path,
1347                                    self._children[1].dev_path)
1348       if not result:
1349         return False
1350       need_localdev_teardown = True
1351     if self._lhost and self._lport and self._rhost and self._rport:
1352       result = self._AssembleNet(minor,
1353                                  (self._lhost, self._lport,
1354                                   self._rhost, self._rport),
1355                                  "C")
1356       if not result:
1357         if need_localdev_teardown:
1358           # we will ignore failures from this
1359           logger.Error("net setup failed, tearing down local device")
1360           self._ShutdownAll(minor)
1361         return False
1362     self._SetFromMinor(minor)
1363     return True
1364
1365   def Shutdown(self):
1366     """Shutdown the DRBD device.
1367
1368     """
1369     if self.minor is None and not self.Attach():
1370       logger.Info("DRBD device not attached to a device during Shutdown")
1371       return True
1372     if not self._ShutdownAll(self.minor):
1373       return False
1374     self.minor = None
1375     self.dev_path = None
1376     return True
1377
1378   def Attach(self):
1379     """Find a DRBD device which matches our config and attach to it.
1380
1381     In case of partially attached (local device matches but no network
1382     setup), we perform the network attach. If successful, we re-test
1383     the attach if can return success.
1384
1385     """
1386     for minor in self._GetUsedDevs():
1387       info = self._GetDevInfo(minor)
1388       match_l = self._MatchesLocal(info)
1389       match_r = self._MatchesNet(info)
1390       if match_l and match_r:
1391         break
1392       if match_l and not match_r and "local_addr" not in info:
1393         res_r = self._AssembleNet(minor,
1394                                   (self._lhost, self._lport,
1395                                    self._rhost, self._rport),
1396                                   "C")
1397         if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1398           break
1399     else:
1400       minor = None
1401
1402     self._SetFromMinor(minor)
1403     return minor is not None
1404
1405   def Open(self, force=False):
1406     """Make the local state primary.
1407
1408     If the 'force' parameter is given, the '--do-what-I-say' parameter
1409     is given. Since this is a pottentialy dangerous operation, the
1410     force flag should be only given after creation, when it actually
1411     has to be given.
1412
1413     """
1414     if self.minor is None and not self.Attach():
1415       logger.Error("DRBD cannot attach to a device during open")
1416       return False
1417     cmd = ["drbdsetup", self.dev_path, "primary"]
1418     if force:
1419       cmd.append("--do-what-I-say")
1420     result = utils.RunCmd(cmd)
1421     if result.failed:
1422       msg = ("Can't make drbd device primary: %s" % result.output)
1423       logger.Error(msg)
1424       raise errors.BlockDeviceError(msg)
1425
1426   def Close(self):
1427     """Make the local state secondary.
1428
1429     This will, of course, fail if the device is in use.
1430
1431     """
1432     if self.minor is None and not self.Attach():
1433       logger.Info("Instance not attached to a device")
1434       raise errors.BlockDeviceError("Can't find device")
1435     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1436     if result.failed:
1437       msg = ("Can't switch drbd device to"
1438              " secondary: %s" % result.output)
1439       logger.Error(msg)
1440       raise errors.BlockDeviceError(msg)
1441
1442   def SetSyncSpeed(self, kbytes):
1443     """Set the speed of the DRBD syncer.
1444
1445     """
1446     children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1447     if self.minor is None:
1448       logger.Info("Instance not attached to a device")
1449       return False
1450     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1451                            kbytes])
1452     if result.failed:
1453       logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1454     return not result.failed and children_result
1455
1456   def GetSyncStatus(self):
1457     """Returns the sync status of the device.
1458
1459     Returns:
1460      (sync_percent, estimated_time, is_degraded, ldisk)
1461
1462     If sync_percent is None, it means all is ok
1463     If estimated_time is None, it means we can't esimate
1464     the time needed, otherwise it's the time left in seconds.
1465
1466     The ldisk parameter will be returned as True, since the DRBD7
1467     devices have not been converted.
1468
1469     """
1470     if self.minor is None and not self.Attach():
1471       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1472     proc_info = self._MassageProcData(self._GetProcData())
1473     if self.minor not in proc_info:
1474       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1475                                     self.minor)
1476     line = proc_info[self.minor]
1477     match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1478                      " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1479     if match:
1480       sync_percent = float(match.group(1))
1481       hours = int(match.group(2))
1482       minutes = int(match.group(3))
1483       seconds = int(match.group(4))
1484       est_time = hours * 3600 + minutes * 60 + seconds
1485     else:
1486       sync_percent = None
1487       est_time = None
1488     match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1489     if not match:
1490       raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1491                                     self.minor)
1492     client_state = match.group(1)
1493     is_degraded = client_state != "Connected"
1494     return sync_percent, est_time, is_degraded, False
1495
1496   @staticmethod
1497   def _ZeroDevice(device):
1498     """Zero a device.
1499
1500     This writes until we get ENOSPC.
1501
1502     """
1503     f = open(device, "w")
1504     buf = "\0" * 1048576
1505     try:
1506       while True:
1507         f.write(buf)
1508     except IOError, err:
1509       if err.errno != errno.ENOSPC:
1510         raise
1511
1512   @classmethod
1513   def Create(cls, unique_id, children, size):
1514     """Create a new DRBD device.
1515
1516     Since DRBD devices are not created per se, just assembled, this
1517     function just zeroes the meta device.
1518
1519     """
1520     if len(children) != 2:
1521       raise errors.ProgrammerError("Invalid setup for the drbd device")
1522     meta = children[1]
1523     meta.Assemble()
1524     if not meta.Attach():
1525       raise errors.BlockDeviceError("Can't attach to meta device")
1526     if not cls._CheckMetaSize(meta.dev_path):
1527       raise errors.BlockDeviceError("Invalid meta device")
1528     logger.Info("Started zeroing device %s" % meta.dev_path)
1529     cls._ZeroDevice(meta.dev_path)
1530     logger.Info("Done zeroing device %s" % meta.dev_path)
1531     return cls(unique_id, children)
1532
1533   def Remove(self):
1534     """Stub remove for DRBD devices.
1535
1536     """
1537     return self.Shutdown()
1538
1539
1540 class DRBD8(BaseDRBD):
1541   """DRBD v8.x block device.
1542
1543   This implements the local host part of the DRBD device, i.e. it
1544   doesn't do anything to the supposed peer. If you need a fully
1545   connected DRBD pair, you need to use this class on both hosts.
1546
1547   The unique_id for the drbd device is the (local_ip, local_port,
1548   remote_ip, remote_port) tuple, and it must have two children: the
1549   data device and the meta_device. The meta device is checked for
1550   valid size and is zeroed on create.
1551
1552   """
1553   _MAX_MINORS = 255
1554   _PARSE_SHOW = None
1555
1556   def __init__(self, unique_id, children):
1557     if children and children.count(None) > 0:
1558       children = []
1559     super(DRBD8, self).__init__(unique_id, children)
1560     self.major = self._DRBD_MAJOR
1561     version = self._GetVersion()
1562     if version['k_major'] != 8 :
1563       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1564                                     " requested ganeti usage: kernel is"
1565                                     " %s.%s, ganeti wants 8.x" %
1566                                     (version['k_major'], version['k_minor']))
1567
1568     if len(children) not in (0, 2):
1569       raise ValueError("Invalid configuration data %s" % str(children))
1570     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1571       raise ValueError("Invalid configuration data %s" % str(unique_id))
1572     self._lhost, self._lport, self._rhost, self._rport = unique_id
1573     self.Attach()
1574
1575   @classmethod
1576   def _InitMeta(cls, minor, dev_path):
1577     """Initialize a meta device.
1578
1579     This will not work if the given minor is in use.
1580
1581     """
1582     result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1583                            "v08", dev_path, "0", "create-md"])
1584     if result.failed:
1585       raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1586                                     result.output)
1587
1588   @classmethod
1589   def _FindUnusedMinor(cls):
1590     """Find an unused DRBD device.
1591
1592     This is specific to 8.x as the minors are allocated dynamically,
1593     so non-existing numbers up to a max minor count are actually free.
1594
1595     """
1596     data = cls._GetProcData()
1597
1598     unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1599     used_line = re.compile("^ *([0-9]+): cs:")
1600     highest = None
1601     for line in data:
1602       match = unused_line.match(line)
1603       if match:
1604         return int(match.group(1))
1605       match = used_line.match(line)
1606       if match:
1607         minor = int(match.group(1))
1608         highest = max(highest, minor)
1609     if highest is None: # there are no minors in use at all
1610       return 0
1611     if highest >= cls._MAX_MINORS:
1612       logger.Error("Error: no free drbd minors!")
1613       raise errors.BlockDeviceError("Can't find a free DRBD minor")
1614     return highest + 1
1615
1616   @classmethod
1617   def _IsValidMeta(cls, meta_device):
1618     """Check if the given meta device looks like a valid one.
1619
1620     """
1621     minor = cls._FindUnusedMinor()
1622     minor_path = cls._DevPath(minor)
1623     result = utils.RunCmd(["drbdmeta", minor_path,
1624                            "v08", meta_device, "0",
1625                            "dstate"])
1626     if result.failed:
1627       logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1628       return False
1629     return True
1630
1631   @classmethod
1632   def _GetShowParser(cls):
1633     """Return a parser for `drbd show` output.
1634
1635     This will either create or return an already-create parser for the
1636     output of the command `drbd show`.
1637
1638     """
1639     if cls._PARSE_SHOW is not None:
1640       return cls._PARSE_SHOW
1641
1642     # pyparsing setup
1643     lbrace = pyp.Literal("{").suppress()
1644     rbrace = pyp.Literal("}").suppress()
1645     semi = pyp.Literal(";").suppress()
1646     # this also converts the value to an int
1647     number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1648
1649     comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1650     defa = pyp.Literal("_is_default").suppress()
1651     dbl_quote = pyp.Literal('"').suppress()
1652
1653     keyword = pyp.Word(pyp.alphanums + '-')
1654
1655     # value types
1656     value = pyp.Word(pyp.alphanums + '_-/.:')
1657     quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1658     addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1659                  number)
1660     # meta device, extended syntax
1661     meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1662                   number + pyp.Word(']').suppress())
1663
1664     # a statement
1665     stmt = (~rbrace + keyword + ~lbrace +
1666             (addr_port ^ value ^ quoted ^ meta_value) +
1667             pyp.Optional(defa) + semi +
1668             pyp.Optional(pyp.restOfLine).suppress())
1669
1670     # an entire section
1671     section_name = pyp.Word(pyp.alphas + '_')
1672     section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1673
1674     bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1675     bnf.ignore(comment)
1676
1677     cls._PARSE_SHOW = bnf
1678
1679     return bnf
1680
1681   @classmethod
1682   def _GetShowData(cls, minor):
1683     """Return the `drbdsetup show` data for a minor.
1684
1685     """
1686     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1687     if result.failed:
1688       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1689       return None
1690     return result.stdout
1691
1692   @classmethod
1693   def _GetDevInfo(cls, out):
1694     """Parse details about a given DRBD minor.
1695
1696     This return, if available, the local backing device (as a path)
1697     and the local and remote (ip, port) information from a string
1698     containing the output of the `drbdsetup show` command as returned
1699     by _GetShowData.
1700
1701     """
1702     data = {}
1703     if not out:
1704       return data
1705
1706     bnf = cls._GetShowParser()
1707     # run pyparse
1708
1709     try:
1710       results = bnf.parseString(out)
1711     except pyp.ParseException, err:
1712       raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1713                                     str(err))
1714
1715     # and massage the results into our desired format
1716     for section in results:
1717       sname = section[0]
1718       if sname == "_this_host":
1719         for lst in section[1:]:
1720           if lst[0] == "disk":
1721             data["local_dev"] = lst[1]
1722           elif lst[0] == "meta-disk":
1723             data["meta_dev"] = lst[1]
1724             data["meta_index"] = lst[2]
1725           elif lst[0] == "address":
1726             data["local_addr"] = tuple(lst[1:])
1727       elif sname == "_remote_host":
1728         for lst in section[1:]:
1729           if lst[0] == "address":
1730             data["remote_addr"] = tuple(lst[1:])
1731     return data
1732
1733   def _MatchesLocal(self, info):
1734     """Test if our local config matches with an existing device.
1735
1736     The parameter should be as returned from `_GetDevInfo()`. This
1737     method tests if our local backing device is the same as the one in
1738     the info parameter, in effect testing if we look like the given
1739     device.
1740
1741     """
1742     if self._children:
1743       backend, meta = self._children
1744     else:
1745       backend = meta = None
1746
1747     if backend is not None:
1748       retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1749     else:
1750       retval = ("local_dev" not in info)
1751
1752     if meta is not None:
1753       retval = retval and ("meta_dev" in info and
1754                            info["meta_dev"] == meta.dev_path)
1755       retval = retval and ("meta_index" in info and
1756                            info["meta_index"] == 0)
1757     else:
1758       retval = retval and ("meta_dev" not in info and
1759                            "meta_index" not in info)
1760     return retval
1761
1762   def _MatchesNet(self, info):
1763     """Test if our network config matches with an existing device.
1764
1765     The parameter should be as returned from `_GetDevInfo()`. This
1766     method tests if our network configuration is the same as the one
1767     in the info parameter, in effect testing if we look like the given
1768     device.
1769
1770     """
1771     if (((self._lhost is None and not ("local_addr" in info)) and
1772          (self._rhost is None and not ("remote_addr" in info)))):
1773       return True
1774
1775     if self._lhost is None:
1776       return False
1777
1778     if not ("local_addr" in info and
1779             "remote_addr" in info):
1780       return False
1781
1782     retval = (info["local_addr"] == (self._lhost, self._lport))
1783     retval = (retval and
1784               info["remote_addr"] == (self._rhost, self._rport))
1785     return retval
1786
1787   @classmethod
1788   def _AssembleLocal(cls, minor, backend, meta):
1789     """Configure the local part of a DRBD device.
1790
1791     This is the first thing that must be done on an unconfigured DRBD
1792     device. And it must be done only once.
1793
1794     """
1795     if not cls._IsValidMeta(meta):
1796       return False
1797     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1798                            backend, meta, "0", "-e", "detach",
1799                            "--create-device"])
1800     if result.failed:
1801       logger.Error("Can't attach local disk: %s" % result.output)
1802     return not result.failed
1803
1804   @classmethod
1805   def _AssembleNet(cls, minor, net_info, protocol,
1806                    dual_pri=False, hmac=None, secret=None):
1807     """Configure the network part of the device.
1808
1809     """
1810     lhost, lport, rhost, rport = net_info
1811     if None in net_info:
1812       # we don't want network connection and actually want to make
1813       # sure its shutdown
1814       return cls._ShutdownNet(minor)
1815
1816     args = ["drbdsetup", cls._DevPath(minor), "net",
1817             "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1818             "-A", "discard-zero-changes",
1819             "-B", "consensus",
1820             "--create-device",
1821             ]
1822     if dual_pri:
1823       args.append("-m")
1824     if hmac and secret:
1825       args.extend(["-a", hmac, "-x", secret])
1826     result = utils.RunCmd(args)
1827     if result.failed:
1828       logger.Error("Can't setup network for dbrd device: %s" %
1829                    result.fail_reason)
1830       return False
1831
1832     timeout = time.time() + 10
1833     ok = False
1834     while time.time() < timeout:
1835       info = cls._GetDevInfo(cls._GetShowData(minor))
1836       if not "local_addr" in info or not "remote_addr" in info:
1837         time.sleep(1)
1838         continue
1839       if (info["local_addr"] != (lhost, lport) or
1840           info["remote_addr"] != (rhost, rport)):
1841         time.sleep(1)
1842         continue
1843       ok = True
1844       break
1845     if not ok:
1846       logger.Error("Timeout while configuring network")
1847       return False
1848     return True
1849
1850   def AddChildren(self, devices):
1851     """Add a disk to the DRBD device.
1852
1853     """
1854     if self.minor is None:
1855       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1856     if len(devices) != 2:
1857       raise errors.BlockDeviceError("Need two devices for AddChildren")
1858     info = self._GetDevInfo(self._GetShowData(self.minor))
1859     if "local_dev" in info:
1860       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1861     backend, meta = devices
1862     if backend.dev_path is None or meta.dev_path is None:
1863       raise errors.BlockDeviceError("Children not ready during AddChildren")
1864     backend.Open()
1865     meta.Open()
1866     if not self._CheckMetaSize(meta.dev_path):
1867       raise errors.BlockDeviceError("Invalid meta device size")
1868     self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1869     if not self._IsValidMeta(meta.dev_path):
1870       raise errors.BlockDeviceError("Cannot initalize meta device")
1871
1872     if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1873       raise errors.BlockDeviceError("Can't attach to local storage")
1874     self._children = devices
1875
1876   def RemoveChildren(self, devices):
1877     """Detach the drbd device from local storage.
1878
1879     """
1880     if self.minor is None:
1881       raise errors.BlockDeviceError("Can't attach to drbd8 during"
1882                                     " RemoveChildren")
1883     # early return if we don't actually have backing storage
1884     info = self._GetDevInfo(self._GetShowData(self.minor))
1885     if "local_dev" not in info:
1886       return
1887     if len(self._children) != 2:
1888       raise errors.BlockDeviceError("We don't have two children: %s" %
1889                                     self._children)
1890     if self._children.count(None) == 2: # we don't actually have children :)
1891       logger.Error("Requested detach while detached")
1892       return
1893     if len(devices) != 2:
1894       raise errors.BlockDeviceError("We need two children in RemoveChildren")
1895     for child, dev in zip(self._children, devices):
1896       if dev != child.dev_path:
1897         raise errors.BlockDeviceError("Mismatch in local storage"
1898                                       " (%s != %s) in RemoveChildren" %
1899                                       (dev, child.dev_path))
1900
1901     if not self._ShutdownLocal(self.minor):
1902       raise errors.BlockDeviceError("Can't detach from local storage")
1903     self._children = []
1904
1905   def SetSyncSpeed(self, kbytes):
1906     """Set the speed of the DRBD syncer.
1907
1908     """
1909     children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1910     if self.minor is None:
1911       logger.Info("Instance not attached to a device")
1912       return False
1913     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1914                            kbytes])
1915     if result.failed:
1916       logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1917     return not result.failed and children_result
1918
1919   def GetSyncStatus(self):
1920     """Returns the sync status of the device.
1921
1922     Returns:
1923      (sync_percent, estimated_time, is_degraded)
1924
1925     If sync_percent is None, it means all is ok
1926     If estimated_time is None, it means we can't esimate
1927     the time needed, otherwise it's the time left in seconds.
1928
1929
1930     We set the is_degraded parameter to True on two conditions:
1931     network not connected or local disk missing.
1932
1933     We compute the ldisk parameter based on wheter we have a local
1934     disk or not.
1935
1936     """
1937     if self.minor is None and not self.Attach():
1938       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1939     proc_info = self._MassageProcData(self._GetProcData())
1940     if self.minor not in proc_info:
1941       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1942                                     self.minor)
1943     line = proc_info[self.minor]
1944     match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1945                      " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1946     if match:
1947       sync_percent = float(match.group(1))
1948       hours = int(match.group(2))
1949       minutes = int(match.group(3))
1950       seconds = int(match.group(4))
1951       est_time = hours * 3600 + minutes * 60 + seconds
1952     else:
1953       sync_percent = None
1954       est_time = None
1955     match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1956     if not match:
1957       raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1958                                     self.minor)
1959     client_state = match.group(1)
1960     local_disk_state = match.group(2)
1961     ldisk = local_disk_state != "UpToDate"
1962     is_degraded = client_state != "Connected"
1963     return sync_percent, est_time, is_degraded or ldisk, ldisk
1964
1965   def Open(self, force=False):
1966     """Make the local state primary.
1967
1968     If the 'force' parameter is given, the '-o' option is passed to
1969     drbdsetup. Since this is a potentially dangerous operation, the
1970     force flag should be only given after creation, when it actually
1971     is mandatory.
1972
1973     """
1974     if self.minor is None and not self.Attach():
1975       logger.Error("DRBD cannot attach to a device during open")
1976       return False
1977     cmd = ["drbdsetup", self.dev_path, "primary"]
1978     if force:
1979       cmd.append("-o")
1980     result = utils.RunCmd(cmd)
1981     if result.failed:
1982       msg = ("Can't make drbd device primary: %s" % result.output)
1983       logger.Error(msg)
1984       raise errors.BlockDeviceError(msg)
1985
1986   def Close(self):
1987     """Make the local state secondary.
1988
1989     This will, of course, fail if the device is in use.
1990
1991     """
1992     if self.minor is None and not self.Attach():
1993       logger.Info("Instance not attached to a device")
1994       raise errors.BlockDeviceError("Can't find device")
1995     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1996     if result.failed:
1997       msg = ("Can't switch drbd device to"
1998              " secondary: %s" % result.output)
1999       logger.Error(msg)
2000       raise errors.BlockDeviceError(msg)
2001
2002   def Attach(self):
2003     """Find a DRBD device which matches our config and attach to it.
2004
2005     In case of partially attached (local device matches but no network
2006     setup), we perform the network attach. If successful, we re-test
2007     the attach if can return success.
2008
2009     """
2010     for minor in self._GetUsedDevs():
2011       info = self._GetDevInfo(self._GetShowData(minor))
2012       match_l = self._MatchesLocal(info)
2013       match_r = self._MatchesNet(info)
2014       if match_l and match_r:
2015         break
2016       if match_l and not match_r and "local_addr" not in info:
2017         res_r = self._AssembleNet(minor,
2018                                   (self._lhost, self._lport,
2019                                    self._rhost, self._rport),
2020                                   "C")
2021         if res_r:
2022           if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2023             break
2024       # the weakest case: we find something that is only net attached
2025       # even though we were passed some children at init time
2026       if match_r and "local_dev" not in info:
2027         break
2028
2029       # this case must be considered only if we actually have local
2030       # storage, i.e. not in diskless mode, because all diskless
2031       # devices are equal from the point of view of local
2032       # configuration
2033       if (match_l and "local_dev" in info and
2034           not match_r and "local_addr" in info):
2035         # strange case - the device network part points to somewhere
2036         # else, even though its local storage is ours; as we own the
2037         # drbd space, we try to disconnect from the remote peer and
2038         # reconnect to our correct one
2039         if not self._ShutdownNet(minor):
2040           raise errors.BlockDeviceError("Device has correct local storage,"
2041                                         " wrong remote peer and is unable to"
2042                                         " disconnect in order to attach to"
2043                                         " the correct peer")
2044         # note: _AssembleNet also handles the case when we don't want
2045         # local storage (i.e. one or more of the _[lr](host|port) is
2046         # None)
2047         if (self._AssembleNet(minor, (self._lhost, self._lport,
2048                                       self._rhost, self._rport), "C") and
2049             self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2050           break
2051
2052     else:
2053       minor = None
2054
2055     self._SetFromMinor(minor)
2056     return minor is not None
2057
2058   def Assemble(self):
2059     """Assemble the drbd.
2060
2061     Method:
2062       - if we have a local backing device, we bind to it by:
2063         - checking the list of used drbd devices
2064         - check if the local minor use of any of them is our own device
2065         - if yes, abort?
2066         - if not, bind
2067       - if we have a local/remote net info:
2068         - redo the local backing device step for the remote device
2069         - check if any drbd device is using the local port,
2070           if yes abort
2071         - check if any remote drbd device is using the remote
2072           port, if yes abort (for now)
2073         - bind our net port
2074         - bind the remote net port
2075
2076     """
2077     self.Attach()
2078     if self.minor is not None:
2079       logger.Info("Already assembled")
2080       return True
2081
2082     result = super(DRBD8, self).Assemble()
2083     if not result:
2084       return result
2085
2086     minor = self._FindUnusedMinor()
2087     need_localdev_teardown = False
2088     if self._children and self._children[0] and self._children[1]:
2089       result = self._AssembleLocal(minor, self._children[0].dev_path,
2090                                    self._children[1].dev_path)
2091       if not result:
2092         return False
2093       need_localdev_teardown = True
2094     if self._lhost and self._lport and self._rhost and self._rport:
2095       result = self._AssembleNet(minor,
2096                                  (self._lhost, self._lport,
2097                                   self._rhost, self._rport),
2098                                  "C")
2099       if not result:
2100         if need_localdev_teardown:
2101           # we will ignore failures from this
2102           logger.Error("net setup failed, tearing down local device")
2103           self._ShutdownAll(minor)
2104         return False
2105     self._SetFromMinor(minor)
2106     return True
2107
2108   @classmethod
2109   def _ShutdownLocal(cls, minor):
2110     """Detach from the local device.
2111
2112     I/Os will continue to be served from the remote device. If we
2113     don't have a remote device, this operation will fail.
2114
2115     """
2116     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2117     if result.failed:
2118       logger.Error("Can't detach local device: %s" % result.output)
2119     return not result.failed
2120
2121   @classmethod
2122   def _ShutdownNet(cls, minor):
2123     """Disconnect from the remote peer.
2124
2125     This fails if we don't have a local device.
2126
2127     """
2128     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2129     if result.failed:
2130       logger.Error("Can't shutdown network: %s" % result.output)
2131     return not result.failed
2132
2133   @classmethod
2134   def _ShutdownAll(cls, minor):
2135     """Deactivate the device.
2136
2137     This will, of course, fail if the device is in use.
2138
2139     """
2140     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2141     if result.failed:
2142       logger.Error("Can't shutdown drbd device: %s" % result.output)
2143     return not result.failed
2144
2145   def Shutdown(self):
2146     """Shutdown the DRBD device.
2147
2148     """
2149     if self.minor is None and not self.Attach():
2150       logger.Info("DRBD device not attached to a device during Shutdown")
2151       return True
2152     if not self._ShutdownAll(self.minor):
2153       return False
2154     self.minor = None
2155     self.dev_path = None
2156     return True
2157
2158   def Remove(self):
2159     """Stub remove for DRBD devices.
2160
2161     """
2162     return self.Shutdown()
2163
2164   @classmethod
2165   def Create(cls, unique_id, children, size):
2166     """Create a new DRBD8 device.
2167
2168     Since DRBD devices are not created per se, just assembled, this
2169     function only initializes the metadata.
2170
2171     """
2172     if len(children) != 2:
2173       raise errors.ProgrammerError("Invalid setup for the drbd device")
2174     meta = children[1]
2175     meta.Assemble()
2176     if not meta.Attach():
2177       raise errors.BlockDeviceError("Can't attach to meta device")
2178     if not cls._CheckMetaSize(meta.dev_path):
2179       raise errors.BlockDeviceError("Invalid meta device size")
2180     cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2181     if not cls._IsValidMeta(meta.dev_path):
2182       raise errors.BlockDeviceError("Cannot initalize meta device")
2183     return cls(unique_id, children)
2184
2185
2186 class FileStorage(BlockDev):
2187   """File device.
2188   
2189   This class represents the a file storage backend device.
2190
2191   The unique_id for the file device is a (file_driver, file_path) tuple.
2192   
2193   """
2194   def __init__(self, unique_id, children):
2195     """Initalizes a file device backend.
2196
2197     """
2198     if children:
2199       raise errors.BlockDeviceError("Invalid setup for file device")
2200     super(FileStorage, self).__init__(unique_id, children)
2201     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2202       raise ValueError("Invalid configuration data %s" % str(unique_id))
2203     self.driver = unique_id[0]
2204     self.dev_path = unique_id[1]
2205
2206   def Assemble(self):
2207     """Assemble the device.
2208
2209     Checks whether the file device exists, raises BlockDeviceError otherwise.
2210
2211     """
2212     if not os.path.exists(self.dev_path):
2213       raise errors.BlockDeviceError("File device '%s' does not exist." %
2214                                     self.dev_path)
2215     return True
2216
2217   def Shutdown(self):
2218     """Shutdown the device.
2219
2220     This is a no-op for the file type, as we don't deacivate
2221     the file on shutdown.
2222
2223     """
2224     return True
2225
2226   def Open(self, force=False):
2227     """Make the device ready for I/O.
2228
2229     This is a no-op for the file type.
2230
2231     """
2232     pass
2233
2234   def Close(self):
2235     """Notifies that the device will no longer be used for I/O.
2236
2237     This is a no-op for the file type.
2238
2239     """
2240     pass
2241
2242   def Remove(self):
2243     """Remove the file backing the block device.
2244
2245     Returns:
2246       boolean indicating wheter removal of file was successful or not.
2247
2248     """
2249     if not os.path.exists(self.dev_path):
2250       return True
2251     try:
2252       os.remove(self.dev_path)
2253       return True
2254     except OSError, err:
2255       logger.Error("Can't remove file '%s': %s"
2256                    % (self.dev_path, err))
2257       return False
2258
2259   def Attach(self):
2260     """Attach to an existing file.
2261
2262     Check if this file already exists.
2263
2264     Returns:
2265       boolean indicating if file exists or not.
2266
2267     """
2268     if os.path.exists(self.dev_path):
2269       return True
2270     return False
2271
2272   @classmethod
2273   def Create(cls, unique_id, children, size):
2274     """Create a new file.
2275
2276     Args:
2277       children:
2278       size: integer size of file in MiB
2279
2280     Returns:
2281       A ganeti.bdev.FileStorage object.
2282
2283     """
2284     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2285       raise ValueError("Invalid configuration data %s" % str(unique_id))
2286     dev_path = unique_id[1]
2287     try:
2288       f = open(dev_path, 'w')
2289     except IOError, err:
2290       raise errors.BlockDeviceError("Could not create '%'" % err)
2291     else:
2292       f.truncate(size * 1024 * 1024)
2293       f.close()
2294
2295     return FileStorage(unique_id, children)
2296
2297
2298 DEV_MAP = {
2299   constants.LD_LV: LogicalVolume,
2300   constants.LD_MD_R1: MDRaid1,
2301   constants.LD_DRBD7: DRBDev,
2302   constants.LD_DRBD8: DRBD8,
2303   constants.LD_FILE: FileStorage,
2304   }
2305
2306
2307 def FindDevice(dev_type, unique_id, children):
2308   """Search for an existing, assembled device.
2309
2310   This will succeed only if the device exists and is assembled, but it
2311   does not do any actions in order to activate the device.
2312
2313   """
2314   if dev_type not in DEV_MAP:
2315     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2316   device = DEV_MAP[dev_type](unique_id, children)
2317   if not device.Attach():
2318     return None
2319   return  device
2320
2321
2322 def AttachOrAssemble(dev_type, unique_id, children):
2323   """Try to attach or assemble an existing device.
2324
2325   This will attach to an existing assembled device or will assemble
2326   the device, as needed, to bring it fully up.
2327
2328   """
2329   if dev_type not in DEV_MAP:
2330     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2331   device = DEV_MAP[dev_type](unique_id, children)
2332   if not device.Attach():
2333     device.Assemble()
2334     if not device.Attach():
2335       raise errors.BlockDeviceError("Can't find a valid block device for"
2336                                     " %s/%s/%s" %
2337                                     (dev_type, unique_id, children))
2338   return device
2339
2340
2341 def Create(dev_type, unique_id, children, size):
2342   """Create a device.
2343
2344   """
2345   if dev_type not in DEV_MAP:
2346     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2347   device = DEV_MAP[dev_type].Create(unique_id, children, size)
2348   return device