cmdlib: Factorize lock releasing
[ganeti-local] / lib / bdev.py
index 3352885..2430b1d 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@
 import re
 import time
 import errno
+import stat
 import pyparsing as pyp
 import os
 import logging
@@ -230,6 +231,20 @@ class BlockDev(object):
         result = result and child.SetSyncSpeed(speed)
     return result
 
+  def PauseResumeSync(self, pause):
+    """Pause/Resume the sync of the mirror.
+
+    In case this is not a mirroring device, this is no-op.
+
+    @param pause: Wheater to pause or resume
+
+    """
+    result = True
+    if self._children:
+      for child in self._children:
+        result = result and child.PauseResumeSync(pause)
+    return result
+
   def GetSyncStatus(self):
     """Returns the sync status of the device.
 
@@ -488,24 +503,26 @@ class LogicalVolume(BlockDev):
     @param filter_readonly: whether to skip over readonly VGs
 
     @rtype: list
-    @return: list of tuples (free_space, name) with free_space in mebibytes
+    @return: list of tuples (free_space, total_size, name) with free_space in
+             MiB
 
     """
     try:
-      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr"])
+      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
+                                        "vg_size"])
     except errors.GenericError, err:
       logging.error("Can't get VG information: %s", err)
       return None
 
     data = []
-    for vg_name, vg_free, vg_attr in info:
+    for vg_name, vg_free, vg_attr, vg_size in info:
       # (possibly) skip over vgs which are not writable
       if filter_readonly and vg_attr[0] == "r":
         continue
       # (possibly) skip over vgs which are not in the right volume group(s)
       if vg_names and vg_name not in vg_names:
         continue
-      data.append((float(vg_free), vg_name))
+      data.append((float(vg_free), float(vg_size), vg_name))
 
     return data
 
@@ -692,6 +709,8 @@ class LogicalVolume(BlockDev):
   def Snapshot(self, size):
     """Create a snapshot copy of an lvm block device.
 
+    @returns: tuple (vg, lv)
+
     """
     snap_name = self._lv_name + ".snap"
 
@@ -702,7 +721,7 @@ class LogicalVolume(BlockDev):
     vg_info = self.GetVGInfo([self._vg_name])
     if not vg_info:
       _ThrowError("Can't compute VG info for vg %s", self._vg_name)
-    free_size, _ = vg_info[0]
+    free_size, _, _ = vg_info[0]
     if free_size < size:
       _ThrowError("Not enough free space: required %s,"
                   " available %s", size, free_size)
@@ -713,7 +732,7 @@ class LogicalVolume(BlockDev):
       _ThrowError("command: %s error: %s - %s",
                   result.cmd, result.fail_reason, result.output)
 
-    return snap_name
+    return (self._vg_name, snap_name)
 
   def SetInfo(self, text):
     """Update metadata with info text.
@@ -1484,6 +1503,30 @@ class DRBD8(BaseDRBD):
     children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
     return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
 
+  def PauseResumeSync(self, pause):
+    """Pauses or resumes the sync of a DRBD device.
+
+    @param pause: Wether to pause or resume
+    @return: the success of the operation
+
+    """
+    if self.minor is None:
+      logging.info("Not attached during PauseSync")
+      return False
+
+    children_result = super(DRBD8, self).PauseResumeSync(pause)
+
+    if pause:
+      cmd = "pause-sync"
+    else:
+      cmd = "resume-sync"
+
+    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
+    if result.failed:
+      logging.error("Can't %s: %s - %s", cmd,
+                    result.fail_reason, result.output)
+    return not result.failed and children_result
+
   def GetProcStatus(self):
     """Return device data from /proc.
 
@@ -2027,12 +2070,123 @@ class FileStorage(BlockDev):
     return FileStorage(unique_id, children, size)
 
 
+class PersistentBlockDevice(BlockDev):
+  """A block device with persistent node
+
+  May be either directly attached, or exposed through DM (e.g. dm-multipath).
+  udev helpers are probably required to give persistent, human-friendly
+  names.
+
+  For the time being, pathnames are required to lie under /dev.
+
+  """
+  def __init__(self, unique_id, children, size):
+    """Attaches to a static block device.
+
+    The unique_id is a path under /dev.
+
+    """
+    super(PersistentBlockDevice, self).__init__(unique_id, children, size)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    self.dev_path = unique_id[1]
+    if not os.path.realpath(self.dev_path).startswith('/dev/'):
+      raise ValueError("Full path '%s' lies outside /dev" %
+                              os.path.realpath(self.dev_path))
+    # TODO: this is just a safety guard checking that we only deal with devices
+    # we know how to handle. In the future this will be integrated with
+    # external storage backends and possible values will probably be collected
+    # from the cluster configuration.
+    if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
+      raise ValueError("Got persistent block device of invalid type: %s" %
+                       unique_id[0])
+
+    self.major = self.minor = None
+    self.Attach()
+
+  @classmethod
+  def Create(cls, unique_id, children, size):
+    """Create a new device
+
+    This is a noop, we only return a PersistentBlockDevice instance
+
+    """
+    return PersistentBlockDevice(unique_id, children, 0)
+
+  def Remove(self):
+    """Remove a device
+
+    This is a noop
+
+    """
+    pass
+
+  def Rename(self, new_id):
+    """Rename this device.
+
+    """
+    _ThrowError("Rename is not supported for PersistentBlockDev storage")
+
+  def Attach(self):
+    """Attach to an existing block device.
+
+
+    """
+    self.attached = False
+    try:
+      st = os.stat(self.dev_path)
+    except OSError, err:
+      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
+      return False
+
+    if not stat.S_ISBLK(st.st_mode):
+      logging.error("%s is not a block device", self.dev_path)
+      return False
+
+    self.major = os.major(st.st_rdev)
+    self.minor = os.minor(st.st_rdev)
+    self.attached = True
+
+    return True
+
+  def Assemble(self):
+    """Assemble the device.
+
+    """
+    pass
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    """
+    pass
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    """
+    pass
+
+  def Grow(self, amount):
+    """Grow the logical volume.
+
+    """
+    _ThrowError("Grow is not supported for PersistentBlockDev storage")
+
+
 DEV_MAP = {
   constants.LD_LV: LogicalVolume,
   constants.LD_DRBD8: DRBD8,
+  constants.LD_BLOCKDEV: PersistentBlockDevice,
   }
 
-if constants.ENABLE_FILE_STORAGE:
+if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
   DEV_MAP[constants.LD_FILE] = FileStorage