Fill the 'call' attribute of offline rpc results
[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 import os
29 import logging
30
31 from ganeti import utils
32 from ganeti import errors
33 from ganeti import constants
34
35
36 class BlockDev(object):
37   """Block device abstract class.
38
39   A block device can be in the following states:
40     - not existing on the system, and by `Create()` it goes into:
41     - existing but not setup/not active, and by `Assemble()` goes into:
42     - active read-write and by `Open()` it goes into
43     - online (=used, or ready for use)
44
45   A device can also be online but read-only, however we are not using
46   the readonly state (LV has it, if needed in the future) and we are
47   usually looking at this like at a stack, so it's easier to
48   conceptualise the transition from not-existing to online and back
49   like a linear one.
50
51   The many different states of the device are due to the fact that we
52   need to cover many device types:
53     - logical volumes are created, lvchange -a y $lv, and used
54     - drbd devices are attached to a local disk/remote peer and made primary
55
56   A block device is identified by three items:
57     - the /dev path of the device (dynamic)
58     - a unique ID of the device (static)
59     - it's major/minor pair (dynamic)
60
61   Not all devices implement both the first two as distinct items. LVM
62   logical volumes have their unique ID (the pair volume group, logical
63   volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
64   the /dev path is again dynamic and the unique id is the pair (host1,
65   dev1), (host2, dev2).
66
67   You can get to a device in two ways:
68     - creating the (real) device, which returns you
69       an attached instance (lvcreate)
70     - attaching of a python instance to an existing (real) device
71
72   The second point, the attachement to a device, is different
73   depending on whether the device is assembled or not. At init() time,
74   we search for a device with the same unique_id as us. If found,
75   good. It also means that the device is already assembled. If not,
76   after assembly we'll have our correct major/minor.
77
78   """
79   def __init__(self, unique_id, children):
80     self._children = children
81     self.dev_path = None
82     self.unique_id = unique_id
83     self.major = None
84     self.minor = None
85     self.attached = False
86
87   def Assemble(self):
88     """Assemble the device from its components.
89
90     Implementations of this method by child classes must ensure that:
91       - after the device has been assembled, it knows its major/minor
92         numbers; this allows other devices (usually parents) to probe
93         correctly for their children
94       - calling this method on an existing, in-use device is safe
95       - if the device is already configured (and in an OK state),
96         this method is idempotent
97
98     """
99     return True
100
101   def Attach(self):
102     """Find a device which matches our config and attach to it.
103
104     """
105     raise NotImplementedError
106
107   def Close(self):
108     """Notifies that the device will no longer be used for I/O.
109
110     """
111     raise NotImplementedError
112
113   @classmethod
114   def Create(cls, unique_id, children, size):
115     """Create the device.
116
117     If the device cannot be created, it will return None
118     instead. Error messages go to the logging system.
119
120     Note that for some devices, the unique_id is used, and for other,
121     the children. The idea is that these two, taken together, are
122     enough for both creation and assembly (later).
123
124     """
125     raise NotImplementedError
126
127   def Remove(self):
128     """Remove this device.
129
130     This makes sense only for some of the device types: LV and file
131     storeage. Also note that if the device can't attach, the removal
132     can't be completed.
133
134     """
135     raise NotImplementedError
136
137   def Rename(self, new_id):
138     """Rename this device.
139
140     This may or may not make sense for a given device type.
141
142     """
143     raise NotImplementedError
144
145   def Open(self, force=False):
146     """Make the device ready for use.
147
148     This makes the device ready for I/O. For now, just the DRBD
149     devices need this.
150
151     The force parameter signifies that if the device has any kind of
152     --force thing, it should be used, we know what we are doing.
153
154     """
155     raise NotImplementedError
156
157   def Shutdown(self):
158     """Shut down the device, freeing its children.
159
160     This undoes the `Assemble()` work, except for the child
161     assembling; as such, the children on the device are still
162     assembled after this call.
163
164     """
165     raise NotImplementedError
166
167   def SetSyncSpeed(self, speed):
168     """Adjust the sync speed of the mirror.
169
170     In case this is not a mirroring device, this is no-op.
171
172     """
173     result = True
174     if self._children:
175       for child in self._children:
176         result = result and child.SetSyncSpeed(speed)
177     return result
178
179   def GetSyncStatus(self):
180     """Returns the sync status of the device.
181
182     If this device is a mirroring device, this function returns the
183     status of the mirror.
184
185     If sync_percent is None, it means the device is not syncing.
186
187     If estimated_time is None, it means we can't estimate
188     the time needed, otherwise it's the time left in seconds.
189
190     If is_degraded is True, it means the device is missing
191     redundancy. This is usually a sign that something went wrong in
192     the device setup, if sync_percent is None.
193
194     The ldisk parameter represents the degradation of the local
195     data. This is only valid for some devices, the rest will always
196     return False (not degraded).
197
198     @rtype: tuple
199     @return: (sync_percent, estimated_time, is_degraded, ldisk)
200
201     """
202     return None, None, False, False
203
204
205   def CombinedSyncStatus(self):
206     """Calculate the mirror status recursively for our children.
207
208     The return value is the same as for `GetSyncStatus()` except the
209     minimum percent and maximum time are calculated across our
210     children.
211
212     """
213     min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
214     if self._children:
215       for child in self._children:
216         c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
217         if min_percent is None:
218           min_percent = c_percent
219         elif c_percent is not None:
220           min_percent = min(min_percent, c_percent)
221         if max_time is None:
222           max_time = c_time
223         elif c_time is not None:
224           max_time = max(max_time, c_time)
225         is_degraded = is_degraded or c_degraded
226         ldisk = ldisk or c_ldisk
227     return min_percent, max_time, is_degraded, ldisk
228
229
230   def SetInfo(self, text):
231     """Update metadata with info text.
232
233     Only supported for some device types.
234
235     """
236     for child in self._children:
237       child.SetInfo(text)
238
239   def Grow(self, amount):
240     """Grow the block device.
241
242     @param amount: the amount (in mebibytes) to grow with
243
244     """
245     raise NotImplementedError
246
247   def __repr__(self):
248     return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
249             (self.__class__, self.unique_id, self._children,
250              self.major, self.minor, self.dev_path))
251
252
253 class LogicalVolume(BlockDev):
254   """Logical Volume block device.
255
256   """
257   def __init__(self, unique_id, children):
258     """Attaches to a LV device.
259
260     The unique_id is a tuple (vg_name, lv_name)
261
262     """
263     super(LogicalVolume, self).__init__(unique_id, children)
264     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
265       raise ValueError("Invalid configuration data %s" % str(unique_id))
266     self._vg_name, self._lv_name = unique_id
267     self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
268     self._degraded = True
269     self.major = self.minor = None
270     self.Attach()
271
272   @classmethod
273   def Create(cls, unique_id, children, size):
274     """Create a new logical volume.
275
276     """
277     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
278       raise errors.ProgrammerError("Invalid configuration data %s" %
279                                    str(unique_id))
280     vg_name, lv_name = unique_id
281     pvs_info = cls.GetPVInfo(vg_name)
282     if not pvs_info:
283       raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
284                                     vg_name)
285     pvs_info.sort()
286     pvs_info.reverse()
287
288     pvlist = [ pv[1] for pv in pvs_info ]
289     free_size = sum([ pv[0] for pv in pvs_info ])
290
291     # The size constraint should have been checked from the master before
292     # calling the create function.
293     if free_size < size:
294       raise errors.BlockDeviceError("Not enough free space: required %s,"
295                                     " available %s" % (size, free_size))
296     result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
297                            vg_name] + pvlist)
298     if result.failed:
299       raise errors.BlockDeviceError("LV create failed (%s): %s" %
300                                     (result.fail_reason, result.output))
301     return LogicalVolume(unique_id, children)
302
303   @staticmethod
304   def GetPVInfo(vg_name):
305     """Get the free space info for PVs in a volume group.
306
307     @param vg_name: the volume group name
308
309     @rtype: list
310     @return: list of tuples (free_space, name) with free_space in mebibytes
311
312     """
313     command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
314                "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
315                "--separator=:"]
316     result = utils.RunCmd(command)
317     if result.failed:
318       logging.error("Can't get the PV information: %s - %s",
319                     result.fail_reason, result.output)
320       return None
321     data = []
322     for line in result.stdout.splitlines():
323       fields = line.strip().split(':')
324       if len(fields) != 4:
325         logging.error("Can't parse pvs output: line '%s'", line)
326         return None
327       # skip over pvs from another vg or ones which are not allocatable
328       if fields[1] != vg_name or fields[3][0] != 'a':
329         continue
330       data.append((float(fields[2]), fields[0]))
331
332     return data
333
334   def Remove(self):
335     """Remove this logical volume.
336
337     """
338     if not self.minor and not self.Attach():
339       # the LV does not exist
340       return True
341     result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
342                            (self._vg_name, self._lv_name)])
343     if result.failed:
344       logging.error("Can't lvremove: %s - %s",
345                     result.fail_reason, result.output)
346
347     return not result.failed
348
349   def Rename(self, new_id):
350     """Rename this logical volume.
351
352     """
353     if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
354       raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
355     new_vg, new_name = new_id
356     if new_vg != self._vg_name:
357       raise errors.ProgrammerError("Can't move a logical volume across"
358                                    " volume groups (from %s to to %s)" %
359                                    (self._vg_name, new_vg))
360     result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
361     if result.failed:
362       raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
363                                     result.output)
364     self._lv_name = new_name
365     self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
366
367   def Attach(self):
368     """Attach to an existing LV.
369
370     This method will try to see if an existing and active LV exists
371     which matches our name. If so, its major/minor will be
372     recorded.
373
374     """
375     self.attached = False
376     result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
377                            "-olv_attr,lv_kernel_major,lv_kernel_minor",
378                            self.dev_path])
379     if result.failed:
380       logging.error("Can't find LV %s: %s, %s",
381                     self.dev_path, result.fail_reason, result.output)
382       return False
383     out = result.stdout.strip().rstrip(',')
384     out = out.split(",")
385     if len(out) != 3:
386       logging.error("Can't parse LVS output, len(%s) != 3", str(out))
387       return False
388
389     status, major, minor = out[:3]
390     if len(status) != 6:
391       logging.error("lvs lv_attr is not 6 characters (%s)", status)
392       return False
393
394     try:
395       major = int(major)
396       minor = int(minor)
397     except ValueError, err:
398       logging.error("lvs major/minor cannot be parsed: %s", str(err))
399
400     self.major = major
401     self.minor = minor
402     self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
403                                       # storage
404     self.attached = True
405     return True
406
407   def Assemble(self):
408     """Assemble the device.
409
410     We alway run `lvchange -ay` on the LV to ensure it's active before
411     use, as there were cases when xenvg was not active after boot
412     (also possibly after disk issues).
413
414     """
415     result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
416     if result.failed:
417       logging.error("Can't activate lv %s: %s", self.dev_path, result.output)
418       return False
419     return self.Attach()
420
421   def Shutdown(self):
422     """Shutdown the device.
423
424     This is a no-op for the LV device type, as we don't deactivate the
425     volumes on shutdown.
426
427     """
428     return True
429
430   def GetSyncStatus(self):
431     """Returns the sync status of the device.
432
433     If this device is a mirroring device, this function returns the
434     status of the mirror.
435
436     For logical volumes, sync_percent and estimated_time are always
437     None (no recovery in progress, as we don't handle the mirrored LV
438     case). The is_degraded parameter is the inverse of the ldisk
439     parameter.
440
441     For the ldisk parameter, we check if the logical volume has the
442     'virtual' type, which means it's not backed by existing storage
443     anymore (read from it return I/O error). This happens after a
444     physical disk failure and subsequent 'vgreduce --removemissing' on
445     the volume group.
446
447     The status was already read in Attach, so we just return it.
448
449     @rtype: tuple
450     @return: (sync_percent, estimated_time, is_degraded, ldisk)
451
452     """
453     return None, None, self._degraded, self._degraded
454
455   def Open(self, force=False):
456     """Make the device ready for I/O.
457
458     This is a no-op for the LV device type.
459
460     """
461     pass
462
463   def Close(self):
464     """Notifies that the device will no longer be used for I/O.
465
466     This is a no-op for the LV device type.
467
468     """
469     pass
470
471   def Snapshot(self, size):
472     """Create a snapshot copy of an lvm block device.
473
474     """
475     snap_name = self._lv_name + ".snap"
476
477     # remove existing snapshot if found
478     snap = LogicalVolume((self._vg_name, snap_name), None)
479     snap.Remove()
480
481     pvs_info = self.GetPVInfo(self._vg_name)
482     if not pvs_info:
483       raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
484                                     self._vg_name)
485     pvs_info.sort()
486     pvs_info.reverse()
487     free_size, pv_name = pvs_info[0]
488     if free_size < size:
489       raise errors.BlockDeviceError("Not enough free space: required %s,"
490                                     " available %s" % (size, free_size))
491
492     result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
493                            "-n%s" % snap_name, self.dev_path])
494     if result.failed:
495       raise errors.BlockDeviceError("command: %s error: %s - %s" %
496                                     (result.cmd, result.fail_reason,
497                                      result.output))
498
499     return snap_name
500
501   def SetInfo(self, text):
502     """Update metadata with info text.
503
504     """
505     BlockDev.SetInfo(self, text)
506
507     # Replace invalid characters
508     text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
509     text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
510
511     # Only up to 128 characters are allowed
512     text = text[:128]
513
514     result = utils.RunCmd(["lvchange", "--addtag", text,
515                            self.dev_path])
516     if result.failed:
517       raise errors.BlockDeviceError("Command: %s error: %s - %s" %
518                                     (result.cmd, result.fail_reason,
519                                      result.output))
520   def Grow(self, amount):
521     """Grow the logical volume.
522
523     """
524     # we try multiple algorithms since the 'best' ones might not have
525     # space available in the right place, but later ones might (since
526     # they have less constraints); also note that only recent LVM
527     # supports 'cling'
528     for alloc_policy in "contiguous", "cling", "normal":
529       result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
530                              "-L", "+%dm" % amount, self.dev_path])
531       if not result.failed:
532         return
533     raise errors.BlockDeviceError("Can't grow LV %s: %s" %
534                                   (self.dev_path, result.output))
535
536
537 class DRBD8Status(object):
538   """A DRBD status representation class.
539
540   Note that this doesn't support unconfigured devices (cs:Unconfigured).
541
542   """
543   UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
544   LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+st:([^/]+)/(\S+)"
545                        "\s+ds:([^/]+)/(\S+)\s+.*$")
546   SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
547                        "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
548
549   def __init__(self, procline):
550     u = self.UNCONF_RE.match(procline)
551     if u:
552       self.cstatus = "Unconfigured"
553       self.lrole = self.rrole = self.ldisk = self.rdisk = None
554     else:
555       m = self.LINE_RE.match(procline)
556       if not m:
557         raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
558       self.cstatus = m.group(1)
559       self.lrole = m.group(2)
560       self.rrole = m.group(3)
561       self.ldisk = m.group(4)
562       self.rdisk = m.group(5)
563
564     # end reading of data from the LINE_RE or UNCONF_RE
565
566     self.is_standalone = self.cstatus == "StandAlone"
567     self.is_wfconn = self.cstatus == "WFConnection"
568     self.is_connected = self.cstatus == "Connected"
569     self.is_primary = self.lrole == "Primary"
570     self.is_secondary = self.lrole == "Secondary"
571     self.peer_primary = self.rrole == "Primary"
572     self.peer_secondary = self.rrole == "Secondary"
573     self.both_primary = self.is_primary and self.peer_primary
574     self.both_secondary = self.is_secondary and self.peer_secondary
575
576     self.is_diskless = self.ldisk == "Diskless"
577     self.is_disk_uptodate = self.ldisk == "UpToDate"
578
579     self.is_in_resync = self.cstatus in ("SyncSource", "SyncTarget")
580     self.is_in_use = self.cstatus != "Unconfigured"
581
582     m = self.SYNC_RE.match(procline)
583     if m:
584       self.sync_percent = float(m.group(1))
585       hours = int(m.group(2))
586       minutes = int(m.group(3))
587       seconds = int(m.group(4))
588       self.est_time = hours * 3600 + minutes * 60 + seconds
589     else:
590       self.sync_percent = None
591       self.est_time = None
592
593     self.is_sync_target = self.peer_sync_source = self.cstatus == "SyncTarget"
594     self.peer_sync_target = self.is_sync_source = self.cstatus == "SyncSource"
595     self.is_resync = self.is_sync_target or self.is_sync_source
596
597
598 class BaseDRBD(BlockDev):
599   """Base DRBD class.
600
601   This class contains a few bits of common functionality between the
602   0.7 and 8.x versions of DRBD.
603
604   """
605   _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
606                            r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
607
608   _DRBD_MAJOR = 147
609   _ST_UNCONFIGURED = "Unconfigured"
610   _ST_WFCONNECTION = "WFConnection"
611   _ST_CONNECTED = "Connected"
612
613   _STATUS_FILE = "/proc/drbd"
614
615   @staticmethod
616   def _GetProcData(filename=_STATUS_FILE):
617     """Return data from /proc/drbd.
618
619     """
620     stat = open(filename, "r")
621     try:
622       data = stat.read().splitlines()
623     finally:
624       stat.close()
625     if not data:
626       raise errors.BlockDeviceError("Can't read any data from %s" % filename)
627     return data
628
629   @staticmethod
630   def _MassageProcData(data):
631     """Transform the output of _GetProdData into a nicer form.
632
633     @return: a dictionary of minor: joined lines from /proc/drbd
634         for that minor
635
636     """
637     lmatch = re.compile("^ *([0-9]+):.*$")
638     results = {}
639     old_minor = old_line = None
640     for line in data:
641       lresult = lmatch.match(line)
642       if lresult is not None:
643         if old_minor is not None:
644           results[old_minor] = old_line
645         old_minor = int(lresult.group(1))
646         old_line = line
647       else:
648         if old_minor is not None:
649           old_line += " " + line.strip()
650     # add last line
651     if old_minor is not None:
652       results[old_minor] = old_line
653     return results
654
655   @classmethod
656   def _GetVersion(cls):
657     """Return the DRBD version.
658
659     This will return a dict with keys:
660       - k_major
661       - k_minor
662       - k_point
663       - api
664       - proto
665       - proto2 (only on drbd > 8.2.X)
666
667     """
668     proc_data = cls._GetProcData()
669     first_line = proc_data[0].strip()
670     version = cls._VERSION_RE.match(first_line)
671     if not version:
672       raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
673                                     first_line)
674
675     values = version.groups()
676     retval = {'k_major': int(values[0]),
677               'k_minor': int(values[1]),
678               'k_point': int(values[2]),
679               'api': int(values[3]),
680               'proto': int(values[4]),
681              }
682     if values[5] is not None:
683       retval['proto2'] = values[5]
684
685     return retval
686
687   @staticmethod
688   def _DevPath(minor):
689     """Return the path to a drbd device for a given minor.
690
691     """
692     return "/dev/drbd%d" % minor
693
694   @classmethod
695   def GetUsedDevs(cls):
696     """Compute the list of used DRBD devices.
697
698     """
699     data = cls._GetProcData()
700
701     used_devs = {}
702     valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
703     for line in data:
704       match = valid_line.match(line)
705       if not match:
706         continue
707       minor = int(match.group(1))
708       state = match.group(2)
709       if state == cls._ST_UNCONFIGURED:
710         continue
711       used_devs[minor] = state, line
712
713     return used_devs
714
715   def _SetFromMinor(self, minor):
716     """Set our parameters based on the given minor.
717
718     This sets our minor variable and our dev_path.
719
720     """
721     if minor is None:
722       self.minor = self.dev_path = None
723       self.attached = False
724     else:
725       self.minor = minor
726       self.dev_path = self._DevPath(minor)
727       self.attached = True
728
729   @staticmethod
730   def _CheckMetaSize(meta_device):
731     """Check if the given meta device looks like a valid one.
732
733     This currently only check the size, which must be around
734     128MiB.
735
736     """
737     result = utils.RunCmd(["blockdev", "--getsize", meta_device])
738     if result.failed:
739       logging.error("Failed to get device size: %s - %s",
740                     result.fail_reason, result.output)
741       return False
742     try:
743       sectors = int(result.stdout)
744     except ValueError:
745       logging.error("Invalid output from blockdev: '%s'", result.stdout)
746       return False
747     bytes = sectors * 512
748     if bytes < 128 * 1024 * 1024: # less than 128MiB
749       logging.error("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
750       return False
751     if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
752       logging.error("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
753       return False
754     return True
755
756   def Rename(self, new_id):
757     """Rename a device.
758
759     This is not supported for drbd devices.
760
761     """
762     raise errors.ProgrammerError("Can't rename a drbd device")
763
764
765 class DRBD8(BaseDRBD):
766   """DRBD v8.x block device.
767
768   This implements the local host part of the DRBD device, i.e. it
769   doesn't do anything to the supposed peer. If you need a fully
770   connected DRBD pair, you need to use this class on both hosts.
771
772   The unique_id for the drbd device is the (local_ip, local_port,
773   remote_ip, remote_port) tuple, and it must have two children: the
774   data device and the meta_device. The meta device is checked for
775   valid size and is zeroed on create.
776
777   """
778   _MAX_MINORS = 255
779   _PARSE_SHOW = None
780
781   # timeout constants
782   _NET_RECONFIG_TIMEOUT = 60
783
784   def __init__(self, unique_id, children):
785     if children and children.count(None) > 0:
786       children = []
787     super(DRBD8, self).__init__(unique_id, children)
788     self.major = self._DRBD_MAJOR
789     version = self._GetVersion()
790     if version['k_major'] != 8 :
791       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
792                                     " requested ganeti usage: kernel is"
793                                     " %s.%s, ganeti wants 8.x" %
794                                     (version['k_major'], version['k_minor']))
795
796     if len(children) not in (0, 2):
797       raise ValueError("Invalid configuration data %s" % str(children))
798     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
799       raise ValueError("Invalid configuration data %s" % str(unique_id))
800     (self._lhost, self._lport,
801      self._rhost, self._rport,
802      self._aminor, self._secret) = unique_id
803     if (self._lhost is not None and self._lhost == self._rhost and
804         self._lport == self._rport):
805       raise ValueError("Invalid configuration data, same local/remote %s" %
806                        (unique_id,))
807     self.Attach()
808
809   @classmethod
810   def _InitMeta(cls, minor, dev_path):
811     """Initialize a meta device.
812
813     This will not work if the given minor is in use.
814
815     """
816     result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
817                            "v08", dev_path, "0", "create-md"])
818     if result.failed:
819       raise errors.BlockDeviceError("Can't initialize meta device: %s" %
820                                     result.output)
821
822   @classmethod
823   def _FindUnusedMinor(cls):
824     """Find an unused DRBD device.
825
826     This is specific to 8.x as the minors are allocated dynamically,
827     so non-existing numbers up to a max minor count are actually free.
828
829     """
830     data = cls._GetProcData()
831
832     unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
833     used_line = re.compile("^ *([0-9]+): cs:")
834     highest = None
835     for line in data:
836       match = unused_line.match(line)
837       if match:
838         return int(match.group(1))
839       match = used_line.match(line)
840       if match:
841         minor = int(match.group(1))
842         highest = max(highest, minor)
843     if highest is None: # there are no minors in use at all
844       return 0
845     if highest >= cls._MAX_MINORS:
846       logging.error("Error: no free drbd minors!")
847       raise errors.BlockDeviceError("Can't find a free DRBD minor")
848     return highest + 1
849
850   @classmethod
851   def _IsValidMeta(cls, meta_device):
852     """Check if the given meta device looks like a valid one.
853
854     """
855     minor = cls._FindUnusedMinor()
856     minor_path = cls._DevPath(minor)
857     result = utils.RunCmd(["drbdmeta", minor_path,
858                            "v08", meta_device, "0",
859                            "dstate"])
860     if result.failed:
861       logging.error("Invalid meta device %s: %s", meta_device, result.output)
862       return False
863     return True
864
865   @classmethod
866   def _GetShowParser(cls):
867     """Return a parser for `drbd show` output.
868
869     This will either create or return an already-create parser for the
870     output of the command `drbd show`.
871
872     """
873     if cls._PARSE_SHOW is not None:
874       return cls._PARSE_SHOW
875
876     # pyparsing setup
877     lbrace = pyp.Literal("{").suppress()
878     rbrace = pyp.Literal("}").suppress()
879     semi = pyp.Literal(";").suppress()
880     # this also converts the value to an int
881     number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
882
883     comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
884     defa = pyp.Literal("_is_default").suppress()
885     dbl_quote = pyp.Literal('"').suppress()
886
887     keyword = pyp.Word(pyp.alphanums + '-')
888
889     # value types
890     value = pyp.Word(pyp.alphanums + '_-/.:')
891     quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
892     addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
893                  number)
894     # meta device, extended syntax
895     meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
896                   number + pyp.Word(']').suppress())
897
898     # a statement
899     stmt = (~rbrace + keyword + ~lbrace +
900             pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
901             pyp.Optional(defa) + semi +
902             pyp.Optional(pyp.restOfLine).suppress())
903
904     # an entire section
905     section_name = pyp.Word(pyp.alphas + '_')
906     section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
907
908     bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
909     bnf.ignore(comment)
910
911     cls._PARSE_SHOW = bnf
912
913     return bnf
914
915   @classmethod
916   def _GetShowData(cls, minor):
917     """Return the `drbdsetup show` data for a minor.
918
919     """
920     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
921     if result.failed:
922       logging.error("Can't display the drbd config: %s - %s",
923                     result.fail_reason, result.output)
924       return None
925     return result.stdout
926
927   @classmethod
928   def _GetDevInfo(cls, out):
929     """Parse details about a given DRBD minor.
930
931     This return, if available, the local backing device (as a path)
932     and the local and remote (ip, port) information from a string
933     containing the output of the `drbdsetup show` command as returned
934     by _GetShowData.
935
936     """
937     data = {}
938     if not out:
939       return data
940
941     bnf = cls._GetShowParser()
942     # run pyparse
943
944     try:
945       results = bnf.parseString(out)
946     except pyp.ParseException, err:
947       raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
948                                     str(err))
949
950     # and massage the results into our desired format
951     for section in results:
952       sname = section[0]
953       if sname == "_this_host":
954         for lst in section[1:]:
955           if lst[0] == "disk":
956             data["local_dev"] = lst[1]
957           elif lst[0] == "meta-disk":
958             data["meta_dev"] = lst[1]
959             data["meta_index"] = lst[2]
960           elif lst[0] == "address":
961             data["local_addr"] = tuple(lst[1:])
962       elif sname == "_remote_host":
963         for lst in section[1:]:
964           if lst[0] == "address":
965             data["remote_addr"] = tuple(lst[1:])
966     return data
967
968   def _MatchesLocal(self, info):
969     """Test if our local config matches with an existing device.
970
971     The parameter should be as returned from `_GetDevInfo()`. This
972     method tests if our local backing device is the same as the one in
973     the info parameter, in effect testing if we look like the given
974     device.
975
976     """
977     if self._children:
978       backend, meta = self._children
979     else:
980       backend = meta = None
981
982     if backend is not None:
983       retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
984     else:
985       retval = ("local_dev" not in info)
986
987     if meta is not None:
988       retval = retval and ("meta_dev" in info and
989                            info["meta_dev"] == meta.dev_path)
990       retval = retval and ("meta_index" in info and
991                            info["meta_index"] == 0)
992     else:
993       retval = retval and ("meta_dev" not in info and
994                            "meta_index" not in info)
995     return retval
996
997   def _MatchesNet(self, info):
998     """Test if our network config matches with an existing device.
999
1000     The parameter should be as returned from `_GetDevInfo()`. This
1001     method tests if our network configuration is the same as the one
1002     in the info parameter, in effect testing if we look like the given
1003     device.
1004
1005     """
1006     if (((self._lhost is None and not ("local_addr" in info)) and
1007          (self._rhost is None and not ("remote_addr" in info)))):
1008       return True
1009
1010     if self._lhost is None:
1011       return False
1012
1013     if not ("local_addr" in info and
1014             "remote_addr" in info):
1015       return False
1016
1017     retval = (info["local_addr"] == (self._lhost, self._lport))
1018     retval = (retval and
1019               info["remote_addr"] == (self._rhost, self._rport))
1020     return retval
1021
1022   @classmethod
1023   def _AssembleLocal(cls, minor, backend, meta):
1024     """Configure the local part of a DRBD device.
1025
1026     This is the first thing that must be done on an unconfigured DRBD
1027     device. And it must be done only once.
1028
1029     """
1030     if not cls._IsValidMeta(meta):
1031       return False
1032     args = ["drbdsetup", cls._DevPath(minor), "disk",
1033             backend, meta, "0", "-e", "detach", "--create-device"]
1034     result = utils.RunCmd(args)
1035     if result.failed:
1036       logging.error("Can't attach local disk: %s", result.output)
1037     return not result.failed
1038
1039   @classmethod
1040   def _AssembleNet(cls, minor, net_info, protocol,
1041                    dual_pri=False, hmac=None, secret=None):
1042     """Configure the network part of the device.
1043
1044     """
1045     lhost, lport, rhost, rport = net_info
1046     if None in net_info:
1047       # we don't want network connection and actually want to make
1048       # sure its shutdown
1049       return cls._ShutdownNet(minor)
1050
1051     # Workaround for a race condition. When DRBD is doing its dance to
1052     # establish a connection with its peer, it also sends the
1053     # synchronization speed over the wire. In some cases setting the
1054     # sync speed only after setting up both sides can race with DRBD
1055     # connecting, hence we set it here before telling DRBD anything
1056     # about its peer.
1057     cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1058
1059     args = ["drbdsetup", cls._DevPath(minor), "net",
1060             "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1061             "-A", "discard-zero-changes",
1062             "-B", "consensus",
1063             "--create-device",
1064             ]
1065     if dual_pri:
1066       args.append("-m")
1067     if hmac and secret:
1068       args.extend(["-a", hmac, "-x", secret])
1069     result = utils.RunCmd(args)
1070     if result.failed:
1071       logging.error("Can't setup network for dbrd device: %s - %s",
1072                     result.fail_reason, result.output)
1073       return False
1074
1075     timeout = time.time() + 10
1076     ok = False
1077     while time.time() < timeout:
1078       info = cls._GetDevInfo(cls._GetShowData(minor))
1079       if not "local_addr" in info or not "remote_addr" in info:
1080         time.sleep(1)
1081         continue
1082       if (info["local_addr"] != (lhost, lport) or
1083           info["remote_addr"] != (rhost, rport)):
1084         time.sleep(1)
1085         continue
1086       ok = True
1087       break
1088     if not ok:
1089       logging.error("Timeout while configuring network")
1090       return False
1091     return True
1092
1093   def AddChildren(self, devices):
1094     """Add a disk to the DRBD device.
1095
1096     """
1097     if self.minor is None:
1098       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1099     if len(devices) != 2:
1100       raise errors.BlockDeviceError("Need two devices for AddChildren")
1101     info = self._GetDevInfo(self._GetShowData(self.minor))
1102     if "local_dev" in info:
1103       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1104     backend, meta = devices
1105     if backend.dev_path is None or meta.dev_path is None:
1106       raise errors.BlockDeviceError("Children not ready during AddChildren")
1107     backend.Open()
1108     meta.Open()
1109     if not self._CheckMetaSize(meta.dev_path):
1110       raise errors.BlockDeviceError("Invalid meta device size")
1111     self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1112     if not self._IsValidMeta(meta.dev_path):
1113       raise errors.BlockDeviceError("Cannot initalize meta device")
1114
1115     if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1116       raise errors.BlockDeviceError("Can't attach to local storage")
1117     self._children = devices
1118
1119   def RemoveChildren(self, devices):
1120     """Detach the drbd device from local storage.
1121
1122     """
1123     if self.minor is None:
1124       raise errors.BlockDeviceError("Can't attach to drbd8 during"
1125                                     " RemoveChildren")
1126     # early return if we don't actually have backing storage
1127     info = self._GetDevInfo(self._GetShowData(self.minor))
1128     if "local_dev" not in info:
1129       return
1130     if len(self._children) != 2:
1131       raise errors.BlockDeviceError("We don't have two children: %s" %
1132                                     self._children)
1133     if self._children.count(None) == 2: # we don't actually have children :)
1134       logging.error("Requested detach while detached")
1135       return
1136     if len(devices) != 2:
1137       raise errors.BlockDeviceError("We need two children in RemoveChildren")
1138     for child, dev in zip(self._children, devices):
1139       if dev != child.dev_path:
1140         raise errors.BlockDeviceError("Mismatch in local storage"
1141                                       " (%s != %s) in RemoveChildren" %
1142                                       (dev, child.dev_path))
1143
1144     if not self._ShutdownLocal(self.minor):
1145       raise errors.BlockDeviceError("Can't detach from local storage")
1146     self._children = []
1147
1148   @classmethod
1149   def _SetMinorSyncSpeed(cls, minor, kbytes):
1150     """Set the speed of the DRBD syncer.
1151
1152     This is the low-level implementation.
1153
1154     @type minor: int
1155     @param minor: the drbd minor whose settings we change
1156     @type kbytes: int
1157     @param kbytes: the speed in kbytes/second
1158     @rtype: boolean
1159     @return: the success of the operation
1160
1161     """
1162     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1163                            "-r", "%d" % kbytes, "--create-device"])
1164     if result.failed:
1165       logging.error("Can't change syncer rate: %s - %s",
1166                     result.fail_reason, result.output)
1167     return not result.failed
1168
1169   def SetSyncSpeed(self, kbytes):
1170     """Set the speed of the DRBD syncer.
1171
1172     @type kbytes: int
1173     @param kbytes: the speed in kbytes/second
1174     @rtype: boolean
1175     @return: the success of the operation
1176
1177     """
1178     if self.minor is None:
1179       logging.info("Not attached during SetSyncSpeed")
1180       return False
1181     children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1182     return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1183
1184   def GetProcStatus(self):
1185     """Return device data from /proc.
1186
1187     """
1188     if self.minor is None:
1189       raise errors.BlockDeviceError("GetStats() called while not attached")
1190     proc_info = self._MassageProcData(self._GetProcData())
1191     if self.minor not in proc_info:
1192       raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1193                                     self.minor)
1194     return DRBD8Status(proc_info[self.minor])
1195
1196   def GetSyncStatus(self):
1197     """Returns the sync status of the device.
1198
1199
1200     If sync_percent is None, it means all is ok
1201     If estimated_time is None, it means we can't esimate
1202     the time needed, otherwise it's the time left in seconds.
1203
1204
1205     We set the is_degraded parameter to True on two conditions:
1206     network not connected or local disk missing.
1207
1208     We compute the ldisk parameter based on wheter we have a local
1209     disk or not.
1210
1211     @rtype: tuple
1212     @return: (sync_percent, estimated_time, is_degraded, ldisk)
1213
1214     """
1215     if self.minor is None and not self.Attach():
1216       raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1217     stats = self.GetProcStatus()
1218     ldisk = not stats.is_disk_uptodate
1219     is_degraded = not stats.is_connected
1220     return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1221
1222   def Open(self, force=False):
1223     """Make the local state primary.
1224
1225     If the 'force' parameter is given, the '-o' option is passed to
1226     drbdsetup. Since this is a potentially dangerous operation, the
1227     force flag should be only given after creation, when it actually
1228     is mandatory.
1229
1230     """
1231     if self.minor is None and not self.Attach():
1232       logging.error("DRBD cannot attach to a device during open")
1233       return False
1234     cmd = ["drbdsetup", self.dev_path, "primary"]
1235     if force:
1236       cmd.append("-o")
1237     result = utils.RunCmd(cmd)
1238     if result.failed:
1239       msg = ("Can't make drbd device primary: %s" % result.output)
1240       logging.error(msg)
1241       raise errors.BlockDeviceError(msg)
1242
1243   def Close(self):
1244     """Make the local state secondary.
1245
1246     This will, of course, fail if the device is in use.
1247
1248     """
1249     if self.minor is None and not self.Attach():
1250       logging.info("Instance not attached to a device")
1251       raise errors.BlockDeviceError("Can't find device")
1252     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1253     if result.failed:
1254       msg = ("Can't switch drbd device to"
1255              " secondary: %s" % result.output)
1256       logging.error(msg)
1257       raise errors.BlockDeviceError(msg)
1258
1259   def DisconnectNet(self):
1260     """Removes network configuration.
1261
1262     This method shutdowns the network side of the device.
1263
1264     The method will wait up to a hardcoded timeout for the device to
1265     go into standalone after the 'disconnect' command before
1266     re-configuring it, as sometimes it takes a while for the
1267     disconnect to actually propagate and thus we might issue a 'net'
1268     command while the device is still connected. If the device will
1269     still be attached to the network and we time out, we raise an
1270     exception.
1271
1272     """
1273     if self.minor is None:
1274       raise errors.BlockDeviceError("DRBD disk not attached in re-attach net")
1275
1276     if None in (self._lhost, self._lport, self._rhost, self._rport):
1277       raise errors.BlockDeviceError("DRBD disk missing network info in"
1278                                     " DisconnectNet()")
1279
1280     ever_disconnected = self._ShutdownNet(self.minor)
1281     timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1282     sleep_time = 0.100 # we start the retry time at 100 miliseconds
1283     while time.time() < timeout_limit:
1284       status = self.GetProcStatus()
1285       if status.is_standalone:
1286         break
1287       # retry the disconnect, it seems possible that due to a
1288       # well-time disconnect on the peer, my disconnect command might
1289       # be ingored and forgotten
1290       ever_disconnected = self._ShutdownNet(self.minor) or ever_disconnected
1291       time.sleep(sleep_time)
1292       sleep_time = min(2, sleep_time * 1.5)
1293
1294     if not status.is_standalone:
1295       if ever_disconnected:
1296         msg = ("Device did not react to the"
1297                " 'disconnect' command in a timely manner")
1298       else:
1299         msg = ("Can't shutdown network, even after multiple retries")
1300       raise errors.BlockDeviceError(msg)
1301
1302     reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1303     if reconfig_time > 15: # hardcoded alert limit
1304       logging.debug("DRBD8.DisconnectNet: detach took %.3f seconds",
1305                     reconfig_time)
1306
1307   def AttachNet(self, multimaster):
1308     """Reconnects the network.
1309
1310     This method connects the network side of the device with a
1311     specified multi-master flag. The device needs to be 'Standalone'
1312     but have valid network configuration data.
1313
1314     Args:
1315       - multimaster: init the network in dual-primary mode
1316
1317     """
1318     if self.minor is None:
1319       raise errors.BlockDeviceError("DRBD disk not attached in AttachNet")
1320
1321     if None in (self._lhost, self._lport, self._rhost, self._rport):
1322       raise errors.BlockDeviceError("DRBD disk missing network info in"
1323                                     " AttachNet()")
1324
1325     status = self.GetProcStatus()
1326
1327     if not status.is_standalone:
1328       raise errors.BlockDeviceError("Device is not standalone in AttachNet")
1329
1330     return self._AssembleNet(self.minor,
1331                              (self._lhost, self._lport,
1332                               self._rhost, self._rport),
1333                              "C", dual_pri=multimaster)
1334
1335   def Attach(self):
1336     """Check if our minor is configured.
1337
1338     This doesn't do any device configurations - it only checks if the
1339     minor is in a state different from Unconfigured.
1340
1341     Note that this function will not change the state of the system in
1342     any way (except in case of side-effects caused by reading from
1343     /proc).
1344
1345     """
1346     used_devs = self.GetUsedDevs()
1347     if self._aminor in used_devs:
1348       minor = self._aminor
1349     else:
1350       minor = None
1351
1352     self._SetFromMinor(minor)
1353     return minor is not None
1354
1355   def Assemble(self):
1356     """Assemble the drbd.
1357
1358     Method:
1359       - if we have a configured device, we try to ensure that it matches
1360         our config
1361       - if not, we create it from zero
1362
1363     """
1364     result = super(DRBD8, self).Assemble()
1365     if not result:
1366       return result
1367
1368     self.Attach()
1369     if self.minor is None:
1370       # local device completely unconfigured
1371       return self._FastAssemble()
1372     else:
1373       # we have to recheck the local and network status and try to fix
1374       # the device
1375       return self._SlowAssemble()
1376
1377   def _SlowAssemble(self):
1378     """Assembles the DRBD device from a (partially) configured device.
1379
1380     In case of partially attached (local device matches but no network
1381     setup), we perform the network attach. If successful, we re-test
1382     the attach if can return success.
1383
1384     """
1385     for minor in (self._aminor,):
1386       info = self._GetDevInfo(self._GetShowData(minor))
1387       match_l = self._MatchesLocal(info)
1388       match_r = self._MatchesNet(info)
1389       if match_l and match_r:
1390         break
1391       if match_l and not match_r and "local_addr" not in info:
1392         res_r = self._AssembleNet(minor,
1393                                   (self._lhost, self._lport,
1394                                    self._rhost, self._rport),
1395                                   constants.DRBD_NET_PROTOCOL,
1396                                   hmac=constants.DRBD_HMAC_ALG,
1397                                   secret=self._secret
1398                                   )
1399         if res_r:
1400           if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1401             break
1402       # the weakest case: we find something that is only net attached
1403       # even though we were passed some children at init time
1404       if match_r and "local_dev" not in info:
1405         break
1406
1407       # this case must be considered only if we actually have local
1408       # storage, i.e. not in diskless mode, because all diskless
1409       # devices are equal from the point of view of local
1410       # configuration
1411       if (match_l and "local_dev" in info and
1412           not match_r and "local_addr" in info):
1413         # strange case - the device network part points to somewhere
1414         # else, even though its local storage is ours; as we own the
1415         # drbd space, we try to disconnect from the remote peer and
1416         # reconnect to our correct one
1417         if not self._ShutdownNet(minor):
1418           raise errors.BlockDeviceError("Device has correct local storage,"
1419                                         " wrong remote peer and is unable to"
1420                                         " disconnect in order to attach to"
1421                                         " the correct peer")
1422         # note: _AssembleNet also handles the case when we don't want
1423         # local storage (i.e. one or more of the _[lr](host|port) is
1424         # None)
1425         if (self._AssembleNet(minor, (self._lhost, self._lport,
1426                                       self._rhost, self._rport),
1427                               constants.DRBD_NET_PROTOCOL,
1428                               hmac=constants.DRBD_HMAC_ALG,
1429                               secret=self._secret) and
1430             self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1431           break
1432
1433     else:
1434       minor = None
1435
1436     self._SetFromMinor(minor)
1437     return minor is not None
1438
1439   def _FastAssemble(self):
1440     """Assemble the drbd device from zero.
1441
1442     This is run when in Assemble we detect our minor is unused.
1443
1444     """
1445     # TODO: maybe completely tear-down the minor (drbdsetup ... down)
1446     # before attaching our own?
1447     minor = self._aminor
1448     need_localdev_teardown = False
1449     if self._children and self._children[0] and self._children[1]:
1450       result = self._AssembleLocal(minor, self._children[0].dev_path,
1451                                    self._children[1].dev_path)
1452       if not result:
1453         return False
1454       need_localdev_teardown = True
1455     if self._lhost and self._lport and self._rhost and self._rport:
1456       result = self._AssembleNet(minor,
1457                                  (self._lhost, self._lport,
1458                                   self._rhost, self._rport),
1459                                  constants.DRBD_NET_PROTOCOL,
1460                                  hmac=constants.DRBD_HMAC_ALG,
1461                                  secret=self._secret)
1462       if not result:
1463         if need_localdev_teardown:
1464           # we will ignore failures from this
1465           logging.error("net setup failed, tearing down local device")
1466           self._ShutdownAll(minor)
1467         return False
1468     self._SetFromMinor(minor)
1469     return True
1470
1471   @classmethod
1472   def _ShutdownLocal(cls, minor):
1473     """Detach from the local device.
1474
1475     I/Os will continue to be served from the remote device. If we
1476     don't have a remote device, this operation will fail.
1477
1478     """
1479     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1480     if result.failed:
1481       logging.error("Can't detach local device: %s", result.output)
1482     return not result.failed
1483
1484   @classmethod
1485   def _ShutdownNet(cls, minor):
1486     """Disconnect from the remote peer.
1487
1488     This fails if we don't have a local device.
1489
1490     """
1491     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1492     if result.failed:
1493       logging.error("Can't shutdown network: %s", result.output)
1494     return not result.failed
1495
1496   @classmethod
1497   def _ShutdownAll(cls, minor):
1498     """Deactivate the device.
1499
1500     This will, of course, fail if the device is in use.
1501
1502     """
1503     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1504     if result.failed:
1505       logging.error("Can't shutdown drbd device: %s", result.output)
1506     return not result.failed
1507
1508   def Shutdown(self):
1509     """Shutdown the DRBD device.
1510
1511     """
1512     if self.minor is None and not self.Attach():
1513       logging.info("DRBD device not attached to a device during Shutdown")
1514       return True
1515     if not self._ShutdownAll(self.minor):
1516       return False
1517     self.minor = None
1518     self.dev_path = None
1519     return True
1520
1521   def Remove(self):
1522     """Stub remove for DRBD devices.
1523
1524     """
1525     return self.Shutdown()
1526
1527   @classmethod
1528   def Create(cls, unique_id, children, size):
1529     """Create a new DRBD8 device.
1530
1531     Since DRBD devices are not created per se, just assembled, this
1532     function only initializes the metadata.
1533
1534     """
1535     if len(children) != 2:
1536       raise errors.ProgrammerError("Invalid setup for the drbd device")
1537     # check that the minor is unused
1538     aminor = unique_id[4]
1539     proc_info = cls._MassageProcData(cls._GetProcData())
1540     if aminor in proc_info:
1541       status = DRBD8Status(proc_info[aminor])
1542       in_use = status.is_in_use
1543     else:
1544       in_use = False
1545     if in_use:
1546       raise errors.BlockDeviceError("DRBD minor %d already in use at"
1547                                     " Create() time" % aminor)
1548     meta = children[1]
1549     meta.Assemble()
1550     if not meta.Attach():
1551       raise errors.BlockDeviceError("Can't attach to meta device")
1552     if not cls._CheckMetaSize(meta.dev_path):
1553       raise errors.BlockDeviceError("Invalid meta device size")
1554     cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1555     if not cls._IsValidMeta(meta.dev_path):
1556       raise errors.BlockDeviceError("Cannot initalize meta device")
1557     return cls(unique_id, children)
1558
1559   def Grow(self, amount):
1560     """Resize the DRBD device and its backing storage.
1561
1562     """
1563     if self.minor is None:
1564       raise errors.ProgrammerError("drbd8: Grow called while not attached")
1565     if len(self._children) != 2 or None in self._children:
1566       raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1567     self._children[0].Grow(amount)
1568     result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1569     if result.failed:
1570       raise errors.BlockDeviceError("resize failed for %s: %s" %
1571                                     (self.dev_path, result.output))
1572     return
1573
1574
1575 class FileStorage(BlockDev):
1576   """File device.
1577
1578   This class represents the a file storage backend device.
1579
1580   The unique_id for the file device is a (file_driver, file_path) tuple.
1581
1582   """
1583   def __init__(self, unique_id, children):
1584     """Initalizes a file device backend.
1585
1586     """
1587     if children:
1588       raise errors.BlockDeviceError("Invalid setup for file device")
1589     super(FileStorage, self).__init__(unique_id, children)
1590     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1591       raise ValueError("Invalid configuration data %s" % str(unique_id))
1592     self.driver = unique_id[0]
1593     self.dev_path = unique_id[1]
1594     self.Attach()
1595
1596   def Assemble(self):
1597     """Assemble the device.
1598
1599     Checks whether the file device exists, raises BlockDeviceError otherwise.
1600
1601     """
1602     if not os.path.exists(self.dev_path):
1603       raise errors.BlockDeviceError("File device '%s' does not exist." %
1604                                     self.dev_path)
1605     return True
1606
1607   def Shutdown(self):
1608     """Shutdown the device.
1609
1610     This is a no-op for the file type, as we don't deacivate
1611     the file on shutdown.
1612
1613     """
1614     return True
1615
1616   def Open(self, force=False):
1617     """Make the device ready for I/O.
1618
1619     This is a no-op for the file type.
1620
1621     """
1622     pass
1623
1624   def Close(self):
1625     """Notifies that the device will no longer be used for I/O.
1626
1627     This is a no-op for the file type.
1628
1629     """
1630     pass
1631
1632   def Remove(self):
1633     """Remove the file backing the block device.
1634
1635     @rtype: boolean
1636     @return: True if the removal was successful
1637
1638     """
1639     if not os.path.exists(self.dev_path):
1640       return True
1641     try:
1642       os.remove(self.dev_path)
1643       return True
1644     except OSError, err:
1645       logging.error("Can't remove file '%s': %s", self.dev_path, err)
1646       return False
1647
1648   def Attach(self):
1649     """Attach to an existing file.
1650
1651     Check if this file already exists.
1652
1653     @rtype: boolean
1654     @return: True if file exists
1655
1656     """
1657     self.attached = os.path.exists(self.dev_path)
1658     return self.attached
1659
1660   @classmethod
1661   def Create(cls, unique_id, children, size):
1662     """Create a new file.
1663
1664     @param size: the size of file in MiB
1665
1666     @rtype: L{bdev.FileStorage}
1667     @return: an instance of FileStorage
1668
1669     """
1670     # TODO: decide whether we should check for existing files and
1671     # abort or not
1672     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1673       raise ValueError("Invalid configuration data %s" % str(unique_id))
1674     dev_path = unique_id[1]
1675     try:
1676       f = open(dev_path, 'w')
1677       f.truncate(size * 1024 * 1024)
1678       f.close()
1679     except IOError, err:
1680       raise errors.BlockDeviceError("Error in file creation: %" % str(err))
1681
1682     return FileStorage(unique_id, children)
1683
1684
1685 DEV_MAP = {
1686   constants.LD_LV: LogicalVolume,
1687   constants.LD_DRBD8: DRBD8,
1688   constants.LD_FILE: FileStorage,
1689   }
1690
1691
1692 def FindDevice(dev_type, unique_id, children):
1693   """Search for an existing, assembled device.
1694
1695   This will succeed only if the device exists and is assembled, but it
1696   does not do any actions in order to activate the device.
1697
1698   """
1699   if dev_type not in DEV_MAP:
1700     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1701   device = DEV_MAP[dev_type](unique_id, children)
1702   if not device.attached:
1703     return None
1704   return device
1705
1706
1707 def Assemble(dev_type, unique_id, children):
1708   """Try to attach or assemble an existing device.
1709
1710   This will attach to assemble the device, as needed, to bring it
1711   fully up. It must be safe to run on already-assembled devices.
1712
1713   """
1714   if dev_type not in DEV_MAP:
1715     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1716   device = DEV_MAP[dev_type](unique_id, children)
1717   if not device.Assemble():
1718     raise errors.BlockDeviceError("Can't find a valid block device for"
1719                                   " %s/%s/%s" %
1720                                   (dev_type, unique_id, children))
1721   return device
1722
1723
1724 def Create(dev_type, unique_id, children, size):
1725   """Create a device.
1726
1727   """
1728   if dev_type not in DEV_MAP:
1729     raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1730   device = DEV_MAP[dev_type].Create(unique_id, children, size)
1731   return device