Reduce the chance of DRBD errors with stale primaries
[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+)\)")
984   _DRBD_MAJOR = 147
985   _ST_UNCONFIGURED = "Unconfigured"
986   _ST_WFCONNECTION = "WFConnection"
987   _ST_CONNECTED = "Connected"
988
989   @staticmethod
990   def _GetProcData():
991     """Return data from /proc/drbd.
992
993     """
994     stat = open("/proc/drbd", "r")
995     try:
996       data = stat.read().splitlines()
997     finally:
998       stat.close()
999     if not data:
1000       raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
1001     return data
1002
1003   @staticmethod
1004   def _MassageProcData(data):
1005     """Transform the output of _GetProdData into a nicer form.
1006
1007     Returns:
1008       a dictionary of minor: joined lines from /proc/drbd for that minor
1009
1010     """
1011     lmatch = re.compile("^ *([0-9]+):.*$")
1012     results = {}
1013     old_minor = old_line = None
1014     for line in data:
1015       lresult = lmatch.match(line)
1016       if lresult is not None:
1017         if old_minor is not None:
1018           results[old_minor] = old_line
1019         old_minor = int(lresult.group(1))
1020         old_line = line
1021       else:
1022         if old_minor is not None:
1023           old_line += " " + line.strip()
1024     # add last line
1025     if old_minor is not None:
1026       results[old_minor] = old_line
1027     return results
1028
1029   @classmethod
1030   def _GetVersion(cls):
1031     """Return the DRBD version.
1032
1033     This will return a list [k_major, k_minor, k_point, api, proto].
1034
1035     """
1036     proc_data = cls._GetProcData()
1037     first_line = proc_data[0].strip()
1038     version = cls._VERSION_RE.match(first_line)
1039     if not version:
1040       raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1041                                     first_line)
1042     return [int(val) for val in version.groups()]
1043
1044   @staticmethod
1045   def _DevPath(minor):
1046     """Return the path to a drbd device for a given minor.
1047
1048     """
1049     return "/dev/drbd%d" % minor
1050
1051   @classmethod
1052   def _GetUsedDevs(cls):
1053     """Compute the list of used DRBD devices.
1054
1055     """
1056     data = cls._GetProcData()
1057
1058     used_devs = {}
1059     valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1060     for line in data:
1061       match = valid_line.match(line)
1062       if not match:
1063         continue
1064       minor = int(match.group(1))
1065       state = match.group(2)
1066       if state == cls._ST_UNCONFIGURED:
1067         continue
1068       used_devs[minor] = state, line
1069
1070     return used_devs
1071
1072   def _SetFromMinor(self, minor):
1073     """Set our parameters based on the given minor.
1074
1075     This sets our minor variable and our dev_path.
1076
1077     """
1078     if minor is None:
1079       self.minor = self.dev_path = None
1080     else:
1081       self.minor = minor
1082       self.dev_path = self._DevPath(minor)
1083
1084   @staticmethod
1085   def _CheckMetaSize(meta_device):
1086     """Check if the given meta device looks like a valid one.
1087
1088     This currently only check the size, which must be around
1089     128MiB.
1090
1091     """
1092     result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1093     if result.failed:
1094       logger.Error("Failed to get device size: %s" % result.fail_reason)
1095       return False
1096     try:
1097       sectors = int(result.stdout)
1098     except ValueError:
1099       logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1100       return False
1101     bytes = sectors * 512
1102     if bytes < 128 * 1024 * 1024: # less than 128MiB
1103       logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1104       return False
1105     if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1106       logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1107       return False
1108     return True
1109
1110   def Rename(self, new_id):
1111     """Rename a device.
1112
1113     This is not supported for drbd devices.
1114
1115     """
1116     raise errors.ProgrammerError("Can't rename a drbd device")
1117
1118
1119 class DRBDev(BaseDRBD):
1120   """DRBD block device.
1121
1122   This implements the local host part of the DRBD device, i.e. it
1123   doesn't do anything to the supposed peer. If you need a fully
1124   connected DRBD pair, you need to use this class on both hosts.
1125
1126   The unique_id for the drbd device is the (local_ip, local_port,
1127   remote_ip, remote_port) tuple, and it must have two children: the
1128   data device and the meta_device. The meta device is checked for
1129   valid size and is zeroed on create.
1130
1131   """
1132   def __init__(self, unique_id, children):
1133     super(DRBDev, self).__init__(unique_id, children)
1134     self.major = self._DRBD_MAJOR
1135     [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1136     if kmaj != 0 and kmin != 7:
1137       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1138                                     " requested ganeti usage: kernel is"
1139                                     " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1140
1141     if len(children) != 2:
1142       raise ValueError("Invalid configuration data %s" % str(children))
1143     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1144       raise ValueError("Invalid configuration data %s" % str(unique_id))
1145     self._lhost, self._lport, self._rhost, self._rport = unique_id
1146     self.Attach()
1147
1148   @classmethod
1149   def _FindUnusedMinor(cls):
1150     """Find an unused DRBD device.
1151
1152     """
1153     data = cls._GetProcData()
1154
1155     valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1156     for line in data:
1157       match = valid_line.match(line)
1158       if match:
1159         return int(match.group(1))
1160     logger.Error("Error: no free drbd minors!")
1161     raise errors.BlockDeviceError("Can't find a free DRBD minor")
1162
1163   @classmethod
1164   def _GetDevInfo(cls, minor):
1165     """Get details about a given DRBD minor.
1166
1167     This return, if available, the local backing device in (major,
1168     minor) formant and the local and remote (ip, port) information.
1169
1170     """
1171     data = {}
1172     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1173     if result.failed:
1174       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1175       return data
1176     out = result.stdout
1177     if out == "Not configured\n":
1178       return data
1179     for line in out.splitlines():
1180       if "local_dev" not in data:
1181         match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1182         if match:
1183           data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1184           continue
1185       if "meta_dev" not in data:
1186         match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1187         if match:
1188           if match.group(2) is not None and match.group(3) is not None:
1189             # matched on the major/minor
1190             data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1191           else:
1192             # matched on the "internal" string
1193             data["meta_dev"] = match.group(1)
1194             # in this case, no meta_index is in the output
1195             data["meta_index"] = -1
1196           continue
1197       if "meta_index" not in data:
1198         match = re.match("^Meta index: ([0-9]+).*$", line)
1199         if match:
1200           data["meta_index"] = int(match.group(1))
1201           continue
1202       if "local_addr" not in data:
1203         match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1204         if match:
1205           data["local_addr"] = (match.group(1), int(match.group(2)))
1206           continue
1207       if "remote_addr" not in data:
1208         match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1209         if match:
1210           data["remote_addr"] = (match.group(1), int(match.group(2)))
1211           continue
1212     return data
1213
1214   def _MatchesLocal(self, info):
1215     """Test if our local config matches with an existing device.
1216
1217     The parameter should be as returned from `_GetDevInfo()`. This
1218     method tests if our local backing device is the same as the one in
1219     the info parameter, in effect testing if we look like the given
1220     device.
1221
1222     """
1223     if not ("local_dev" in info and "meta_dev" in info and
1224             "meta_index" in info):
1225       return False
1226
1227     backend = self._children[0]
1228     if backend is not None:
1229       retval = (info["local_dev"] == (backend.major, backend.minor))
1230     else:
1231       retval = (info["local_dev"] == (0, 0))
1232     meta = self._children[1]
1233     if meta is not None:
1234       retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1235       retval = retval and (info["meta_index"] == 0)
1236     else:
1237       retval = retval and (info["meta_dev"] == "internal" and
1238                            info["meta_index"] == -1)
1239     return retval
1240
1241   def _MatchesNet(self, info):
1242     """Test if our network config matches with an existing device.
1243
1244     The parameter should be as returned from `_GetDevInfo()`. This
1245     method tests if our network configuration is the same as the one
1246     in the info parameter, in effect testing if we look like the given
1247     device.
1248
1249     """
1250     if (((self._lhost is None and not ("local_addr" in info)) and
1251          (self._rhost is None and not ("remote_addr" in info)))):
1252       return True
1253
1254     if self._lhost is None:
1255       return False
1256
1257     if not ("local_addr" in info and
1258             "remote_addr" in info):
1259       return False
1260
1261     retval = (info["local_addr"] == (self._lhost, self._lport))
1262     retval = (retval and
1263               info["remote_addr"] == (self._rhost, self._rport))
1264     return retval
1265
1266   @classmethod
1267   def _AssembleLocal(cls, minor, backend, meta):
1268     """Configure the local part of a DRBD device.
1269
1270     This is the first thing that must be done on an unconfigured DRBD
1271     device. And it must be done only once.
1272
1273     """
1274     if not cls._CheckMetaSize(meta):
1275       return False
1276     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1277                            backend, meta, "0", "-e", "detach"])
1278     if result.failed:
1279       logger.Error("Can't attach local disk: %s" % result.output)
1280     return not result.failed
1281
1282   @classmethod
1283   def _ShutdownLocal(cls, minor):
1284     """Detach from the local device.
1285
1286     I/Os will continue to be served from the remote device. If we
1287     don't have a remote device, this operation will fail.
1288
1289     """
1290     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1291     if result.failed:
1292       logger.Error("Can't detach local device: %s" % result.output)
1293     return not result.failed
1294
1295   @staticmethod
1296   def _ShutdownAll(minor):
1297     """Deactivate the device.
1298
1299     This will, of course, fail if the device is in use.
1300
1301     """
1302     result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1303     if result.failed:
1304       logger.Error("Can't shutdown drbd device: %s" % result.output)
1305     return not result.failed
1306
1307   @classmethod
1308   def _AssembleNet(cls, minor, net_info, protocol):
1309     """Configure the network part of the device.
1310
1311     This operation can be, in theory, done multiple times, but there
1312     have been cases (in lab testing) in which the network part of the
1313     device had become stuck and couldn't be shut down because activity
1314     from the new peer (also stuck) triggered a timer re-init and
1315     needed remote peer interface shutdown in order to clear. So please
1316     don't change online the net config.
1317
1318     """
1319     lhost, lport, rhost, rport = net_info
1320     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1321                            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1322                            protocol])
1323     if result.failed:
1324       logger.Error("Can't setup network for dbrd device: %s" %
1325                    result.fail_reason)
1326       return False
1327
1328     timeout = time.time() + 10
1329     ok = False
1330     while time.time() < timeout:
1331       info = cls._GetDevInfo(minor)
1332       if not "local_addr" in info or not "remote_addr" in info:
1333         time.sleep(1)
1334         continue
1335       if (info["local_addr"] != (lhost, lport) or
1336           info["remote_addr"] != (rhost, rport)):
1337         time.sleep(1)
1338         continue
1339       ok = True
1340       break
1341     if not ok:
1342       logger.Error("Timeout while configuring network")
1343       return False
1344     return True
1345
1346   @classmethod
1347   def _ShutdownNet(cls, minor):
1348     """Disconnect from the remote peer.
1349
1350     This fails if we don't have a local device.
1351
1352     """
1353     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1354     if result.failed:
1355       logger.Error("Can't shutdown network: %s" % result.output)
1356     return not result.failed
1357
1358   def Assemble(self):
1359     """Assemble the drbd.
1360
1361     Method:
1362       - if we have a local backing device, we bind to it by:
1363         - checking the list of used drbd devices
1364         - check if the local minor use of any of them is our own device
1365         - if yes, abort?
1366         - if not, bind
1367       - if we have a local/remote net info:
1368         - redo the local backing device step for the remote device
1369         - check if any drbd device is using the local port,
1370           if yes abort
1371         - check if any remote drbd device is using the remote
1372           port, if yes abort (for now)
1373         - bind our net port
1374         - bind the remote net port
1375
1376     """
1377     self.Attach()
1378     if self.minor is not None:
1379       logger.Info("Already assembled")
1380       return True
1381
1382     result = super(DRBDev, self).Assemble()
1383     if not result:
1384       return result
1385
1386     minor = self._FindUnusedMinor()
1387     need_localdev_teardown = False
1388     if self._children[0]:
1389       result = self._AssembleLocal(minor, self._children[0].dev_path,
1390                                    self._children[1].dev_path)
1391       if not result:
1392         return False
1393       need_localdev_teardown = True
1394     if self._lhost and self._lport and self._rhost and self._rport:
1395       result = self._AssembleNet(minor,
1396                                  (self._lhost, self._lport,
1397                                   self._rhost, self._rport),
1398                                  "C")
1399       if not result:
1400         if need_localdev_teardown:
1401           # we will ignore failures from this
1402           logger.Error("net setup failed, tearing down local device")
1403           self._ShutdownAll(minor)
1404         return False
1405     self._SetFromMinor(minor)
1406     return True
1407
1408   def Shutdown(self):
1409     """Shutdown the DRBD device.
1410
1411     """
1412     if self.minor is None and not self.Attach():
1413       logger.Info("DRBD device not attached to a device during Shutdown")
1414       return True
1415     if not self._ShutdownAll(self.minor):
1416       return False
1417     self.minor = None
1418     self.dev_path = None
1419     return True
1420
1421   def Attach(self):
1422     """Find a DRBD device which matches our config and attach to it.
1423
1424     In case of partially attached (local device matches but no network
1425     setup), we perform the network attach. If successful, we re-test
1426     the attach if can return success.
1427
1428     """
1429     for minor in self._GetUsedDevs():
1430       info = self._GetDevInfo(minor)
1431       match_l = self._MatchesLocal(info)
1432       match_r = self._MatchesNet(info)
1433       if match_l and match_r:
1434         break
1435       if match_l and not match_r and "local_addr" not in info:
1436         res_r = self._AssembleNet(minor,
1437                                   (self._lhost, self._lport,
1438                                    self._rhost, self._rport),
1439                                   "C")
1440         if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1441           break
1442     else:
1443       minor = None
1444
1445     self._SetFromMinor(minor)
1446     return minor is not None
1447
1448   def Open(self, force=False):
1449     """Make the local state primary.
1450
1451     If the 'force' parameter is given, the '--do-what-I-say' parameter
1452     is given. Since this is a pottentialy dangerous operation, the
1453     force flag should be only given after creation, when it actually
1454     has to be given.
1455
1456     """
1457     if self.minor is None and not self.Attach():
1458       logger.Error("DRBD cannot attach to a device during open")
1459       return False
1460     cmd = ["drbdsetup", self.dev_path, "primary"]
1461     if force:
1462       cmd.append("--do-what-I-say")
1463     result = utils.RunCmd(cmd)
1464     if result.failed:
1465       msg = ("Can't make drbd device primary: %s" % result.output)
1466       logger.Error(msg)
1467       raise errors.BlockDeviceError(msg)
1468
1469   def Close(self):
1470     """Make the local state secondary.
1471
1472     This will, of course, fail if the device is in use.
1473
1474     """
1475     if self.minor is None and not self.Attach():
1476       logger.Info("Instance not attached to a device")
1477       raise errors.BlockDeviceError("Can't find device")
1478     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1479     if result.failed:
1480       msg = ("Can't switch drbd device to"
1481              " secondary: %s" % result.output)
1482       logger.Error(msg)
1483       raise errors.BlockDeviceError(msg)
1484
1485   def SetSyncSpeed(self, kbytes):
1486     """Set the speed of the DRBD syncer.
1487
1488     """
1489     children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1490     if self.minor is None:
1491       logger.Info("Instance not attached to a device")
1492       return False
1493     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1494                            kbytes])
1495     if result.failed:
1496       logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1497     return not result.failed and children_result
1498
1499   def GetSyncStatus(self):
1500     """Returns the sync status of the device.
1501
1502     Returns:
1503      (sync_percent, estimated_time, is_degraded, ldisk)
1504
1505     If sync_percent is None, it means all is ok
1506     If estimated_time is None, it means we can't esimate
1507     the time needed, otherwise it's the time left in seconds.
1508
1509     The ldisk parameter will be returned as True, since the DRBD7
1510     devices have not been converted.
1511
1512     """
1513     if self.minor is None and not self.Attach():
1514       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1515     proc_info = self._MassageProcData(self._GetProcData())
1516     if self.minor not in proc_info:
1517       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1518                                     self.minor)
1519     line = proc_info[self.minor]
1520     match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1521                      " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1522     if match:
1523       sync_percent = float(match.group(1))
1524       hours = int(match.group(2))
1525       minutes = int(match.group(3))
1526       seconds = int(match.group(4))
1527       est_time = hours * 3600 + minutes * 60 + seconds
1528     else:
1529       sync_percent = None
1530       est_time = None
1531     match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1532     if not match:
1533       raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1534                                     self.minor)
1535     client_state = match.group(1)
1536     is_degraded = client_state != "Connected"
1537     return sync_percent, est_time, is_degraded, False
1538
1539   def GetStatus(self):
1540     """Compute the status of the DRBD device
1541
1542     Note that DRBD devices don't have the STATUS_EXISTING state.
1543
1544     """
1545     if self.minor is None and not self.Attach():
1546       return self.STATUS_UNKNOWN
1547
1548     data = self._GetProcData()
1549     match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1550                        self.minor)
1551     for line in data:
1552       mresult = match.match(line)
1553       if mresult:
1554         break
1555     else:
1556       logger.Error("Can't find myself!")
1557       return self.STATUS_UNKNOWN
1558
1559     state = mresult.group(2)
1560     if state == "Primary":
1561       result = self.STATUS_ONLINE
1562     else:
1563       result = self.STATUS_STANDBY
1564
1565     return result
1566
1567   @staticmethod
1568   def _ZeroDevice(device):
1569     """Zero a device.
1570
1571     This writes until we get ENOSPC.
1572
1573     """
1574     f = open(device, "w")
1575     buf = "\0" * 1048576
1576     try:
1577       while True:
1578         f.write(buf)
1579     except IOError, err:
1580       if err.errno != errno.ENOSPC:
1581         raise
1582
1583   @classmethod
1584   def Create(cls, unique_id, children, size):
1585     """Create a new DRBD device.
1586
1587     Since DRBD devices are not created per se, just assembled, this
1588     function just zeroes the meta device.
1589
1590     """
1591     if len(children) != 2:
1592       raise errors.ProgrammerError("Invalid setup for the drbd device")
1593     meta = children[1]
1594     meta.Assemble()
1595     if not meta.Attach():
1596       raise errors.BlockDeviceError("Can't attach to meta device")
1597     if not cls._CheckMetaSize(meta.dev_path):
1598       raise errors.BlockDeviceError("Invalid meta device")
1599     logger.Info("Started zeroing device %s" % meta.dev_path)
1600     cls._ZeroDevice(meta.dev_path)
1601     logger.Info("Done zeroing device %s" % meta.dev_path)
1602     return cls(unique_id, children)
1603
1604   def Remove(self):
1605     """Stub remove for DRBD devices.
1606
1607     """
1608     return self.Shutdown()
1609
1610
1611 class DRBD8(BaseDRBD):
1612   """DRBD v8.x block device.
1613
1614   This implements the local host part of the DRBD device, i.e. it
1615   doesn't do anything to the supposed peer. If you need a fully
1616   connected DRBD pair, you need to use this class on both hosts.
1617
1618   The unique_id for the drbd device is the (local_ip, local_port,
1619   remote_ip, remote_port) tuple, and it must have two children: the
1620   data device and the meta_device. The meta device is checked for
1621   valid size and is zeroed on create.
1622
1623   """
1624   _MAX_MINORS = 255
1625   _PARSE_SHOW = None
1626
1627   def __init__(self, unique_id, children):
1628     if children and children.count(None) > 0:
1629       children = []
1630     super(DRBD8, self).__init__(unique_id, children)
1631     self.major = self._DRBD_MAJOR
1632     [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1633     if kmaj != 8:
1634       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1635                                     " requested ganeti usage: kernel is"
1636                                     " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1637
1638     if len(children) not in (0, 2):
1639       raise ValueError("Invalid configuration data %s" % str(children))
1640     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1641       raise ValueError("Invalid configuration data %s" % str(unique_id))
1642     self._lhost, self._lport, self._rhost, self._rport = unique_id
1643     self.Attach()
1644
1645   @classmethod
1646   def _InitMeta(cls, minor, dev_path):
1647     """Initialize a meta device.
1648
1649     This will not work if the given minor is in use.
1650
1651     """
1652     result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1653                            "v08", dev_path, "0", "create-md"])
1654     if result.failed:
1655       raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1656                                     result.output)
1657
1658   @classmethod
1659   def _FindUnusedMinor(cls):
1660     """Find an unused DRBD device.
1661
1662     This is specific to 8.x as the minors are allocated dynamically,
1663     so non-existing numbers up to a max minor count are actually free.
1664
1665     """
1666     data = cls._GetProcData()
1667
1668     unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1669     used_line = re.compile("^ *([0-9]+): cs:")
1670     highest = None
1671     for line in data:
1672       match = unused_line.match(line)
1673       if match:
1674         return int(match.group(1))
1675       match = used_line.match(line)
1676       if match:
1677         minor = int(match.group(1))
1678         highest = max(highest, minor)
1679     if highest is None: # there are no minors in use at all
1680       return 0
1681     if highest >= cls._MAX_MINORS:
1682       logger.Error("Error: no free drbd minors!")
1683       raise errors.BlockDeviceError("Can't find a free DRBD minor")
1684     return highest + 1
1685
1686   @classmethod
1687   def _IsValidMeta(cls, meta_device):
1688     """Check if the given meta device looks like a valid one.
1689
1690     """
1691     minor = cls._FindUnusedMinor()
1692     minor_path = cls._DevPath(minor)
1693     result = utils.RunCmd(["drbdmeta", minor_path,
1694                            "v08", meta_device, "0",
1695                            "dstate"])
1696     if result.failed:
1697       logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1698       return False
1699     return True
1700
1701   @classmethod
1702   def _GetShowParser(cls):
1703     """Return a parser for `drbd show` output.
1704
1705     This will either create or return an already-create parser for the
1706     output of the command `drbd show`.
1707
1708     """
1709     if cls._PARSE_SHOW is not None:
1710       return cls._PARSE_SHOW
1711
1712     # pyparsing setup
1713     lbrace = pyp.Literal("{").suppress()
1714     rbrace = pyp.Literal("}").suppress()
1715     semi = pyp.Literal(";").suppress()
1716     # this also converts the value to an int
1717     number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1718
1719     comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1720     defa = pyp.Literal("_is_default").suppress()
1721     dbl_quote = pyp.Literal('"').suppress()
1722
1723     keyword = pyp.Word(pyp.alphanums + '-')
1724
1725     # value types
1726     value = pyp.Word(pyp.alphanums + '_-/.:')
1727     quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1728     addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1729                  number)
1730     # meta device, extended syntax
1731     meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1732                   number + pyp.Word(']').suppress())
1733
1734     # a statement
1735     stmt = (~rbrace + keyword + ~lbrace +
1736             (addr_port ^ value ^ quoted ^ meta_value) +
1737             pyp.Optional(defa) + semi +
1738             pyp.Optional(pyp.restOfLine).suppress())
1739
1740     # an entire section
1741     section_name = pyp.Word(pyp.alphas + '_')
1742     section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1743
1744     bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1745     bnf.ignore(comment)
1746
1747     cls._PARSE_SHOW = bnf
1748
1749     return bnf
1750
1751   @classmethod
1752   def _GetShowData(cls, minor):
1753     """Return the `drbdsetup show` data for a minor.
1754
1755     """
1756     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1757     if result.failed:
1758       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1759       return None
1760     return result.stdout
1761
1762   @classmethod
1763   def _GetDevInfo(cls, out):
1764     """Parse details about a given DRBD minor.
1765
1766     This return, if available, the local backing device (as a path)
1767     and the local and remote (ip, port) information from a string
1768     containing the output of the `drbdsetup show` command as returned
1769     by _GetShowData.
1770
1771     """
1772     data = {}
1773     if not out:
1774       return data
1775
1776     bnf = cls._GetShowParser()
1777     # run pyparse
1778
1779     try:
1780       results = bnf.parseString(out)
1781     except pyp.ParseException, err:
1782       raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1783                                     str(err))
1784
1785     # and massage the results into our desired format
1786     for section in results:
1787       sname = section[0]
1788       if sname == "_this_host":
1789         for lst in section[1:]:
1790           if lst[0] == "disk":
1791             data["local_dev"] = lst[1]
1792           elif lst[0] == "meta-disk":
1793             data["meta_dev"] = lst[1]
1794             data["meta_index"] = lst[2]
1795           elif lst[0] == "address":
1796             data["local_addr"] = tuple(lst[1:])
1797       elif sname == "_remote_host":
1798         for lst in section[1:]:
1799           if lst[0] == "address":
1800             data["remote_addr"] = tuple(lst[1:])
1801     return data
1802
1803   def _MatchesLocal(self, info):
1804     """Test if our local config matches with an existing device.
1805
1806     The parameter should be as returned from `_GetDevInfo()`. This
1807     method tests if our local backing device is the same as the one in
1808     the info parameter, in effect testing if we look like the given
1809     device.
1810
1811     """
1812     if self._children:
1813       backend, meta = self._children
1814     else:
1815       backend = meta = None
1816
1817     if backend is not None:
1818       retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1819     else:
1820       retval = ("local_dev" not in info)
1821
1822     if meta is not None:
1823       retval = retval and ("meta_dev" in info and
1824                            info["meta_dev"] == meta.dev_path)
1825       retval = retval and ("meta_index" in info and
1826                            info["meta_index"] == 0)
1827     else:
1828       retval = retval and ("meta_dev" not in info and
1829                            "meta_index" not in info)
1830     return retval
1831
1832   def _MatchesNet(self, info):
1833     """Test if our network config matches with an existing device.
1834
1835     The parameter should be as returned from `_GetDevInfo()`. This
1836     method tests if our network configuration is the same as the one
1837     in the info parameter, in effect testing if we look like the given
1838     device.
1839
1840     """
1841     if (((self._lhost is None and not ("local_addr" in info)) and
1842          (self._rhost is None and not ("remote_addr" in info)))):
1843       return True
1844
1845     if self._lhost is None:
1846       return False
1847
1848     if not ("local_addr" in info and
1849             "remote_addr" in info):
1850       return False
1851
1852     retval = (info["local_addr"] == (self._lhost, self._lport))
1853     retval = (retval and
1854               info["remote_addr"] == (self._rhost, self._rport))
1855     return retval
1856
1857   @classmethod
1858   def _AssembleLocal(cls, minor, backend, meta):
1859     """Configure the local part of a DRBD device.
1860
1861     This is the first thing that must be done on an unconfigured DRBD
1862     device. And it must be done only once.
1863
1864     """
1865     if not cls._IsValidMeta(meta):
1866       return False
1867     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1868                            backend, meta, "0", "-e", "detach",
1869                            "--create-device"])
1870     if result.failed:
1871       logger.Error("Can't attach local disk: %s" % result.output)
1872     return not result.failed
1873
1874   @classmethod
1875   def _AssembleNet(cls, minor, net_info, protocol,
1876                    dual_pri=False, hmac=None, secret=None):
1877     """Configure the network part of the device.
1878
1879     """
1880     lhost, lport, rhost, rport = net_info
1881     if None in net_info:
1882       # we don't want network connection and actually want to make
1883       # sure its shutdown
1884       return cls._ShutdownNet(minor)
1885
1886     args = ["drbdsetup", cls._DevPath(minor), "net",
1887             "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1888             "-A", "discard-zero-changes",
1889             "-B", "consensus",
1890             ]
1891     if dual_pri:
1892       args.append("-m")
1893     if hmac and secret:
1894       args.extend(["-a", hmac, "-x", secret])
1895     result = utils.RunCmd(args)
1896     if result.failed:
1897       logger.Error("Can't setup network for dbrd device: %s" %
1898                    result.fail_reason)
1899       return False
1900
1901     timeout = time.time() + 10
1902     ok = False
1903     while time.time() < timeout:
1904       info = cls._GetDevInfo(cls._GetShowData(minor))
1905       if not "local_addr" in info or not "remote_addr" in info:
1906         time.sleep(1)
1907         continue
1908       if (info["local_addr"] != (lhost, lport) or
1909           info["remote_addr"] != (rhost, rport)):
1910         time.sleep(1)
1911         continue
1912       ok = True
1913       break
1914     if not ok:
1915       logger.Error("Timeout while configuring network")
1916       return False
1917     return True
1918
1919   def AddChildren(self, devices):
1920     """Add a disk to the DRBD device.
1921
1922     """
1923     if self.minor is None:
1924       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1925     if len(devices) != 2:
1926       raise errors.BlockDeviceError("Need two devices for AddChildren")
1927     info = self._GetDevInfo(self._GetShowData(self.minor))
1928     if "local_dev" in info:
1929       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1930     backend, meta = devices
1931     if backend.dev_path is None or meta.dev_path is None:
1932       raise errors.BlockDeviceError("Children not ready during AddChildren")
1933     backend.Open()
1934     meta.Open()
1935     if not self._CheckMetaSize(meta.dev_path):
1936       raise errors.BlockDeviceError("Invalid meta device size")
1937     self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1938     if not self._IsValidMeta(meta.dev_path):
1939       raise errors.BlockDeviceError("Cannot initalize meta device")
1940
1941     if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1942       raise errors.BlockDeviceError("Can't attach to local storage")
1943     self._children = devices
1944
1945   def RemoveChildren(self, devices):
1946     """Detach the drbd device from local storage.
1947
1948     """
1949     if self.minor is None:
1950       raise errors.BlockDeviceError("Can't attach to drbd8 during"
1951                                     " RemoveChildren")
1952     # early return if we don't actually have backing storage
1953     info = self._GetDevInfo(self._GetShowData(self.minor))
1954     if "local_dev" not in info:
1955       return
1956     if len(self._children) != 2:
1957       raise errors.BlockDeviceError("We don't have two children: %s" %
1958                                     self._children)
1959     if self._children.count(None) == 2: # we don't actually have children :)
1960       logger.Error("Requested detach while detached")
1961       return
1962     if len(devices) != 2:
1963       raise errors.BlockDeviceError("We need two children in RemoveChildren")
1964     for child, dev in zip(self._children, devices):
1965       if dev != child.dev_path:
1966         raise errors.BlockDeviceError("Mismatch in local storage"
1967                                       " (%s != %s) in RemoveChildren" %
1968                                       (dev, child.dev_path))
1969
1970     if not self._ShutdownLocal(self.minor):
1971       raise errors.BlockDeviceError("Can't detach from local storage")
1972     self._children = []
1973
1974   def SetSyncSpeed(self, kbytes):
1975     """Set the speed of the DRBD syncer.
1976
1977     """
1978     children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1979     if self.minor is None:
1980       logger.Info("Instance not attached to a device")
1981       return False
1982     result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1983                            kbytes])
1984     if result.failed:
1985       logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1986     return not result.failed and children_result
1987
1988   def GetSyncStatus(self):
1989     """Returns the sync status of the device.
1990
1991     Returns:
1992      (sync_percent, estimated_time, is_degraded)
1993
1994     If sync_percent is None, it means all is ok
1995     If estimated_time is None, it means we can't esimate
1996     the time needed, otherwise it's the time left in seconds.
1997
1998
1999     We set the is_degraded parameter to True on two conditions:
2000     network not connected or local disk missing.
2001
2002     We compute the ldisk parameter based on wheter we have a local
2003     disk or not.
2004
2005     """
2006     if self.minor is None and not self.Attach():
2007       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
2008     proc_info = self._MassageProcData(self._GetProcData())
2009     if self.minor not in proc_info:
2010       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
2011                                     self.minor)
2012     line = proc_info[self.minor]
2013     match = re.match("^.*sync'ed: *([0-9.]+)%.*"
2014                      " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
2015     if match:
2016       sync_percent = float(match.group(1))
2017       hours = int(match.group(2))
2018       minutes = int(match.group(3))
2019       seconds = int(match.group(4))
2020       est_time = hours * 3600 + minutes * 60 + seconds
2021     else:
2022       sync_percent = None
2023       est_time = None
2024     match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
2025     if not match:
2026       raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2027                                     self.minor)
2028     client_state = match.group(1)
2029     local_disk_state = match.group(2)
2030     ldisk = local_disk_state != "UpToDate"
2031     is_degraded = client_state != "Connected"
2032     return sync_percent, est_time, is_degraded or ldisk, ldisk
2033
2034   def GetStatus(self):
2035     """Compute the status of the DRBD device
2036
2037     Note that DRBD devices don't have the STATUS_EXISTING state.
2038
2039     """
2040     if self.minor is None and not self.Attach():
2041       return self.STATUS_UNKNOWN
2042
2043     data = self._GetProcData()
2044     match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2045                        self.minor)
2046     for line in data:
2047       mresult = match.match(line)
2048       if mresult:
2049         break
2050     else:
2051       logger.Error("Can't find myself!")
2052       return self.STATUS_UNKNOWN
2053
2054     state = mresult.group(2)
2055     if state == "Primary":
2056       result = self.STATUS_ONLINE
2057     else:
2058       result = self.STATUS_STANDBY
2059
2060     return result
2061
2062   def Open(self, force=False):
2063     """Make the local state primary.
2064
2065     If the 'force' parameter is given, the '--do-what-I-say' parameter
2066     is given. Since this is a pottentialy dangerous operation, the
2067     force flag should be only given after creation, when it actually
2068     has to be given.
2069
2070     """
2071     if self.minor is None and not self.Attach():
2072       logger.Error("DRBD cannot attach to a device during open")
2073       return False
2074     cmd = ["drbdsetup", self.dev_path, "primary"]
2075     if force:
2076       cmd.append("-o")
2077     result = utils.RunCmd(cmd)
2078     if result.failed:
2079       msg = ("Can't make drbd device primary: %s" % result.output)
2080       logger.Error(msg)
2081       raise errors.BlockDeviceError(msg)
2082
2083   def Close(self):
2084     """Make the local state secondary.
2085
2086     This will, of course, fail if the device is in use.
2087
2088     """
2089     if self.minor is None and not self.Attach():
2090       logger.Info("Instance not attached to a device")
2091       raise errors.BlockDeviceError("Can't find device")
2092     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2093     if result.failed:
2094       msg = ("Can't switch drbd device to"
2095              " secondary: %s" % result.output)
2096       logger.Error(msg)
2097       raise errors.BlockDeviceError(msg)
2098
2099   def Attach(self):
2100     """Find a DRBD device which matches our config and attach to it.
2101
2102     In case of partially attached (local device matches but no network
2103     setup), we perform the network attach. If successful, we re-test
2104     the attach if can return success.
2105
2106     """
2107     for minor in self._GetUsedDevs():
2108       info = self._GetDevInfo(self._GetShowData(minor))
2109       match_l = self._MatchesLocal(info)
2110       match_r = self._MatchesNet(info)
2111       if match_l and match_r:
2112         break
2113       if match_l and not match_r and "local_addr" not in info:
2114         res_r = self._AssembleNet(minor,
2115                                   (self._lhost, self._lport,
2116                                    self._rhost, self._rport),
2117                                   "C")
2118         if res_r:
2119           if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2120             break
2121       # the weakest case: we find something that is only net attached
2122       # even though we were passed some children at init time
2123       if match_r and "local_dev" not in info:
2124         break
2125       if match_l and not match_r and "local_addr" in info:
2126         # strange case - the device network part points to somewhere
2127         # else, even though its local storage is ours; as we own the
2128         # drbd space, we try to disconnect from the remote peer and
2129         # reconnect to our correct one
2130         if not self._ShutdownNet(minor):
2131           raise errors.BlockDeviceError("Device has correct local storage,"
2132                                         " wrong remote peer and is unable to"
2133                                         " disconnect in order to attach to"
2134                                         " the correct peer")
2135         # note: _AssembleNet also handles the case when we don't want
2136         # local storage (i.e. one or more of the _[lr](host|port) is
2137         # None)
2138         if (self._AssembleNet(minor, (self._lhost, self._lport,
2139                                       self._rhost, self._rport), "C") and
2140             self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2141           break
2142
2143     else:
2144       minor = None
2145
2146     self._SetFromMinor(minor)
2147     return minor is not None
2148
2149   def Assemble(self):
2150     """Assemble the drbd.
2151
2152     Method:
2153       - if we have a local backing device, we bind to it by:
2154         - checking the list of used drbd devices
2155         - check if the local minor use of any of them is our own device
2156         - if yes, abort?
2157         - if not, bind
2158       - if we have a local/remote net info:
2159         - redo the local backing device step for the remote device
2160         - check if any drbd device is using the local port,
2161           if yes abort
2162         - check if any remote drbd device is using the remote
2163           port, if yes abort (for now)
2164         - bind our net port
2165         - bind the remote net port
2166
2167     """
2168     self.Attach()
2169     if self.minor is not None:
2170       logger.Info("Already assembled")
2171       return True
2172
2173     result = super(DRBD8, self).Assemble()
2174     if not result:
2175       return result
2176
2177     minor = self._FindUnusedMinor()
2178     need_localdev_teardown = False
2179     if self._children and self._children[0] and self._children[1]:
2180       result = self._AssembleLocal(minor, self._children[0].dev_path,
2181                                    self._children[1].dev_path)
2182       if not result:
2183         return False
2184       need_localdev_teardown = True
2185     if self._lhost and self._lport and self._rhost and self._rport:
2186       result = self._AssembleNet(minor,
2187                                  (self._lhost, self._lport,
2188                                   self._rhost, self._rport),
2189                                  "C")
2190       if not result:
2191         if need_localdev_teardown:
2192           # we will ignore failures from this
2193           logger.Error("net setup failed, tearing down local device")
2194           self._ShutdownAll(minor)
2195         return False
2196     self._SetFromMinor(minor)
2197     return True
2198
2199   @classmethod
2200   def _ShutdownLocal(cls, minor):
2201     """Detach from the local device.
2202
2203     I/Os will continue to be served from the remote device. If we
2204     don't have a remote device, this operation will fail.
2205
2206     """
2207     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2208     if result.failed:
2209       logger.Error("Can't detach local device: %s" % result.output)
2210     return not result.failed
2211
2212   @classmethod
2213   def _ShutdownNet(cls, minor):
2214     """Disconnect from the remote peer.
2215
2216     This fails if we don't have a local device.
2217
2218     """
2219     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2220     if result.failed:
2221       logger.Error("Can't shutdown network: %s" % result.output)
2222     return not result.failed
2223
2224   @classmethod
2225   def _ShutdownAll(cls, minor):
2226     """Deactivate the device.
2227
2228     This will, of course, fail if the device is in use.
2229
2230     """
2231     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2232     if result.failed:
2233       logger.Error("Can't shutdown drbd device: %s" % result.output)
2234     return not result.failed
2235
2236   def Shutdown(self):
2237     """Shutdown the DRBD device.
2238
2239     """
2240     if self.minor is None and not self.Attach():
2241       logger.Info("DRBD device not attached to a device during Shutdown")
2242       return True
2243     if not self._ShutdownAll(self.minor):
2244       return False
2245     self.minor = None
2246     self.dev_path = None
2247     return True
2248
2249   def Remove(self):
2250     """Stub remove for DRBD devices.
2251
2252     """
2253     return self.Shutdown()
2254
2255   @classmethod
2256   def Create(cls, unique_id, children, size):
2257     """Create a new DRBD8 device.
2258
2259     Since DRBD devices are not created per se, just assembled, this
2260     function only initializes the metadata.
2261
2262     """
2263     if len(children) != 2:
2264       raise errors.ProgrammerError("Invalid setup for the drbd device")
2265     meta = children[1]
2266     meta.Assemble()
2267     if not meta.Attach():
2268       raise errors.BlockDeviceError("Can't attach to meta device")
2269     if not cls._CheckMetaSize(meta.dev_path):
2270       raise errors.BlockDeviceError("Invalid meta device size")
2271     cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2272     if not cls._IsValidMeta(meta.dev_path):
2273       raise errors.BlockDeviceError("Cannot initalize meta device")
2274     return cls(unique_id, children)
2275
2276
2277 DEV_MAP = {
2278   constants.LD_LV: LogicalVolume,
2279   constants.LD_MD_R1: MDRaid1,
2280   constants.LD_DRBD7: DRBDev,
2281   constants.LD_DRBD8: DRBD8,
2282   }
2283
2284
2285 def FindDevice(dev_type, unique_id, children):
2286   """Search for an existing, assembled device.
2287
2288   This will succeed only if the device exists and is assembled, but it
2289   does not do any actions in order to activate the device.
2290
2291   """
2292   if dev_type not in DEV_MAP:
2293     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2294   device = DEV_MAP[dev_type](unique_id, children)
2295   if not device.Attach():
2296     return None
2297   return  device
2298
2299
2300 def AttachOrAssemble(dev_type, unique_id, children):
2301   """Try to attach or assemble an existing device.
2302
2303   This will attach to an existing assembled device or will assemble
2304   the device, as needed, to bring it fully up.
2305
2306   """
2307   if dev_type not in DEV_MAP:
2308     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2309   device = DEV_MAP[dev_type](unique_id, children)
2310   if not device.Attach():
2311     device.Assemble()
2312     if not device.Attach():
2313       raise errors.BlockDeviceError("Can't find a valid block device for"
2314                                     " %s/%s/%s" %
2315                                     (dev_type, unique_id, children))
2316   return device
2317
2318
2319 def Create(dev_type, unique_id, children, size):
2320   """Create a device.
2321
2322   """
2323   if dev_type not in DEV_MAP:
2324     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2325   device = DEV_MAP[dev_type].Create(unique_id, children, size)
2326   return device