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