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