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