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