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