Merge branch 'stable-2.6' into devel-2.6
[ganeti-local] / lib / storage.py
index eefe124..d77d80b 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2009 Google Inc.
+# Copyright (C) 2009, 2011, 2012 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
 
 """
 
+# pylint: disable=W0232,R0201
+
+# W0232, since we use these as singletons rather than object holding
+# data
+
+# R0201, for the same reason
+
+# TODO: FileStorage initialised with paths whereas the others not
 
 import logging
 
@@ -50,7 +58,7 @@ class _Base:
     """
     raise NotImplementedError()
 
-  def Modify(self, name, changes):
+  def Modify(self, name, changes): # pylint: disable=W0613
     """Modifies an entity within the storage unit.
 
     @type name: string
@@ -64,8 +72,19 @@ class _Base:
       raise errors.ProgrammerError("Unable to modify the following"
                                    "fields: %r" % (changes.keys(), ))
 
+  def Execute(self, name, op):
+    """Executes an operation on an entity within the storage unit.
+
+    @type name: string
+    @param name: Entity name
+    @type op: string
+    @param op: Operation name
+
+    """
+    raise NotImplementedError()
 
-class FileStorage(_Base):
+
+class FileStorage(_Base): # pylint: disable=W0223
   """File storage unit.
 
   """
@@ -114,10 +133,10 @@ class FileStorage(_Base):
     else:
       dirsize = None
 
-    if constants.SF_FREE in fields:
-      fsfree = utils.GetFreeFilesystemSpace(path)
+    if constants.SF_FREE in fields or constants.SF_SIZE in fields:
+      fsstats = utils.GetFilesystemStats(path)
     else:
-      fsfree = None
+      fsstats = None
 
     # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
     for field_name in fields:
@@ -128,7 +147,13 @@ class FileStorage(_Base):
         values.append(dirsize)
 
       elif field_name == constants.SF_FREE:
-        values.append(fsfree)
+        values.append(fsstats[1])
+
+      elif field_name == constants.SF_SIZE:
+        values.append(fsstats[0])
+
+      elif field_name == constants.SF_ALLOCATABLE:
+        values.append(True)
 
       else:
         raise errors.StorageError("Unknown field: %r" % field_name)
@@ -136,9 +161,14 @@ class FileStorage(_Base):
     return values
 
 
-class _LvmBase(_Base):
+class _LvmBase(_Base): # pylint: disable=W0223
   """Base class for LVM storage containers.
 
+  @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
+      constants, lvm command output fields (list), and conversion
+      function or static value (for static value, the lvm output field
+      can be an empty list)
+
   """
   LIST_SEP = "|"
   LIST_COMMAND = None
@@ -178,7 +208,8 @@ class _LvmBase(_Base):
 
     """
     field_to_idx = dict([(field_name, idx)
-                         for (idx, (field_name, _, _)) in enumerate(fields_def)])
+                         for (idx, (field_name, _, _)) in
+                         enumerate(fields_def)])
 
     lvm_fields = []
 
@@ -188,9 +219,9 @@ class _LvmBase(_Base):
       except IndexError:
         raise errors.StorageError("Unknown field: %r" % field_name)
 
-      (_, lvm_name, _) = fields_def[idx]
+      (_, lvm_names, _) = fields_def[idx]
 
-      lvm_fields.append(lvm_name)
+      lvm_fields.extend(lvm_names)
 
     return utils.UniqueSequence(lvm_fields)
 
@@ -211,21 +242,34 @@ class _LvmBase(_Base):
     lvm_name_to_idx = dict([(lvm_name, idx)
                            for (idx, lvm_name) in enumerate(lvm_fields)])
     field_to_idx = dict([(field_name, idx)
-                         for (idx, (field_name, _, _)) in enumerate(fields_def)])
+                         for (idx, (field_name, _, _)) in
+                         enumerate(fields_def)])
 
     data = []
     for raw_data in cmd_result:
       row = []
 
       for field_name in wanted_field_names:
-        (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
-
-        value = raw_data[lvm_name_to_idx[lvm_name]]
-
-        if convert_fn:
-          value = convert_fn(value)
-
-        row.append(value)
+        (_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
+
+        values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
+
+        if callable(mapper):
+          # we got a function, call it with all the declared fields
+          val = mapper(*values) # pylint: disable=W0142
+        elif len(values) == 1:
+          assert mapper is None, ("Invalid mapper value (neither callable"
+                                  " nor None) for one-element fields")
+          # we don't have a function, but we had a single field
+          # declared, pass it unchanged
+          val = values[0]
+        else:
+          # let's make sure there are no fields declared (cannot map >
+          # 1 field without a function)
+          assert not values, "LVM storage has multi-fields without a function"
+          val = mapper
+
+        row.append(val)
 
       data.append(row)
 
@@ -284,32 +328,39 @@ class _LvmBase(_Base):
       fields = line.strip().split(sep)
 
       if len(fields) != fieldcount:
+        logging.warning("Invalid line returned from lvm command: %s", line)
         continue
 
       yield fields
 
 
-class LvmPvStorage(_LvmBase):
-  """LVM Physical Volume storage unit.
+def _LvmPvGetAllocatable(attr):
+  """Determines whether LVM PV is allocatable.
+
+  @rtype: bool
 
   """
-  def _GetAllocatable(attr):
-    if attr:
-      return (attr[0] == "a")
-    else:
-      logging.warning("Invalid PV attribute: %r", attr)
-      return False
+  if attr:
+    return (attr[0] == "a")
+  else:
+    logging.warning("Invalid PV attribute: %r", attr)
+    return False
 
+
+class LvmPvStorage(_LvmBase): # pylint: disable=W0223
+  """LVM Physical Volume storage unit.
+
+  """
   LIST_COMMAND = "pvs"
 
   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
   # definitions.
   LIST_FIELDS = [
-    (constants.SF_NAME, "pv_name", None),
-    (constants.SF_SIZE, "pv_size", _ParseSize),
-    (constants.SF_USED, "pv_used", _ParseSize),
-    (constants.SF_FREE, "pv_free", _ParseSize),
-    (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
+    (constants.SF_NAME, ["pv_name"], None),
+    (constants.SF_SIZE, ["pv_size"], _ParseSize),
+    (constants.SF_USED, ["pv_used"], _ParseSize),
+    (constants.SF_FREE, ["pv_free"], _ParseSize),
+    (constants.SF_ALLOCATABLE, ["pv_attr"], _LvmPvGetAllocatable),
     ]
 
   def _SetAllocatable(self, name, allocatable):
@@ -355,14 +406,62 @@ class LvmVgStorage(_LvmBase):
 
   """
   LIST_COMMAND = "vgs"
+  VGREDUCE_COMMAND = "vgreduce"
 
   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
   # definitions.
   LIST_FIELDS = [
-    (constants.SF_NAME, "vg_name", None),
-    (constants.SF_SIZE, "vg_size", _ParseSize),
+    (constants.SF_NAME, ["vg_name"], None),
+    (constants.SF_SIZE, ["vg_size"], _ParseSize),
+    (constants.SF_FREE, ["vg_free"], _ParseSize),
+    (constants.SF_USED, ["vg_size", "vg_free"],
+     lambda x, y: _ParseSize(x) - _ParseSize(y)),
+    (constants.SF_ALLOCATABLE, [], True),
     ]
 
+  def _RemoveMissing(self, name, _runcmd_fn=utils.RunCmd):
+    """Runs "vgreduce --removemissing" on a volume group.
+
+    @type name: string
+    @param name: Volume group name
+
+    """
+    # Ignoring vgreduce exit code. Older versions exit with an error even tough
+    # the VG is already consistent. This was fixed in later versions, but we
+    # cannot depend on it.
+    result = _runcmd_fn([self.VGREDUCE_COMMAND, "--removemissing", name])
+
+    # Keep output in case something went wrong
+    vgreduce_output = result.output
+
+    # work around newer LVM version
+    if ("Wrote out consistent volume group" not in vgreduce_output or
+        "vgreduce --removemissing --force" in vgreduce_output):
+      # we need to re-run with --force
+      result = _runcmd_fn([self.VGREDUCE_COMMAND, "--removemissing",
+                           "--force", name])
+      vgreduce_output += "\n" + result.output
+
+    result = _runcmd_fn([self.LIST_COMMAND, "--noheadings",
+                         "--nosuffix", name])
+    # we also need to check the output
+    if result.failed or "Couldn't find device with uuid" in result.output:
+      raise errors.StorageError(("Volume group '%s' still not consistent,"
+                                 " 'vgreduce' output: %r,"
+                                 " 'vgs' output: %r") %
+                                (name, vgreduce_output, result.output))
+
+  def Execute(self, name, op):
+    """Executes an operation on a virtual volume.
+
+    See L{_Base.Execute}.
+
+    """
+    if op == constants.SO_FIX_CONSISTENCY:
+      return self._RemoveMissing(name)
+
+    return _LvmBase.Execute(self, name, op)
+
 
 # Lookup table for storage types
 _STORAGE_TYPES = {