Improve out-of-minors handling
[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
28 from ganeti import utils
29 from ganeti import logger
30 from ganeti import errors
31 from ganeti import constants
32
33
34 class BlockDev(object):
35   """Block device abstract class.
36
37   A block device can be in the following states:
38     - not existing on the system, and by `Create()` it goes into:
39     - existing but not setup/not active, and by `Assemble()` goes into:
40     - active read-write and by `Open()` it goes into
41     - online (=used, or ready for use)
42
43   A device can also be online but read-only, however we are not using
44   the readonly state (MD and LV have it, if needed in the future)
45   and we are usually looking at this like at a stack, so it's easier
46   to conceptualise the transition from not-existing to online and back
47   like a linear one.
48
49   The many different states of the device are due to the fact that we
50   need to cover many device types:
51     - logical volumes are created, lvchange -a y $lv, and used
52     - md arrays are created or assembled and used
53     - drbd devices are attached to a local disk/remote peer and made primary
54
55   The status of the device can be examined by `GetStatus()`, which
56   returns a numerical value, depending on the position in the
57   transition stack of the device.
58
59   A block device is identified by three items:
60     - the /dev path of the device (dynamic)
61     - a unique ID of the device (static)
62     - it's major/minor pair (dynamic)
63
64   Not all devices implement both the first two as distinct items. LVM
65   logical volumes have their unique ID (the pair volume group, logical
66   volume name) in a 1-to-1 relation to the dev path. For MD devices,
67   the /dev path is dynamic and the unique ID is the UUID generated at
68   array creation plus the slave list. For DRBD devices, the /dev path
69   is again dynamic and the unique id is the pair (host1, dev1),
70   (host2, dev2).
71
72   You can get to a device in two ways:
73     - creating the (real) device, which returns you
74       an attached instance (lvcreate, mdadm --create)
75     - attaching of a python instance to an existing (real) device
76
77   The second point, the attachement to a device, is different
78   depending on whether the device is assembled or not. At init() time,
79   we search for a device with the same unique_id as us. If found,
80   good. It also means that the device is already assembled. If not,
81   after assembly we'll have our correct major/minor.
82
83   """
84   STATUS_UNKNOWN = 0
85   STATUS_EXISTING = 1
86   STATUS_STANDBY = 2
87   STATUS_ONLINE = 3
88
89   STATUS_MAP = {
90     STATUS_UNKNOWN: "unknown",
91     STATUS_EXISTING: "existing",
92     STATUS_STANDBY: "ready for use",
93     STATUS_ONLINE: "online",
94     }
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
105   def Assemble(self):
106     """Assemble the device from its components.
107
108     If this is a plain block device (e.g. LVM) than assemble does
109     nothing, as the LVM has no children and we don't put logical
110     volumes offline.
111
112     One guarantee is that after the device has been assembled, it
113     knows its major/minor numbers. This allows other devices (usually
114     parents) to probe correctly for their children.
115
116     """
117     status = True
118     for child in self._children:
119       if not isinstance(child, BlockDev):
120         raise TypeError("Invalid child passed of type '%s'" % type(child))
121       if not status:
122         break
123       status = status and child.Assemble()
124       if not status:
125         break
126       status = status and child.Open()
127
128     if not status:
129       for child in self._children:
130         child.Shutdown()
131     return status
132
133
134   def Attach(self):
135     """Find a device which matches our config and attach to it.
136
137     """
138     raise NotImplementedError
139
140
141   def Close(self):
142     """Notifies that the device will no longer be used for I/O.
143
144     """
145     raise NotImplementedError
146
147
148   @classmethod
149   def Create(cls, unique_id, children, size):
150     """Create the device.
151
152     If the device cannot be created, it will return None
153     instead. Error messages go to the logging system.
154
155     Note that for some devices, the unique_id is used, and for other,
156     the children. The idea is that these two, taken together, are
157     enough for both creation and assembly (later).
158
159     """
160     raise NotImplementedError
161
162
163   def Remove(self):
164     """Remove this device.
165
166     This makes sense only for some of the device types: LV and to a
167     lesser degree, md devices. Also note that if the device can't
168     attach, the removal can't be completed.
169
170     """
171     raise NotImplementedError
172
173
174   def GetStatus(self):
175     """Return the status of the device.
176
177     """
178     raise NotImplementedError
179
180
181   def Open(self, force=False):
182     """Make the device ready for use.
183
184     This makes the device ready for I/O. For now, just the DRBD
185     devices need this.
186
187     The force parameter signifies that if the device has any kind of
188     --force thing, it should be used, we know what we are doing.
189
190     """
191     raise NotImplementedError
192
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
205   def SetSyncSpeed(self, speed):
206     """Adjust the sync speed of the mirror.
207
208     In case this is not a mirroring device, this is no-op.
209
210     """
211     result = True
212     if self._children:
213       for child in self._children:
214         result = result and child.SetSyncSpeed(speed)
215     return result
216
217
218   def GetSyncStatus(self):
219     """Returns the sync status of the device.
220
221     If this device is a mirroring device, this function returns the
222     status of the mirror.
223
224     Returns:
225      (sync_percent, estimated_time, is_degraded)
226
227     If sync_percent is None, it means all is ok
228     If estimated_time is None, it means we can't estimate
229     the time needed, otherwise it's the time left in seconds
230     If is_degraded is True, it means the device is missing
231     redundancy. This is usually a sign that something went wrong in
232     the device setup, if sync_percent is None.
233
234     """
235     return None, None, False
236
237
238   def CombinedSyncStatus(self):
239     """Calculate the mirror status recursively for our children.
240
241     The return value is the same as for `GetSyncStatus()` except the
242     minimum percent and maximum time are calculated across our
243     children.
244
245     """
246     min_percent, max_time, is_degraded = self.GetSyncStatus()
247     if self._children:
248       for child in self._children:
249         c_percent, c_time, c_degraded = child.GetSyncStatus()
250         if min_percent is None:
251           min_percent = c_percent
252         elif c_percent is not None:
253           min_percent = min(min_percent, c_percent)
254         if max_time is None:
255           max_time = c_time
256         elif c_time is not None:
257           max_time = max(max_time, c_time)
258         is_degraded = is_degraded or c_degraded
259     return min_percent, max_time, is_degraded
260
261
262   def SetInfo(self, text):
263     """Update metadata with info text.
264
265     Only supported for some device types.
266
267     """
268     for child in self._children:
269       child.SetInfo(text)
270
271
272   def __repr__(self):
273     return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
274             (self.__class__, self.unique_id, self._children,
275              self.major, self.minor, self.dev_path))
276
277
278 class LogicalVolume(BlockDev):
279   """Logical Volume block device.
280
281   """
282   def __init__(self, unique_id, children):
283     """Attaches to a LV device.
284
285     The unique_id is a tuple (vg_name, lv_name)
286
287     """
288     super(LogicalVolume, self).__init__(unique_id, children)
289     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
290       raise ValueError("Invalid configuration data %s" % str(unique_id))
291     self._vg_name, self._lv_name = unique_id
292     self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
293     self.Attach()
294
295
296   @classmethod
297   def Create(cls, unique_id, children, size):
298     """Create a new logical volume.
299
300     """
301     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
302       raise ValueError("Invalid configuration data %s" % str(unique_id))
303     vg_name, lv_name = unique_id
304     pvs_info = cls.GetPVInfo(vg_name)
305     if not pvs_info:
306       raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
307                                     vg_name)
308     pvs_info.sort()
309     pvs_info.reverse()
310
311     pvlist = [ pv[1] for pv in pvs_info ]
312     free_size = sum([ pv[0] for pv in pvs_info ])
313
314     # The size constraint should have been checked from the master before
315     # calling the create function.
316     if free_size < size:
317       raise errors.BlockDeviceError("Not enough free space: required %s,"
318                                     " available %s" % (size, free_size))
319     result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
320                            vg_name] + pvlist)
321     if result.failed:
322       raise errors.BlockDeviceError(result.fail_reason)
323     return LogicalVolume(unique_id, children)
324
325   @staticmethod
326   def GetPVInfo(vg_name):
327     """Get the free space info for PVs in a volume group.
328
329     Args:
330       vg_name: the volume group name
331
332     Returns:
333       list of (free_space, name) with free_space in mebibytes
334
335     """
336     command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
337                "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
338                "--separator=:"]
339     result = utils.RunCmd(command)
340     if result.failed:
341       logger.Error("Can't get the PV information: %s" % result.fail_reason)
342       return None
343     data = []
344     for line in result.stdout.splitlines():
345       fields = line.strip().split(':')
346       if len(fields) != 4:
347         logger.Error("Can't parse pvs output: line '%s'" % line)
348         return None
349       # skip over pvs from another vg or ones which are not allocatable
350       if fields[1] != vg_name or fields[3][0] != 'a':
351         continue
352       data.append((float(fields[2]), fields[0]))
353
354     return data
355
356   def Remove(self):
357     """Remove this logical volume.
358
359     """
360     if not self.minor and not self.Attach():
361       # the LV does not exist
362       return True
363     result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
364                            (self._vg_name, self._lv_name)])
365     if result.failed:
366       logger.Error("Can't lvremove: %s" % result.fail_reason)
367
368     return not result.failed
369
370
371   def Attach(self):
372     """Attach to an existing LV.
373
374     This method will try to see if an existing and active LV exists
375     which matches the our name. If so, its major/minor will be
376     recorded.
377
378     """
379     result = utils.RunCmd(["lvdisplay", self.dev_path])
380     if result.failed:
381       logger.Error("Can't find LV %s: %s" %
382                    (self.dev_path, result.fail_reason))
383       return False
384     match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
385     for line in result.stdout.splitlines():
386       match_result = match.match(line)
387       if match_result:
388         self.major = int(match_result.group(1))
389         self.minor = int(match_result.group(2))
390         return True
391     return False
392
393
394   def Assemble(self):
395     """Assemble the device.
396
397     This is a no-op for the LV device type. Eventually, we could
398     lvchange -ay here if we see that the LV is not active.
399
400     """
401     return True
402
403
404   def Shutdown(self):
405     """Shutdown the device.
406
407     This is a no-op for the LV device type, as we don't deactivate the
408     volumes on shutdown.
409
410     """
411     return True
412
413
414   def GetStatus(self):
415     """Return the status of the device.
416
417     Logical volumes will can be in all four states, although we don't
418     deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
419     should not be seen for our devices.
420
421     """
422     result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
423     if result.failed:
424       logger.Error("Can't display lv: %s" % result.fail_reason)
425       return self.STATUS_UNKNOWN
426     out = result.stdout.strip()
427     # format: type/permissions/alloc/fixed_minor/state/open
428     if len(out) != 6:
429       return self.STATUS_UNKNOWN
430     #writable = (out[1] == "w")
431     active = (out[4] == "a")
432     online = (out[5] == "o")
433     if online:
434       retval = self.STATUS_ONLINE
435     elif active:
436       retval = self.STATUS_STANDBY
437     else:
438       retval = self.STATUS_EXISTING
439
440     return retval
441
442
443   def Open(self, force=False):
444     """Make the device ready for I/O.
445
446     This is a no-op for the LV device type.
447
448     """
449     return True
450
451
452   def Close(self):
453     """Notifies that the device will no longer be used for I/O.
454
455     This is a no-op for the LV device type.
456
457     """
458     return True
459
460
461   def Snapshot(self, size):
462     """Create a snapshot copy of an lvm block device.
463
464     """
465     snap_name = self._lv_name + ".snap"
466
467     # remove existing snapshot if found
468     snap = LogicalVolume((self._vg_name, snap_name), None)
469     snap.Remove()
470
471     pvs_info = self.GetPVInfo(self._vg_name)
472     if not pvs_info:
473       raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
474                                     self._vg_name)
475     pvs_info.sort()
476     pvs_info.reverse()
477     free_size, pv_name = pvs_info[0]
478     if free_size < size:
479       raise errors.BlockDeviceError("Not enough free space: required %s,"
480                                     " available %s" % (size, free_size))
481
482     result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
483                            "-n%s" % snap_name, self.dev_path])
484     if result.failed:
485       raise errors.BlockDeviceError("command: %s error: %s" %
486                                     (result.cmd, result.fail_reason))
487
488     return snap_name
489
490
491   def SetInfo(self, text):
492     """Update metadata with info text.
493
494     """
495     BlockDev.SetInfo(self, text)
496
497     # Replace invalid characters
498     text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
499     text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
500
501     # Only up to 128 characters are allowed
502     text = text[:128]
503
504     result = utils.RunCmd(["lvchange", "--addtag", text,
505                            self.dev_path])
506     if result.failed:
507       raise errors.BlockDeviceError("Command: %s error: %s" %
508                                     (result.cmd, result.fail_reason))
509
510
511 class MDRaid1(BlockDev):
512   """raid1 device implemented via md.
513
514   """
515   def __init__(self, unique_id, children):
516     super(MDRaid1, self).__init__(unique_id, children)
517     self.major = 9
518     self.Attach()
519
520
521   def Attach(self):
522     """Find an array which matches our config and attach to it.
523
524     This tries to find a MD array which has the same UUID as our own.
525
526     """
527     minor = self._FindMDByUUID(self.unique_id)
528     if minor is not None:
529       self._SetFromMinor(minor)
530     else:
531       self.minor = None
532       self.dev_path = None
533
534     return (minor is not None)
535
536
537   @staticmethod
538   def _GetUsedDevs():
539     """Compute the list of in-use MD devices.
540
541     It doesn't matter if the used device have other raid level, just
542     that they are in use.
543
544     """
545     mdstat = open("/proc/mdstat", "r")
546     data = mdstat.readlines()
547     mdstat.close()
548
549     used_md = {}
550     valid_line = re.compile("^md([0-9]+) : .*$")
551     for line in data:
552       match = valid_line.match(line)
553       if match:
554         md_no = int(match.group(1))
555         used_md[md_no] = line
556
557     return used_md
558
559
560   @staticmethod
561   def _GetDevInfo(minor):
562     """Get info about a MD device.
563
564     Currently only uuid is returned.
565
566     """
567     result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
568     if result.failed:
569       logger.Error("Can't display md: %s" % result.fail_reason)
570       return None
571     retval = {}
572     for line in result.stdout.splitlines():
573       line = line.strip()
574       kv = line.split(" : ", 1)
575       if kv:
576         if kv[0] == "UUID":
577           retval["uuid"] = kv[1].split()[0]
578         elif kv[0] == "State":
579           retval["state"] = kv[1].split(", ")
580     return retval
581
582
583   @staticmethod
584   def _FindUnusedMinor():
585     """Compute an unused MD minor.
586
587     This code assumes that there are 256 minors only.
588
589     """
590     used_md = MDRaid1._GetUsedDevs()
591     i = 0
592     while i < 256:
593       if i not in used_md:
594         break
595       i += 1
596     if i == 256:
597       logger.Error("Critical: Out of md minor numbers.")
598       raise errors.BlockDeviceError("Can't find a free MD minor")
599     return i
600
601
602   @classmethod
603   def _FindMDByUUID(cls, uuid):
604     """Find the minor of an MD array with a given UUID.
605
606     """
607     md_list = cls._GetUsedDevs()
608     for minor in md_list:
609       info = cls._GetDevInfo(minor)
610       if info and info["uuid"] == uuid:
611         return minor
612     return None
613
614
615   @staticmethod
616   def _ZeroSuperblock(dev_path):
617     """Zero the possible locations for an MD superblock.
618
619     The zero-ing can't be done via ``mdadm --zero-superblock`` as that
620     fails in versions 2.x with the same error code as non-writable
621     device.
622
623     The superblocks are located at (negative values are relative to
624     the end of the block device):
625       - -128k to end for version 0.90 superblock
626       - -8k to -12k for version 1.0 superblock (included in the above)
627       - 0k to 4k for version 1.1 superblock
628       - 4k to 8k for version 1.2 superblock
629
630     To cover all situations, the zero-ing will be:
631       - 0k to 128k
632       - -128k to end
633
634     As such, the minimum device size must be 128k, otherwise we'll get
635     I/O errors.
636
637     Note that this function depends on the fact that one can open,
638     read and write block devices normally.
639
640     """
641     overwrite_size = 128 * 1024
642     empty_buf = '\0' * overwrite_size
643     fd = open(dev_path, "r+")
644     try:
645       fd.seek(0, 0)
646       p1 = fd.tell()
647       fd.write(empty_buf)
648       p2 = fd.tell()
649       logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
650       fd.seek(-overwrite_size, 2)
651       p1 = fd.tell()
652       fd.write(empty_buf)
653       p2 = fd.tell()
654       logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
655     finally:
656       fd.close()
657
658   @classmethod
659   def Create(cls, unique_id, children, size):
660     """Create a new MD raid1 array.
661
662     """
663     if not isinstance(children, (tuple, list)):
664       raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
665                        str(children))
666     for i in children:
667       if not isinstance(i, BlockDev):
668         raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
669     for i in children:
670       try:
671         cls._ZeroSuperblock(i.dev_path)
672       except EnvironmentError, err:
673         logger.Error("Can't zero superblock for %s: %s" %
674                      (i.dev_path, str(err)))
675         return None
676     minor = cls._FindUnusedMinor()
677     result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
678                            "--auto=yes", "--force", "-l1",
679                            "-n%d" % len(children)] +
680                           [dev.dev_path for dev in children])
681
682     if result.failed:
683       logger.Error("Can't create md: %s: %s" % (result.fail_reason,
684                                                 result.output))
685       return None
686     info = cls._GetDevInfo(minor)
687     if not info or not "uuid" in info:
688       logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
689       return None
690     return MDRaid1(info["uuid"], children)
691
692
693   def Remove(self):
694     """Stub remove function for MD RAID 1 arrays.
695
696     We don't remove the superblock right now. Mark a to do.
697
698     """
699     #TODO: maybe zero superblock on child devices?
700     return self.Shutdown()
701
702
703   def AddChild(self, device):
704     """Add a new member to the md raid1.
705
706     """
707     if self.minor is None and not self.Attach():
708       raise errors.BlockDeviceError("Can't attach to device")
709     if device.dev_path is None:
710       raise errors.BlockDeviceError("New child is not initialised")
711     result = utils.RunCmd(["mdadm", "-a", self.dev_path, device.dev_path])
712     if result.failed:
713       raise errors.BlockDeviceError("Failed to add new device to array: %s" %
714                                     result.output)
715     new_len = len(self._children) + 1
716     result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
717     if result.failed:
718       raise errors.BlockDeviceError("Can't grow md array: %s" %
719                                     result.output)
720     self._children.append(device)
721
722
723   def RemoveChild(self, dev_path):
724     """Remove member from the md raid1.
725
726     """
727     if self.minor is None and not self.Attach():
728       raise errors.BlockDeviceError("Can't attach to device")
729     if len(self._children) == 1:
730       raise errors.BlockDeviceError("Can't reduce member when only one"
731                                     " child left")
732     for device in self._children:
733       if device.dev_path == dev_path:
734         break
735     else:
736       raise errors.BlockDeviceError("Can't find child with this path")
737     new_len = len(self._children) - 1
738     result = utils.RunCmd(["mdadm", "-f", self.dev_path, dev_path])
739     if result.failed:
740       raise errors.BlockDeviceError("Failed to mark device as failed: %s" %
741                                     result.output)
742
743     # it seems here we need a short delay for MD to update its
744     # superblocks
745     time.sleep(0.5)
746     result = utils.RunCmd(["mdadm", "-r", self.dev_path, dev_path])
747     if result.failed:
748       raise errors.BlockDeviceError("Failed to remove device from array:"
749                                         " %s" % result.output)
750     result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
751                            "-n", new_len])
752     if result.failed:
753       raise errors.BlockDeviceError("Can't shrink md array: %s" %
754                                     result.output)
755     self._children.remove(device)
756
757
758   def GetStatus(self):
759     """Return the status of the device.
760
761     """
762     self.Attach()
763     if self.minor is None:
764       retval = self.STATUS_UNKNOWN
765     else:
766       retval = self.STATUS_ONLINE
767     return retval
768
769
770   def _SetFromMinor(self, minor):
771     """Set our parameters based on the given minor.
772
773     This sets our minor variable and our dev_path.
774
775     """
776     self.minor = minor
777     self.dev_path = "/dev/md%d" % minor
778
779
780   def Assemble(self):
781     """Assemble the MD device.
782
783     At this point we should have:
784       - list of children devices
785       - uuid
786
787     """
788     result = super(MDRaid1, self).Assemble()
789     if not result:
790       return result
791     md_list = self._GetUsedDevs()
792     for minor in md_list:
793       info = self._GetDevInfo(minor)
794       if info and info["uuid"] == self.unique_id:
795         self._SetFromMinor(minor)
796         logger.Info("MD array %s already started" % str(self))
797         return True
798     free_minor = self._FindUnusedMinor()
799     result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
800                            self.unique_id, "/dev/md%d" % free_minor] +
801                           [bdev.dev_path for bdev in self._children])
802     if result.failed:
803       logger.Error("Can't assemble MD array: %s: %s" %
804                    (result.fail_reason, result.output))
805       self.minor = None
806     else:
807       self.minor = free_minor
808     return not result.failed
809
810
811   def Shutdown(self):
812     """Tear down the MD array.
813
814     This does a 'mdadm --stop' so after this command, the array is no
815     longer available.
816
817     """
818     if self.minor is None and not self.Attach():
819       logger.Info("MD object not attached to a device")
820       return True
821
822     result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
823     if result.failed:
824       logger.Error("Can't stop MD array: %s" % result.fail_reason)
825       return False
826     self.minor = None
827     self.dev_path = None
828     return True
829
830
831   def SetSyncSpeed(self, kbytes):
832     """Set the maximum sync speed for the MD array.
833
834     """
835     result = super(MDRaid1, self).SetSyncSpeed(kbytes)
836     if self.minor is None:
837       logger.Error("MD array not attached to a device")
838       return False
839     f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
840     try:
841       f.write("%d" % kbytes)
842     finally:
843       f.close()
844     f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
845     try:
846       f.write("%d" % (kbytes/2))
847     finally:
848       f.close()
849     return result
850
851
852   def GetSyncStatus(self):
853     """Returns the sync status of the device.
854
855     Returns:
856      (sync_percent, estimated_time)
857
858     If sync_percent is None, it means all is ok
859     If estimated_time is None, it means we can't esimate
860     the time needed, otherwise it's the time left in seconds
861
862     """
863     if self.minor is None and not self.Attach():
864       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
865     dev_info = self._GetDevInfo(self.minor)
866     is_clean = ("state" in dev_info and
867                 len(dev_info["state"]) == 1 and
868                 dev_info["state"][0] in ("clean", "active"))
869     sys_path = "/sys/block/md%s/md/" % self.minor
870     f = file(sys_path + "sync_action")
871     sync_status = f.readline().strip()
872     f.close()
873     if sync_status == "idle":
874       return None, None, not is_clean
875     f = file(sys_path + "sync_completed")
876     sync_completed = f.readline().strip().split(" / ")
877     f.close()
878     if len(sync_completed) != 2:
879       return 0, None, not is_clean
880     sync_done, sync_total = [float(i) for i in sync_completed]
881     sync_percent = 100.0*sync_done/sync_total
882     f = file(sys_path + "sync_speed")
883     sync_speed_k = int(f.readline().strip())
884     if sync_speed_k == 0:
885       time_est = None
886     else:
887       time_est = (sync_total - sync_done) / 2 / sync_speed_k
888     return sync_percent, time_est, not is_clean
889
890
891   def Open(self, force=False):
892     """Make the device ready for I/O.
893
894     This is a no-op for the MDRaid1 device type, although we could use
895     the 2.6.18's new array_state thing.
896
897     """
898     return True
899
900
901   def Close(self):
902     """Notifies that the device will no longer be used for I/O.
903
904     This is a no-op for the MDRaid1 device type, but see comment for
905     `Open()`.
906
907     """
908     return True
909
910
911 class BaseDRBD(BlockDev):
912   """Base DRBD class.
913
914   This class contains a few bits of common functionality between the
915   0.7 and 8.x versions of DRBD.
916
917   """
918   _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
919                            r" \(api:(\d+)/proto:(\d+)\)")
920   _DRBD_MAJOR = 147
921   _ST_UNCONFIGURED = "Unconfigured"
922   _ST_WFCONNECTION = "WFConnection"
923   _ST_CONNECTED = "Connected"
924
925   @staticmethod
926   def _GetProcData():
927     """Return data from /proc/drbd.
928
929     """
930     stat = open("/proc/drbd", "r")
931     try:
932       data = stat.read().splitlines()
933     finally:
934       stat.close()
935     if not data:
936       raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
937     return data
938
939   @classmethod
940   def _GetVersion(cls):
941     """Return the DRBD version.
942
943     This will return a list [k_major, k_minor, k_point, api, proto].
944
945     """
946     proc_data = cls._GetProcData()
947     first_line = proc_data[0].strip()
948     version = cls._VERSION_RE.match(first_line)
949     if not version:
950       raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
951                                     first_line)
952     return [int(val) for val in version.groups()]
953
954   @staticmethod
955   def _DevPath(minor):
956     """Return the path to a drbd device for a given minor.
957
958     """
959     return "/dev/drbd%d" % minor
960
961   @classmethod
962   def _GetUsedDevs(cls):
963     """Compute the list of used DRBD devices.
964
965     """
966     data = cls._GetProcData()
967
968     used_devs = {}
969     valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
970     for line in data:
971       match = valid_line.match(line)
972       if not match:
973         continue
974       minor = int(match.group(1))
975       state = match.group(2)
976       if state == cls._ST_UNCONFIGURED:
977         continue
978       used_devs[minor] = state, line
979
980     return used_devs
981
982
983 class DRBDev(BaseDRBD):
984   """DRBD block device.
985
986   This implements the local host part of the DRBD device, i.e. it
987   doesn't do anything to the supposed peer. If you need a fully
988   connected DRBD pair, you need to use this class on both hosts.
989
990   The unique_id for the drbd device is the (local_ip, local_port,
991   remote_ip, remote_port) tuple, and it must have two children: the
992   data device and the meta_device. The meta device is checked for
993   valid size and is zeroed on create.
994
995   """
996   def __init__(self, unique_id, children):
997     super(DRBDev, self).__init__(unique_id, children)
998     self.major = self._DRBD_MAJOR
999     [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1000     if kmaj != 0 and kmin != 7:
1001       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1002                                     " requested ganeti usage: kernel is"
1003                                     " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1004
1005     if len(children) != 2:
1006       raise ValueError("Invalid configuration data %s" % str(children))
1007     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1008       raise ValueError("Invalid configuration data %s" % str(unique_id))
1009     self._lhost, self._lport, self._rhost, self._rport = unique_id
1010     self.Attach()
1011
1012   @classmethod
1013   def _FindUnusedMinor(cls):
1014     """Find an unused DRBD device.
1015
1016     """
1017     data = cls._GetProcData()
1018
1019     valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1020     for line in data:
1021       match = valid_line.match(line)
1022       if match:
1023         return int(match.group(1))
1024     logger.Error("Error: no free drbd minors!")
1025     raise errors.BlockDeviceError("Can't find a free DRBD minor")
1026
1027   @classmethod
1028   def _GetDevInfo(cls, minor):
1029     """Get details about a given DRBD minor.
1030
1031     This return, if available, the local backing device in (major,
1032     minor) formant and the local and remote (ip, port) information.
1033
1034     """
1035     data = {}
1036     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1037     if result.failed:
1038       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1039       return data
1040     out = result.stdout
1041     if out == "Not configured\n":
1042       return data
1043     for line in out.splitlines():
1044       if "local_dev" not in data:
1045         match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1046         if match:
1047           data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1048           continue
1049       if "meta_dev" not in data:
1050         match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1051         if match:
1052           if match.group(2) is not None and match.group(3) is not None:
1053             # matched on the major/minor
1054             data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1055           else:
1056             # matched on the "internal" string
1057             data["meta_dev"] = match.group(1)
1058             # in this case, no meta_index is in the output
1059             data["meta_index"] = -1
1060           continue
1061       if "meta_index" not in data:
1062         match = re.match("^Meta index: ([0-9]+).*$", line)
1063         if match:
1064           data["meta_index"] = int(match.group(1))
1065           continue
1066       if "local_addr" not in data:
1067         match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1068         if match:
1069           data["local_addr"] = (match.group(1), int(match.group(2)))
1070           continue
1071       if "remote_addr" not in data:
1072         match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1073         if match:
1074           data["remote_addr"] = (match.group(1), int(match.group(2)))
1075           continue
1076     return data
1077
1078
1079   def _MatchesLocal(self, info):
1080     """Test if our local config matches with an existing device.
1081
1082     The parameter should be as returned from `_GetDevInfo()`. This
1083     method tests if our local backing device is the same as the one in
1084     the info parameter, in effect testing if we look like the given
1085     device.
1086
1087     """
1088     if not ("local_dev" in info and "meta_dev" in info and
1089             "meta_index" in info):
1090       return False
1091
1092     backend = self._children[0]
1093     if backend is not None:
1094       retval = (info["local_dev"] == (backend.major, backend.minor))
1095     else:
1096       retval = (info["local_dev"] == (0, 0))
1097     meta = self._children[1]
1098     if meta is not None:
1099       retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1100       retval = retval and (info["meta_index"] == 0)
1101     else:
1102       retval = retval and (info["meta_dev"] == "internal" and
1103                            info["meta_index"] == -1)
1104     return retval
1105
1106
1107   def _MatchesNet(self, info):
1108     """Test if our network config matches with an existing device.
1109
1110     The parameter should be as returned from `_GetDevInfo()`. This
1111     method tests if our network configuration is the same as the one
1112     in the info parameter, in effect testing if we look like the given
1113     device.
1114
1115     """
1116     if (((self._lhost is None and not ("local_addr" in info)) and
1117          (self._rhost is None and not ("remote_addr" in info)))):
1118       return True
1119
1120     if self._lhost is None:
1121       return False
1122
1123     if not ("local_addr" in info and
1124             "remote_addr" in info):
1125       return False
1126
1127     retval = (info["local_addr"] == (self._lhost, self._lport))
1128     retval = (retval and
1129               info["remote_addr"] == (self._rhost, self._rport))
1130     return retval
1131
1132
1133   @staticmethod
1134   def _IsValidMeta(meta_device):
1135     """Check if the given meta device looks like a valid one.
1136
1137     This currently only check the size, which must be around
1138     128MiB.
1139
1140     """
1141     result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1142     if result.failed:
1143       logger.Error("Failed to get device size: %s" % result.fail_reason)
1144       return False
1145     try:
1146       sectors = int(result.stdout)
1147     except ValueError:
1148       logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1149       return False
1150     bytes = sectors * 512
1151     if bytes < 128*1024*1024: # less than 128MiB
1152       logger.Error("Meta device too small (%.2fMib)" % (bytes/1024/1024))
1153       return False
1154     if bytes > (128+32)*1024*1024: # account for an extra (big) PE on LVM
1155       logger.Error("Meta device too big (%.2fMiB)" % (bytes/1024/1024))
1156       return False
1157     return True
1158
1159
1160   @classmethod
1161   def _AssembleLocal(cls, minor, backend, meta):
1162     """Configure the local part of a DRBD device.
1163
1164     This is the first thing that must be done on an unconfigured DRBD
1165     device. And it must be done only once.
1166
1167     """
1168     if not cls._IsValidMeta(meta):
1169       return False
1170     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1171                            backend, meta, "0", "-e", "detach"])
1172     if result.failed:
1173       logger.Error("Can't attach local disk: %s" % result.output)
1174     return not result.failed
1175
1176
1177   @classmethod
1178   def _ShutdownLocal(cls, minor):
1179     """Detach from the local device.
1180
1181     I/Os will continue to be served from the remote device. If we
1182     don't have a remote device, this operation will fail.
1183
1184     """
1185     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1186     if result.failed:
1187       logger.Error("Can't detach local device: %s" % result.output)
1188     return not result.failed
1189
1190
1191   @staticmethod
1192   def _ShutdownAll(minor):
1193     """Deactivate the device.
1194
1195     This will, of course, fail if the device is in use.
1196
1197     """
1198     result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1199     if result.failed:
1200       logger.Error("Can't shutdown drbd device: %s" % result.output)
1201     return not result.failed
1202
1203
1204   @classmethod
1205   def _AssembleNet(cls, minor, net_info, protocol):
1206     """Configure the network part of the device.
1207
1208     This operation can be, in theory, done multiple times, but there
1209     have been cases (in lab testing) in which the network part of the
1210     device had become stuck and couldn't be shut down because activity
1211     from the new peer (also stuck) triggered a timer re-init and
1212     needed remote peer interface shutdown in order to clear. So please
1213     don't change online the net config.
1214
1215     """
1216     lhost, lport, rhost, rport = net_info
1217     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1218                            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1219                            protocol])
1220     if result.failed:
1221       logger.Error("Can't setup network for dbrd device: %s" %
1222                    result.fail_reason)
1223       return False
1224
1225     timeout = time.time() + 10
1226     ok = False
1227     while time.time() < timeout:
1228       info = cls._GetDevInfo(minor)
1229       if not "local_addr" in info or not "remote_addr" in info:
1230         time.sleep(1)
1231         continue
1232       if (info["local_addr"] != (lhost, lport) or
1233           info["remote_addr"] != (rhost, rport)):
1234         time.sleep(1)
1235         continue
1236       ok = True
1237       break
1238     if not ok:
1239       logger.Error("Timeout while configuring network")
1240       return False
1241     return True
1242
1243
1244   @classmethod
1245   def _ShutdownNet(cls, minor):
1246     """Disconnect from the remote peer.
1247
1248     This fails if we don't have a local device.
1249
1250     """
1251     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1252     logger.Error("Can't shutdown network: %s" % result.output)
1253     return not result.failed
1254
1255
1256   def _SetFromMinor(self, minor):
1257     """Set our parameters based on the given minor.
1258
1259     This sets our minor variable and our dev_path.
1260
1261     """
1262     if minor is None:
1263       self.minor = self.dev_path = None
1264     else:
1265       self.minor = minor
1266       self.dev_path = self._DevPath(minor)
1267
1268
1269   def Assemble(self):
1270     """Assemble the drbd.
1271
1272     Method:
1273       - if we have a local backing device, we bind to it by:
1274         - checking the list of used drbd devices
1275         - check if the local minor use of any of them is our own device
1276         - if yes, abort?
1277         - if not, bind
1278       - if we have a local/remote net info:
1279         - redo the local backing device step for the remote device
1280         - check if any drbd device is using the local port,
1281           if yes abort
1282         - check if any remote drbd device is using the remote
1283           port, if yes abort (for now)
1284         - bind our net port
1285         - bind the remote net port
1286
1287     """
1288     self.Attach()
1289     if self.minor is not None:
1290       logger.Info("Already assembled")
1291       return True
1292
1293     result = super(DRBDev, self).Assemble()
1294     if not result:
1295       return result
1296
1297     minor = self._FindUnusedMinor()
1298     need_localdev_teardown = False
1299     if self._children[0]:
1300       result = self._AssembleLocal(minor, self._children[0].dev_path,
1301                                    self._children[1].dev_path)
1302       if not result:
1303         return False
1304       need_localdev_teardown = True
1305     if self._lhost and self._lport and self._rhost and self._rport:
1306       result = self._AssembleNet(minor,
1307                                  (self._lhost, self._lport,
1308                                   self._rhost, self._rport),
1309                                  "C")
1310       if not result:
1311         if need_localdev_teardown:
1312           # we will ignore failures from this
1313           logger.Error("net setup failed, tearing down local device")
1314           self._ShutdownAll(minor)
1315         return False
1316     self._SetFromMinor(minor)
1317     return True
1318
1319
1320   def Shutdown(self):
1321     """Shutdown the DRBD device.
1322
1323     """
1324     if self.minor is None and not self.Attach():
1325       logger.Info("DRBD device not attached to a device during Shutdown")
1326       return True
1327     if not self._ShutdownAll(self.minor):
1328       return False
1329     self.minor = None
1330     self.dev_path = None
1331     return True
1332
1333
1334   def Attach(self):
1335     """Find a DRBD device which matches our config and attach to it.
1336
1337     In case of partially attached (local device matches but no network
1338     setup), we perform the network attach. If successful, we re-test
1339     the attach if can return success.
1340
1341     """
1342     for minor in self._GetUsedDevs():
1343       info = self._GetDevInfo(minor)
1344       match_l = self._MatchesLocal(info)
1345       match_r = self._MatchesNet(info)
1346       if match_l and match_r:
1347         break
1348       if match_l and not match_r and "local_addr" not in info:
1349         res_r = self._AssembleNet(minor,
1350                                   (self._lhost, self._lport,
1351                                    self._rhost, self._rport),
1352                                   "C")
1353         if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1354           break
1355     else:
1356       minor = None
1357
1358     self._SetFromMinor(minor)
1359     return minor is not None
1360
1361
1362   def Open(self, force=False):
1363     """Make the local state primary.
1364
1365     If the 'force' parameter is given, the '--do-what-I-say' parameter
1366     is given. Since this is a pottentialy dangerous operation, the
1367     force flag should be only given after creation, when it actually
1368     has to be given.
1369
1370     """
1371     if self.minor is None and not self.Attach():
1372       logger.Error("DRBD cannot attach to a device during open")
1373       return False
1374     cmd = ["drbdsetup", self.dev_path, "primary"]
1375     if force:
1376       cmd.append("--do-what-I-say")
1377     result = utils.RunCmd(cmd)
1378     if result.failed:
1379       logger.Error("Can't make drbd device primary: %s" % result.output)
1380       return False
1381     return True
1382
1383
1384   def Close(self):
1385     """Make the local state secondary.
1386
1387     This will, of course, fail if the device is in use.
1388
1389     """
1390     if self.minor is None and not self.Attach():
1391       logger.Info("Instance not attached to a device")
1392       raise errors.BlockDeviceError("Can't find device")
1393     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1394     if result.failed:
1395       logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1396       raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1397
1398
1399   def SetSyncSpeed(self, kbytes):
1400     """Set the speed of the DRBD syncer.
1401
1402     """
1403     children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1404     if self.minor is None:
1405       logger.Info("Instance not attached to a device")
1406       return False
1407     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1408                            kbytes])
1409     if result.failed:
1410       logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1411     return not result.failed and children_result
1412
1413
1414   def GetSyncStatus(self):
1415     """Returns the sync status of the device.
1416
1417     Returns:
1418      (sync_percent, estimated_time)
1419
1420     If sync_percent is None, it means all is ok
1421     If estimated_time is None, it means we can't esimate
1422     the time needed, otherwise it's the time left in seconds
1423
1424     """
1425     if self.minor is None and not self.Attach():
1426       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1427     proc_info = self._MassageProcData(self._GetProcData())
1428     if self.minor not in proc_info:
1429       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1430                                     self.minor)
1431     line = proc_info[self.minor]
1432     match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1433                      " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1434     if match:
1435       sync_percent = float(match.group(1))
1436       hours = int(match.group(2))
1437       minutes = int(match.group(3))
1438       seconds = int(match.group(4))
1439       est_time = hours * 3600 + minutes * 60 + seconds
1440     else:
1441       sync_percent = None
1442       est_time = None
1443     match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1444     if not match:
1445       raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1446                                     self.minor)
1447     client_state = match.group(1)
1448     is_degraded = client_state != "Connected"
1449     return sync_percent, est_time, is_degraded
1450
1451
1452   @staticmethod
1453   def _MassageProcData(data):
1454     """Transform the output of _GetProdData into a nicer form.
1455
1456     Returns:
1457       a dictionary of minor: joined lines from /proc/drbd for that minor
1458
1459     """
1460     lmatch = re.compile("^ *([0-9]+):.*$")
1461     results = {}
1462     old_minor = old_line = None
1463     for line in data:
1464       lresult = lmatch.match(line)
1465       if lresult is not None:
1466         if old_minor is not None:
1467           results[old_minor] = old_line
1468         old_minor = int(lresult.group(1))
1469         old_line = line
1470       else:
1471         if old_minor is not None:
1472           old_line += " " + line.strip()
1473     # add last line
1474     if old_minor is not None:
1475       results[old_minor] = old_line
1476     return results
1477
1478
1479   def GetStatus(self):
1480     """Compute the status of the DRBD device
1481
1482     Note that DRBD devices don't have the STATUS_EXISTING state.
1483
1484     """
1485     if self.minor is None and not self.Attach():
1486       return self.STATUS_UNKNOWN
1487
1488     data = self._GetProcData()
1489     match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1490                        self.minor)
1491     for line in data:
1492       mresult = match.match(line)
1493       if mresult:
1494         break
1495     else:
1496       logger.Error("Can't find myself!")
1497       return self.STATUS_UNKNOWN
1498
1499     state = mresult.group(2)
1500     if state == "Primary":
1501       result = self.STATUS_ONLINE
1502     else:
1503       result = self.STATUS_STANDBY
1504
1505     return result
1506
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
1525   @classmethod
1526   def Create(cls, unique_id, children, size):
1527     """Create a new DRBD device.
1528
1529     Since DRBD devices are not created per se, just assembled, this
1530     function just zeroes the meta device.
1531
1532     """
1533     if len(children) != 2:
1534       raise errors.ProgrammerError("Invalid setup for the drbd device")
1535     meta = children[1]
1536     meta.Assemble()
1537     if not meta.Attach():
1538       raise errors.BlockDeviceError("Can't attach to meta device")
1539     if not cls._IsValidMeta(meta.dev_path):
1540       raise errors.BlockDeviceError("Invalid meta device")
1541     logger.Info("Started zeroing device %s" % meta.dev_path)
1542     cls._ZeroDevice(meta.dev_path)
1543     logger.Info("Done zeroing device %s" % meta.dev_path)
1544     return cls(unique_id, children)
1545
1546
1547   def Remove(self):
1548     """Stub remove for DRBD devices.
1549
1550     """
1551     return self.Shutdown()
1552
1553
1554 DEV_MAP = {
1555   constants.LD_LV: LogicalVolume,
1556   constants.LD_MD_R1: MDRaid1,
1557   constants.LD_DRBD7: DRBDev,
1558   }
1559
1560
1561 def FindDevice(dev_type, unique_id, children):
1562   """Search for an existing, assembled device.
1563
1564   This will succeed only if the device exists and is assembled, but it
1565   does not do any actions in order to activate the device.
1566
1567   """
1568   if dev_type not in DEV_MAP:
1569     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1570   device = DEV_MAP[dev_type](unique_id, children)
1571   if not device.Attach():
1572     return None
1573   return  device
1574
1575
1576 def AttachOrAssemble(dev_type, unique_id, children):
1577   """Try to attach or assemble an existing device.
1578
1579   This will attach to an existing assembled device or will assemble
1580   the device, as needed, to bring it fully up.
1581
1582   """
1583   if dev_type not in DEV_MAP:
1584     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1585   device = DEV_MAP[dev_type](unique_id, children)
1586   if not device.Attach():
1587     device.Assemble()
1588   if not device.Attach():
1589     raise errors.BlockDeviceError("Can't find a valid block device for"
1590                                   " %s/%s/%s" %
1591                                   (dev_type, unique_id, children))
1592   return device
1593
1594
1595 def Create(dev_type, unique_id, children, size):
1596   """Create a device.
1597
1598   """
1599   if dev_type not in DEV_MAP:
1600     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1601   device = DEV_MAP[dev_type].Create(unique_id, children, size)
1602   return device