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