Fix two pylint uninitialized variable errors
[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             ]
1821     if dual_pri:
1822       args.append("-m")
1823     if hmac and secret:
1824       args.extend(["-a", hmac, "-x", secret])
1825     result = utils.RunCmd(args)
1826     if result.failed:
1827       logger.Error("Can't setup network for dbrd device: %s" %
1828                    result.fail_reason)
1829       return False
1830
1831     timeout = time.time() + 10
1832     ok = False
1833     while time.time() < timeout:
1834       info = cls._GetDevInfo(cls._GetShowData(minor))
1835       if not "local_addr" in info or not "remote_addr" in info:
1836         time.sleep(1)
1837         continue
1838       if (info["local_addr"] != (lhost, lport) or
1839           info["remote_addr"] != (rhost, rport)):
1840         time.sleep(1)
1841         continue
1842       ok = True
1843       break
1844     if not ok:
1845       logger.Error("Timeout while configuring network")
1846       return False
1847     return True
1848
1849   def AddChildren(self, devices):
1850     """Add a disk to the DRBD device.
1851
1852     """
1853     if self.minor is None:
1854       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1855     if len(devices) != 2:
1856       raise errors.BlockDeviceError("Need two devices for AddChildren")
1857     info = self._GetDevInfo(self._GetShowData(self.minor))
1858     if "local_dev" in info:
1859       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1860     backend, meta = devices
1861     if backend.dev_path is None or meta.dev_path is None:
1862       raise errors.BlockDeviceError("Children not ready during AddChildren")
1863     backend.Open()
1864     meta.Open()
1865     if not self._CheckMetaSize(meta.dev_path):
1866       raise errors.BlockDeviceError("Invalid meta device size")
1867     self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1868     if not self._IsValidMeta(meta.dev_path):
1869       raise errors.BlockDeviceError("Cannot initalize meta device")
1870
1871     if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1872       raise errors.BlockDeviceError("Can't attach to local storage")
1873     self._children = devices
1874
1875   def RemoveChildren(self, devices):
1876     """Detach the drbd device from local storage.
1877
1878     """
1879     if self.minor is None:
1880       raise errors.BlockDeviceError("Can't attach to drbd8 during"
1881                                     " RemoveChildren")
1882     # early return if we don't actually have backing storage
1883     info = self._GetDevInfo(self._GetShowData(self.minor))
1884     if "local_dev" not in info:
1885       return
1886     if len(self._children) != 2:
1887       raise errors.BlockDeviceError("We don't have two children: %s" %
1888                                     self._children)
1889     if self._children.count(None) == 2: # we don't actually have children :)
1890       logger.Error("Requested detach while detached")
1891       return
1892     if len(devices) != 2:
1893       raise errors.BlockDeviceError("We need two children in RemoveChildren")
1894     for child, dev in zip(self._children, devices):
1895       if dev != child.dev_path:
1896         raise errors.BlockDeviceError("Mismatch in local storage"
1897                                       " (%s != %s) in RemoveChildren" %
1898                                       (dev, child.dev_path))
1899
1900     if not self._ShutdownLocal(self.minor):
1901       raise errors.BlockDeviceError("Can't detach from local storage")
1902     self._children = []
1903
1904   def SetSyncSpeed(self, kbytes):
1905     """Set the speed of the DRBD syncer.
1906
1907     """
1908     children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1909     if self.minor is None:
1910       logger.Info("Instance not attached to a device")
1911       return False
1912     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1913                            kbytes])
1914     if result.failed:
1915       logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1916     return not result.failed and children_result
1917
1918   def GetSyncStatus(self):
1919     """Returns the sync status of the device.
1920
1921     Returns:
1922      (sync_percent, estimated_time, is_degraded)
1923
1924     If sync_percent is None, it means all is ok
1925     If estimated_time is None, it means we can't esimate
1926     the time needed, otherwise it's the time left in seconds.
1927
1928
1929     We set the is_degraded parameter to True on two conditions:
1930     network not connected or local disk missing.
1931
1932     We compute the ldisk parameter based on wheter we have a local
1933     disk or not.
1934
1935     """
1936     if self.minor is None and not self.Attach():
1937       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1938     proc_info = self._MassageProcData(self._GetProcData())
1939     if self.minor not in proc_info:
1940       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1941                                     self.minor)
1942     line = proc_info[self.minor]
1943     match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1944                      " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1945     if match:
1946       sync_percent = float(match.group(1))
1947       hours = int(match.group(2))
1948       minutes = int(match.group(3))
1949       seconds = int(match.group(4))
1950       est_time = hours * 3600 + minutes * 60 + seconds
1951     else:
1952       sync_percent = None
1953       est_time = None
1954     match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1955     if not match:
1956       raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1957                                     self.minor)
1958     client_state = match.group(1)
1959     local_disk_state = match.group(2)
1960     ldisk = local_disk_state != "UpToDate"
1961     is_degraded = client_state != "Connected"
1962     return sync_percent, est_time, is_degraded or ldisk, ldisk
1963
1964   def Open(self, force=False):
1965     """Make the local state primary.
1966
1967     If the 'force' parameter is given, the '--do-what-I-say' parameter
1968     is given. Since this is a pottentialy dangerous operation, the
1969     force flag should be only given after creation, when it actually
1970     has to be given.
1971
1972     """
1973     if self.minor is None and not self.Attach():
1974       logger.Error("DRBD cannot attach to a device during open")
1975       return False
1976     cmd = ["drbdsetup", self.dev_path, "primary"]
1977     if force:
1978       cmd.append("-o")
1979     result = utils.RunCmd(cmd)
1980     if result.failed:
1981       msg = ("Can't make drbd device primary: %s" % result.output)
1982       logger.Error(msg)
1983       raise errors.BlockDeviceError(msg)
1984
1985   def Close(self):
1986     """Make the local state secondary.
1987
1988     This will, of course, fail if the device is in use.
1989
1990     """
1991     if self.minor is None and not self.Attach():
1992       logger.Info("Instance not attached to a device")
1993       raise errors.BlockDeviceError("Can't find device")
1994     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1995     if result.failed:
1996       msg = ("Can't switch drbd device to"
1997              " secondary: %s" % result.output)
1998       logger.Error(msg)
1999       raise errors.BlockDeviceError(msg)
2000
2001   def Attach(self):
2002     """Find a DRBD device which matches our config and attach to it.
2003
2004     In case of partially attached (local device matches but no network
2005     setup), we perform the network attach. If successful, we re-test
2006     the attach if can return success.
2007
2008     """
2009     for minor in self._GetUsedDevs():
2010       info = self._GetDevInfo(self._GetShowData(minor))
2011       match_l = self._MatchesLocal(info)
2012       match_r = self._MatchesNet(info)
2013       if match_l and match_r:
2014         break
2015       if match_l and not match_r and "local_addr" not in info:
2016         res_r = self._AssembleNet(minor,
2017                                   (self._lhost, self._lport,
2018                                    self._rhost, self._rport),
2019                                   "C")
2020         if res_r:
2021           if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2022             break
2023       # the weakest case: we find something that is only net attached
2024       # even though we were passed some children at init time
2025       if match_r and "local_dev" not in info:
2026         break
2027       if match_l and not match_r and "local_addr" in info:
2028         # strange case - the device network part points to somewhere
2029         # else, even though its local storage is ours; as we own the
2030         # drbd space, we try to disconnect from the remote peer and
2031         # reconnect to our correct one
2032         if not self._ShutdownNet(minor):
2033           raise errors.BlockDeviceError("Device has correct local storage,"
2034                                         " wrong remote peer and is unable to"
2035                                         " disconnect in order to attach to"
2036                                         " the correct peer")
2037         # note: _AssembleNet also handles the case when we don't want
2038         # local storage (i.e. one or more of the _[lr](host|port) is
2039         # None)
2040         if (self._AssembleNet(minor, (self._lhost, self._lport,
2041                                       self._rhost, self._rport), "C") and
2042             self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2043           break
2044
2045     else:
2046       minor = None
2047
2048     self._SetFromMinor(minor)
2049     return minor is not None
2050
2051   def Assemble(self):
2052     """Assemble the drbd.
2053
2054     Method:
2055       - if we have a local backing device, we bind to it by:
2056         - checking the list of used drbd devices
2057         - check if the local minor use of any of them is our own device
2058         - if yes, abort?
2059         - if not, bind
2060       - if we have a local/remote net info:
2061         - redo the local backing device step for the remote device
2062         - check if any drbd device is using the local port,
2063           if yes abort
2064         - check if any remote drbd device is using the remote
2065           port, if yes abort (for now)
2066         - bind our net port
2067         - bind the remote net port
2068
2069     """
2070     self.Attach()
2071     if self.minor is not None:
2072       logger.Info("Already assembled")
2073       return True
2074
2075     result = super(DRBD8, self).Assemble()
2076     if not result:
2077       return result
2078
2079     minor = self._FindUnusedMinor()
2080     need_localdev_teardown = False
2081     if self._children and self._children[0] and self._children[1]:
2082       result = self._AssembleLocal(minor, self._children[0].dev_path,
2083                                    self._children[1].dev_path)
2084       if not result:
2085         return False
2086       need_localdev_teardown = True
2087     if self._lhost and self._lport and self._rhost and self._rport:
2088       result = self._AssembleNet(minor,
2089                                  (self._lhost, self._lport,
2090                                   self._rhost, self._rport),
2091                                  "C")
2092       if not result:
2093         if need_localdev_teardown:
2094           # we will ignore failures from this
2095           logger.Error("net setup failed, tearing down local device")
2096           self._ShutdownAll(minor)
2097         return False
2098     self._SetFromMinor(minor)
2099     return True
2100
2101   @classmethod
2102   def _ShutdownLocal(cls, minor):
2103     """Detach from the local device.
2104
2105     I/Os will continue to be served from the remote device. If we
2106     don't have a remote device, this operation will fail.
2107
2108     """
2109     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2110     if result.failed:
2111       logger.Error("Can't detach local device: %s" % result.output)
2112     return not result.failed
2113
2114   @classmethod
2115   def _ShutdownNet(cls, minor):
2116     """Disconnect from the remote peer.
2117
2118     This fails if we don't have a local device.
2119
2120     """
2121     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2122     if result.failed:
2123       logger.Error("Can't shutdown network: %s" % result.output)
2124     return not result.failed
2125
2126   @classmethod
2127   def _ShutdownAll(cls, minor):
2128     """Deactivate the device.
2129
2130     This will, of course, fail if the device is in use.
2131
2132     """
2133     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2134     if result.failed:
2135       logger.Error("Can't shutdown drbd device: %s" % result.output)
2136     return not result.failed
2137
2138   def Shutdown(self):
2139     """Shutdown the DRBD device.
2140
2141     """
2142     if self.minor is None and not self.Attach():
2143       logger.Info("DRBD device not attached to a device during Shutdown")
2144       return True
2145     if not self._ShutdownAll(self.minor):
2146       return False
2147     self.minor = None
2148     self.dev_path = None
2149     return True
2150
2151   def Remove(self):
2152     """Stub remove for DRBD devices.
2153
2154     """
2155     return self.Shutdown()
2156
2157   @classmethod
2158   def Create(cls, unique_id, children, size):
2159     """Create a new DRBD8 device.
2160
2161     Since DRBD devices are not created per se, just assembled, this
2162     function only initializes the metadata.
2163
2164     """
2165     if len(children) != 2:
2166       raise errors.ProgrammerError("Invalid setup for the drbd device")
2167     meta = children[1]
2168     meta.Assemble()
2169     if not meta.Attach():
2170       raise errors.BlockDeviceError("Can't attach to meta device")
2171     if not cls._CheckMetaSize(meta.dev_path):
2172       raise errors.BlockDeviceError("Invalid meta device size")
2173     cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2174     if not cls._IsValidMeta(meta.dev_path):
2175       raise errors.BlockDeviceError("Cannot initalize meta device")
2176     return cls(unique_id, children)
2177
2178
2179 class FileStorage(BlockDev):
2180   """File device.
2181   
2182   This class represents the a file storage backend device.
2183
2184   The unique_id for the file device is a (file_driver, file_path) tuple.
2185   
2186   """
2187   def __init__(self, unique_id, children):
2188     """Initalizes a file device backend.
2189
2190     """
2191     if children:
2192       raise errors.BlockDeviceError("Invalid setup for file device")
2193     super(FileStorage, self).__init__(unique_id, children)
2194     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2195       raise ValueError("Invalid configuration data %s" % str(unique_id))
2196     self.driver = unique_id[0]
2197     self.dev_path = unique_id[1]
2198
2199   def Assemble(self):
2200     """Assemble the device.
2201
2202     Checks whether the file device exists, raises BlockDeviceError otherwise.
2203
2204     """
2205     if not os.path.exists(self.dev_path):
2206       raise errors.BlockDeviceError("File device '%s' does not exist." %
2207                                     self.dev_path)
2208     return True
2209
2210   def Shutdown(self):
2211     """Shutdown the device.
2212
2213     This is a no-op for the file type, as we don't deacivate
2214     the file on shutdown.
2215
2216     """
2217     return True
2218
2219   def Open(self, force=False):
2220     """Make the device ready for I/O.
2221
2222     This is a no-op for the file type.
2223
2224     """
2225     pass
2226
2227   def Close(self):
2228     """Notifies that the device will no longer be used for I/O.
2229
2230     This is a no-op for the file type.
2231
2232     """
2233     pass
2234
2235   def Remove(self):
2236     """Remove the file backing the block device.
2237
2238     Returns:
2239       boolean indicating wheter removal of file was successful or not.
2240
2241     """
2242     if not os.path.exists(self.dev_path):
2243       return True
2244     try:
2245       os.remove(self.dev_path)
2246       return True
2247     except OSError, err:
2248       logger.Error("Can't remove file '%s': %s"
2249                    % (self.dev_path, err))
2250       return False
2251
2252   def Attach(self):
2253     """Attach to an existing file.
2254
2255     Check if this file already exists.
2256
2257     Returns:
2258       boolean indicating if file exists or not.
2259
2260     """
2261     if os.path.exists(self.dev_path):
2262       return True
2263     return False
2264
2265   @classmethod
2266   def Create(cls, unique_id, children, size):
2267     """Create a new file.
2268
2269     Args:
2270       children:
2271       size: integer size of file in MiB
2272
2273     Returns:
2274       A ganeti.bdev.FileStorage object.
2275
2276     """
2277     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2278       raise ValueError("Invalid configuration data %s" % str(unique_id))
2279     dev_path = unique_id[1]
2280     try:
2281       f = open(dev_path, 'w')
2282     except IOError, err:
2283       raise errors.BlockDeviceError("Could not create '%'" % err)
2284     else:
2285       f.truncate(size * 1024 * 1024)
2286       f.close()
2287
2288     return FileStorage(unique_id, children)
2289
2290
2291 DEV_MAP = {
2292   constants.LD_LV: LogicalVolume,
2293   constants.LD_MD_R1: MDRaid1,
2294   constants.LD_DRBD7: DRBDev,
2295   constants.LD_DRBD8: DRBD8,
2296   constants.LD_FILE: FileStorage,
2297   }
2298
2299
2300 def FindDevice(dev_type, unique_id, children):
2301   """Search for an existing, assembled device.
2302
2303   This will succeed only if the device exists and is assembled, but it
2304   does not do any actions in order to activate the device.
2305
2306   """
2307   if dev_type not in DEV_MAP:
2308     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2309   device = DEV_MAP[dev_type](unique_id, children)
2310   if not device.Attach():
2311     return None
2312   return  device
2313
2314
2315 def AttachOrAssemble(dev_type, unique_id, children):
2316   """Try to attach or assemble an existing device.
2317
2318   This will attach to an existing assembled device or will assemble
2319   the device, as needed, to bring it fully up.
2320
2321   """
2322   if dev_type not in DEV_MAP:
2323     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2324   device = DEV_MAP[dev_type](unique_id, children)
2325   if not device.Attach():
2326     device.Assemble()
2327     if not device.Attach():
2328       raise errors.BlockDeviceError("Can't find a valid block device for"
2329                                     " %s/%s/%s" %
2330                                     (dev_type, unique_id, children))
2331   return device
2332
2333
2334 def Create(dev_type, unique_id, children, size):
2335   """Create a device.
2336
2337   """
2338   if dev_type not in DEV_MAP:
2339     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2340   device = DEV_MAP[dev_type].Create(unique_id, children, size)
2341   return device