Verify: add instance information to node_info
[ganeti-local] / lib / bdev.py
index b18aa76..8ba5930 100644 (file)
@@ -25,6 +25,7 @@ import re
 import time
 import errno
 import pyparsing as pyp
+import os
 
 from ganeti import utils
 from ganeti import logger
@@ -53,10 +54,6 @@ class BlockDev(object):
     - md arrays are created or assembled and used
     - drbd devices are attached to a local disk/remote peer and made primary
 
-  The status of the device can be examined by `GetStatus()`, which
-  returns a numerical value, depending on the position in the
-  transition stack of the device.
-
   A block device is identified by three items:
     - the /dev path of the device (dynamic)
     - a unique ID of the device (static)
@@ -82,18 +79,6 @@ class BlockDev(object):
   after assembly we'll have our correct major/minor.
 
   """
-  STATUS_UNKNOWN = 0
-  STATUS_EXISTING = 1
-  STATUS_STANDBY = 2
-  STATUS_ONLINE = 3
-
-  STATUS_MAP = {
-    STATUS_UNKNOWN: "unknown",
-    STATUS_EXISTING: "existing",
-    STATUS_STANDBY: "ready for use",
-    STATUS_ONLINE: "online",
-    }
-
   def __init__(self, unique_id, children):
     self._children = children
     self.dev_path = None
@@ -122,7 +107,13 @@ class BlockDev(object):
       status = status and child.Assemble()
       if not status:
         break
-      status = status and child.Open()
+
+      try:
+        child.Open()
+      except errors.BlockDeviceError:
+        for child in self._children:
+          child.Shutdown()
+        raise
 
     if not status:
       for child in self._children:
@@ -173,12 +164,6 @@ class BlockDev(object):
     """
     raise NotImplementedError
 
-  def GetStatus(self):
-    """Return the status of the device.
-
-    """
-    raise NotImplementedError
-
   def Open(self, force=False):
     """Make the device ready for use.
 
@@ -389,12 +374,11 @@ class LogicalVolume(BlockDev):
     self._lv_name = new_name
     self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
 
-
   def Attach(self):
     """Attach to an existing LV.
 
     This method will try to see if an existing and active LV exists
-    which matches the our name. If so, its major/minor will be
+    which matches our name. If so, its major/minor will be
     recorded.
 
     """
@@ -415,11 +399,15 @@ class LogicalVolume(BlockDev):
   def Assemble(self):
     """Assemble the device.
 
-    This is a no-op for the LV device type. Eventually, we could
-    lvchange -ay here if we see that the LV is not active.
+    We alway run `lvchange -ay` on the LV to ensure it's active before
+    use, as there were cases when xenvg was not active after boot
+    (also possibly after disk issues).
 
     """
-    return True
+    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
+    if result.failed:
+      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
+    return not result.failed
 
   def Shutdown(self):
     """Shutdown the device.
@@ -430,34 +418,6 @@ class LogicalVolume(BlockDev):
     """
     return True
 
-  def GetStatus(self):
-    """Return the status of the device.
-
-    Logical volumes will can be in all four states, although we don't
-    deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
-    should not be seen for our devices.
-
-    """
-    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
-    if result.failed:
-      logger.Error("Can't display lv: %s" % result.fail_reason)
-      return self.STATUS_UNKNOWN
-    out = result.stdout.strip()
-    # format: type/permissions/alloc/fixed_minor/state/open
-    if len(out) != 6:
-      return self.STATUS_UNKNOWN
-    #writable = (out[1] == "w")
-    active = (out[4] == "a")
-    online = (out[5] == "o")
-    if online:
-      retval = self.STATUS_ONLINE
-    elif active:
-      retval = self.STATUS_STANDBY
-    else:
-      retval = self.STATUS_EXISTING
-
-    return retval
-
   def GetSyncStatus(self):
     """Returns the sync status of the device.
 
@@ -498,7 +458,7 @@ class LogicalVolume(BlockDev):
     This is a no-op for the LV device type.
 
     """
-    return True
+    pass
 
   def Close(self):
     """Notifies that the device will no longer be used for I/O.
@@ -506,7 +466,7 @@ class LogicalVolume(BlockDev):
     This is a no-op for the LV device type.
 
     """
-    return True
+    pass
 
   def Snapshot(self, size):
     """Create a snapshot copy of an lvm block device.
@@ -814,17 +774,6 @@ class MDRaid1(BlockDev):
     for dev in orig_devs:
       self._children.remove(dev)
 
-  def GetStatus(self):
-    """Return the status of the device.
-
-    """
-    self.Attach()
-    if self.minor is None:
-      retval = self.STATUS_UNKNOWN
-    else:
-      retval = self.STATUS_ONLINE
-    return retval
-
   def _SetFromMinor(self, minor):
     """Set our parameters based on the given minor.
 
@@ -950,7 +899,7 @@ class MDRaid1(BlockDev):
     the 2.6.18's new array_state thing.
 
     """
-    return True
+    pass
 
   def Close(self):
     """Notifies that the device will no longer be used for I/O.
@@ -959,7 +908,7 @@ class MDRaid1(BlockDev):
     `Open()`.
 
     """
-    return True
+    pass
 
 
 class BaseDRBD(BlockDev):
@@ -970,7 +919,8 @@ class BaseDRBD(BlockDev):
 
   """
   _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
-                           r" \(api:(\d+)/proto:(\d+)\)")
+                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
+
   _DRBD_MAJOR = 147
   _ST_UNCONFIGURED = "Unconfigured"
   _ST_WFCONNECTION = "WFConnection"
@@ -1020,7 +970,13 @@ class BaseDRBD(BlockDev):
   def _GetVersion(cls):
     """Return the DRBD version.
 
-    This will return a list [k_major, k_minor, k_point, api, proto].
+    This will return a dict with keys:
+      k_major,
+      k_minor,
+      k_point,
+      api,
+      proto,
+      proto2 (only on drbd > 8.2.X)
 
     """
     proc_data = cls._GetProcData()
@@ -1029,7 +985,18 @@ class BaseDRBD(BlockDev):
     if not version:
       raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
                                     first_line)
-    return [int(val) for val in version.groups()]
+
+    values = version.groups()
+    retval = {'k_major': int(values[0]),
+              'k_minor': int(values[1]),
+              'k_point': int(values[2]),
+              'api': int(values[3]),
+              'proto': int(values[4]),
+             }
+    if values[5] is not None:
+      retval['proto2'] = values[5]
+
+    return retval
 
   @staticmethod
   def _DevPath(minor):
@@ -1122,12 +1089,12 @@ class DRBDev(BaseDRBD):
   def __init__(self, unique_id, children):
     super(DRBDev, self).__init__(unique_id, children)
     self.major = self._DRBD_MAJOR
-    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
-    if kmaj != 0 and kmin != 7:
+    version = self._GetVersion()
+    if version['k_major'] != 0 and version['k_minor'] != 7:
       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
                                     " requested ganeti usage: kernel is"
-                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
-
+                                    " %s.%s, ganeti wants 0.7" %
+                                    (version['k_major'], version['k_minor']))
     if len(children) != 2:
       raise ValueError("Invalid configuration data %s" % str(children))
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
@@ -1341,7 +1308,8 @@ class DRBDev(BaseDRBD):
 
     """
     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
-    logger.Error("Can't shutdown network: %s" % result.output)
+    if result.failed:
+      logger.Error("Can't shutdown network: %s" % result.output)
     return not result.failed
 
   def Assemble(self):
@@ -1451,9 +1419,9 @@ class DRBDev(BaseDRBD):
       cmd.append("--do-what-I-say")
     result = utils.RunCmd(cmd)
     if result.failed:
-      logger.Error("Can't make drbd device primary: %s" % result.output)
-      return False
-    return True
+      msg = ("Can't make drbd device primary: %s" % result.output)
+      logger.Error(msg)
+      raise errors.BlockDeviceError(msg)
 
   def Close(self):
     """Make the local state secondary.
@@ -1466,8 +1434,10 @@ class DRBDev(BaseDRBD):
       raise errors.BlockDeviceError("Can't find device")
     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
     if result.failed:
-      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
-      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
+      msg = ("Can't switch drbd device to"
+             " secondary: %s" % result.output)
+      logger.Error(msg)
+      raise errors.BlockDeviceError(msg)
 
   def SetSyncSpeed(self, kbytes):
     """Set the speed of the DRBD syncer.
@@ -1523,34 +1493,6 @@ class DRBDev(BaseDRBD):
     is_degraded = client_state != "Connected"
     return sync_percent, est_time, is_degraded, False
 
-  def GetStatus(self):
-    """Compute the status of the DRBD device
-
-    Note that DRBD devices don't have the STATUS_EXISTING state.
-
-    """
-    if self.minor is None and not self.Attach():
-      return self.STATUS_UNKNOWN
-
-    data = self._GetProcData()
-    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
-                       self.minor)
-    for line in data:
-      mresult = match.match(line)
-      if mresult:
-        break
-    else:
-      logger.Error("Can't find myself!")
-      return self.STATUS_UNKNOWN
-
-    state = mresult.group(2)
-    if state == "Primary":
-      result = self.STATUS_ONLINE
-    else:
-      result = self.STATUS_STANDBY
-
-    return result
-
   @staticmethod
   def _ZeroDevice(device):
     """Zero a device.
@@ -1616,11 +1558,12 @@ class DRBD8(BaseDRBD):
       children = []
     super(DRBD8, self).__init__(unique_id, children)
     self.major = self._DRBD_MAJOR
-    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
-    if kmaj != 8:
+    version = self._GetVersion()
+    if version['k_major'] != 8 :
       raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
                                     " requested ganeti usage: kernel is"
-                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
+                                    " %s.%s, ganeti wants 8.x" %
+                                    (version['k_major'], version['k_minor']))
 
     if len(children) not in (0, 2):
       raise ValueError("Invalid configuration data %s" % str(children))
@@ -1701,7 +1644,7 @@ class DRBD8(BaseDRBD):
     rbrace = pyp.Literal("}").suppress()
     semi = pyp.Literal(";").suppress()
     # this also converts the value to an int
-    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
+    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
 
     comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
     defa = pyp.Literal("_is_default").suppress()
@@ -1736,19 +1679,27 @@ class DRBD8(BaseDRBD):
     return bnf
 
   @classmethod
-  def _GetDevInfo(cls, minor):
-    """Get details about a given DRBD minor.
-
-    This return, if available, the local backing device (as a path)
-    and the local and remote (ip, port) information.
+  def _GetShowData(cls, minor):
+    """Return the `drbdsetup show` data for a minor.
 
     """
-    data = {}
     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
     if result.failed:
       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
-      return data
-    out = result.stdout
+      return None
+    return result.stdout
+
+  @classmethod
+  def _GetDevInfo(cls, out):
+    """Parse details about a given DRBD minor.
+
+    This return, if available, the local backing device (as a path)
+    and the local and remote (ip, port) information from a string
+    containing the output of the `drbdsetup show` command as returned
+    by _GetShowData.
+
+    """
+    data = {}
     if not out:
       return data
 
@@ -1857,6 +1808,11 @@ class DRBD8(BaseDRBD):
 
     """
     lhost, lport, rhost, rport = net_info
+    if None in net_info:
+      # we don't want network connection and actually want to make
+      # sure its shutdown
+      return cls._ShutdownNet(minor)
+
     args = ["drbdsetup", cls._DevPath(minor), "net",
             "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
             "-A", "discard-zero-changes",
@@ -1875,7 +1831,7 @@ class DRBD8(BaseDRBD):
     timeout = time.time() + 10
     ok = False
     while time.time() < timeout:
-      info = cls._GetDevInfo(minor)
+      info = cls._GetDevInfo(cls._GetShowData(minor))
       if not "local_addr" in info or not "remote_addr" in info:
         time.sleep(1)
         continue
@@ -1898,7 +1854,7 @@ class DRBD8(BaseDRBD):
       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
     if len(devices) != 2:
       raise errors.BlockDeviceError("Need two devices for AddChildren")
-    info = self._GetDevInfo(self.minor)
+    info = self._GetDevInfo(self._GetShowData(self.minor))
     if "local_dev" in info:
       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
     backend, meta = devices
@@ -1924,7 +1880,7 @@ class DRBD8(BaseDRBD):
       raise errors.BlockDeviceError("Can't attach to drbd8 during"
                                     " RemoveChildren")
     # early return if we don't actually have backing storage
-    info = self._GetDevInfo(self.minor)
+    info = self._GetDevInfo(self._GetShowData(self.minor))
     if "local_dev" not in info:
       return
     if len(self._children) != 2:
@@ -2005,34 +1961,6 @@ class DRBD8(BaseDRBD):
     is_degraded = client_state != "Connected"
     return sync_percent, est_time, is_degraded or ldisk, ldisk
 
-  def GetStatus(self):
-    """Compute the status of the DRBD device
-
-    Note that DRBD devices don't have the STATUS_EXISTING state.
-
-    """
-    if self.minor is None and not self.Attach():
-      return self.STATUS_UNKNOWN
-
-    data = self._GetProcData()
-    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
-                       self.minor)
-    for line in data:
-      mresult = match.match(line)
-      if mresult:
-        break
-    else:
-      logger.Error("Can't find myself!")
-      return self.STATUS_UNKNOWN
-
-    state = mresult.group(2)
-    if state == "Primary":
-      result = self.STATUS_ONLINE
-    else:
-      result = self.STATUS_STANDBY
-
-    return result
-
   def Open(self, force=False):
     """Make the local state primary.
 
@@ -2050,9 +1978,9 @@ class DRBD8(BaseDRBD):
       cmd.append("-o")
     result = utils.RunCmd(cmd)
     if result.failed:
-      logger.Error("Can't make drbd device primary: %s" % result.output)
-      return False
-    return True
+      msg = ("Can't make drbd device primary: %s" % result.output)
+      logger.Error(msg)
+      raise errors.BlockDeviceError(msg)
 
   def Close(self):
     """Make the local state secondary.
@@ -2065,8 +1993,10 @@ class DRBD8(BaseDRBD):
       raise errors.BlockDeviceError("Can't find device")
     result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
     if result.failed:
-      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
-      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
+      msg = ("Can't switch drbd device to"
+             " secondary: %s" % result.output)
+      logger.Error(msg)
+      raise errors.BlockDeviceError(msg)
 
   def Attach(self):
     """Find a DRBD device which matches our config and attach to it.
@@ -2077,7 +2007,7 @@ class DRBD8(BaseDRBD):
 
     """
     for minor in self._GetUsedDevs():
-      info = self._GetDevInfo(minor)
+      info = self._GetDevInfo(self._GetShowData(minor))
       match_l = self._MatchesLocal(info)
       match_r = self._MatchesNet(info)
       if match_l and match_r:
@@ -2087,12 +2017,31 @@ class DRBD8(BaseDRBD):
                                   (self._lhost, self._lport,
                                    self._rhost, self._rport),
                                   "C")
-        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
-          break
+        if res_r:
+          if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
+            break
       # the weakest case: we find something that is only net attached
       # even though we were passed some children at init time
       if match_r and "local_dev" not in info:
         break
+      if match_l and not match_r and "local_addr" in info:
+        # strange case - the device network part points to somewhere
+        # else, even though its local storage is ours; as we own the
+        # drbd space, we try to disconnect from the remote peer and
+        # reconnect to our correct one
+        if not self._ShutdownNet(minor):
+          raise errors.BlockDeviceError("Device has correct local storage,"
+                                        " wrong remote peer and is unable to"
+                                        " disconnect in order to attach to"
+                                        " the correct peer")
+        # note: _AssembleNet also handles the case when we don't want
+        # local storage (i.e. one or more of the _[lr](host|port) is
+        # None)
+        if (self._AssembleNet(minor, (self._lhost, self._lport,
+                                      self._rhost, self._rport), "C") and
+            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
+          break
+
     else:
       minor = None
 
@@ -2170,7 +2119,8 @@ class DRBD8(BaseDRBD):
 
     """
     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
-    logger.Error("Can't shutdown network: %s" % result.output)
+    if result.failed:
+      logger.Error("Can't shutdown network: %s" % result.output)
     return not result.failed
 
   @classmethod
@@ -2198,27 +2148,6 @@ class DRBD8(BaseDRBD):
     self.dev_path = None
     return True
 
-  def Rename(self, new_uid):
-    """Re-connect this device to another peer.
-
-    """
-    if self.minor is None:
-      raise errors.BlockDeviceError("Device not attached during rename")
-    if self._rhost is not None:
-      # this means we did have a host when we attached, so we are connected
-      if not self._ShutdownNet(self.minor):
-        raise errors.BlockDeviceError("Can't disconnect from remote peer")
-      old_id = self.unique_id
-    else:
-      old_id = None
-    self.unique_id = new_uid
-    if not self._AssembleNet(self.minor, self.unique_id, "C"):
-      logger.Error("Can't attach to new peer!")
-      if old_id is not None:
-        self._AssembleNet(self.minor, old_id, "C")
-      self.unique_id = old_id
-      raise errors.BlockDeviceError("Can't attach to new peer")
-
   def Remove(self):
     """Stub remove for DRBD devices.
 
@@ -2247,11 +2176,124 @@ class DRBD8(BaseDRBD):
     return cls(unique_id, children)
 
 
+class FileStorage(BlockDev):
+  """File device.
+  
+  This class represents the a file storage backend device.
+
+  The unique_id for the file device is a (file_driver, file_path) tuple.
+  
+  """
+  def __init__(self, unique_id, children):
+    """Initalizes a file device backend.
+
+    """
+    if children:
+      raise errors.BlockDeviceError("Invalid setup for file device")
+    super(FileStorage, self).__init__(unique_id, children)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    self.driver = unique_id[0]
+    self.dev_path = unique_id[1]
+
+  def Assemble(self):
+    """Assemble the device.
+
+    Checks whether the file device exists, raises BlockDeviceError otherwise.
+
+    """
+    if not os.path.exists(self.dev_path):
+      raise errors.BlockDeviceError("File device '%s' does not exist." %
+                                    self.dev_path)
+    return True
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    This is a no-op for the file type, as we don't deacivate
+    the file on shutdown.
+
+    """
+    return True
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    This is a no-op for the file type.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    This is a no-op for the file type.
+
+    """
+    pass
+
+  def Remove(self):
+    """Remove the file backing the block device.
+
+    Returns:
+      boolean indicating wheter removal of file was successful or not.
+
+    """
+    if not os.path.exists(self.dev_path):
+      return True
+    try:
+      os.remove(self.dev_path)
+      return True
+    except OSError, err:
+      logger.Error("Can't remove file '%s': %s"
+                   % (self.dev_path, err))
+      return False
+
+  def Attach(self):
+    """Attach to an existing file.
+
+    Check if this file already exists.
+
+    Returns:
+      boolean indicating if file exists or not.
+
+    """
+    if os.path.exists(self.dev_path):
+      return True
+    return False
+
+  @classmethod
+  def Create(cls, unique_id, children, size):
+    """Create a new file.
+
+    Args:
+      children:
+      size: integer size of file in MiB
+
+    Returns:
+      A ganeti.bdev.FileStorage object.
+
+    """
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    dev_path = unique_id[1]
+    try:
+      f = open(dev_path, 'w')
+    except IOError, err:
+      raise BlockDeviceError("Could not create '%'" % err)
+    else:
+      f.truncate(size * 1024 * 1024)
+      f.close()
+
+    return FileStorage(unique_id, children)
+
+
 DEV_MAP = {
   constants.LD_LV: LogicalVolume,
   constants.LD_MD_R1: MDRaid1,
   constants.LD_DRBD7: DRBDev,
   constants.LD_DRBD8: DRBD8,
+  constants.LD_FILE: FileStorage,
   }
 
 
@@ -2282,10 +2324,10 @@ def AttachOrAssemble(dev_type, unique_id, children):
   device = DEV_MAP[dev_type](unique_id, children)
   if not device.Attach():
     device.Assemble()
-  if not device.Attach():
-    raise errors.BlockDeviceError("Can't find a valid block device for"
-                                  " %s/%s/%s" %
-                                  (dev_type, unique_id, children))
+    if not device.Attach():
+      raise errors.BlockDeviceError("Can't find a valid block device for"
+                                    " %s/%s/%s" %
+                                    (dev_type, unique_id, children))
   return device