Renaming 'storage.py' to 'container.py'. It will be moved into the new
'storage' directory, which will come in later patches to avoid clashes of
notation.

Signed-off-by: Helga Velroyen <[email protected]>
---
 Makefile.am                          |   4 +-
 lib/container.py                     | 494 +++++++++++++++++++++++++++++++++++
 lib/server/noded.py                  |   8 +-
 lib/storage.py                       | 494 -----------------------------------
 test/py/ganeti.container_unittest.py | 113 ++++++++
 test/py/ganeti.storage_unittest.py   | 113 --------
 6 files changed, 613 insertions(+), 613 deletions(-)
 create mode 100644 lib/container.py
 delete mode 100644 lib/storage.py
 create mode 100755 test/py/ganeti.container_unittest.py
 delete mode 100755 test/py/ganeti.storage_unittest.py

diff --git a/Makefile.am b/Makefile.am
index 4721836..4728e55 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -265,6 +265,7 @@ pkgpython_PYTHON = \
        lib/compat.py \
        lib/config.py \
        lib/constants.py \
+       lib/container.py \
        lib/daemon.py \
        lib/errors.py \
        lib/hooksmaster.py \
@@ -288,7 +289,6 @@ pkgpython_PYTHON = \
        lib/serializer.py \
        lib/ssconf.py \
        lib/ssh.py \
-       lib/storage.py \
        lib/uidpool.py \
        lib/vcluster.py \
        lib/network.py \
@@ -1153,6 +1153,7 @@ python_tests = \
        test/py/ganeti.confd.client_unittest.py \
        test/py/ganeti.config_unittest.py \
        test/py/ganeti.constants_unittest.py \
+       test/py/ganeti.container_unittest.py \
        test/py/ganeti.daemon_unittest.py \
        test/py/ganeti.errors_unittest.py \
        test/py/ganeti.hooks_unittest.py \
@@ -1190,7 +1191,6 @@ python_tests = \
        test/py/ganeti.server.rapi_unittest.py \
        test/py/ganeti.ssconf_unittest.py \
        test/py/ganeti.ssh_unittest.py \
-       test/py/ganeti.storage_unittest.py \
        test/py/ganeti.tools.burnin_unittest.py \
        test/py/ganeti.tools.ensure_dirs_unittest.py \
        test/py/ganeti.tools.node_daemon_setup_unittest.py \
diff --git a/lib/container.py b/lib/container.py
new file mode 100644
index 0000000..d77d80b
--- /dev/null
+++ b/lib/container.py
@@ -0,0 +1,494 @@
+#
+#
+
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Storage container abstraction.
+
+"""
+
+# 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
+
+from ganeti import errors
+from ganeti import constants
+from ganeti import utils
+
+
+def _ParseSize(value):
+  return int(round(float(value), 0))
+
+
+class _Base:
+  """Base class for storage abstraction.
+
+  """
+  def List(self, name, fields):
+    """Returns a list of all entities within the storage unit.
+
+    @type name: string or None
+    @param name: Entity name or None for all
+    @type fields: list
+    @param fields: List with all requested result fields (order is preserved)
+
+    """
+    raise NotImplementedError()
+
+  def Modify(self, name, changes): # pylint: disable=W0613
+    """Modifies an entity within the storage unit.
+
+    @type name: string
+    @param name: Entity name
+    @type changes: dict
+    @param changes: New field values
+
+    """
+    # Don't raise an error if no changes are requested
+    if changes:
+      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): # pylint: disable=W0223
+  """File storage unit.
+
+  """
+  def __init__(self, paths):
+    """Initializes this class.
+
+    @type paths: list
+    @param paths: List of file storage paths
+
+    """
+    self._paths = paths
+
+  def List(self, name, fields):
+    """Returns a list of all entities within the storage unit.
+
+    See L{_Base.List}.
+
+    """
+    rows = []
+
+    if name is None:
+      paths = self._paths
+    else:
+      paths = [name]
+
+    for path in paths:
+      rows.append(self._ListInner(path, fields))
+
+    return rows
+
+  @staticmethod
+  def _ListInner(path, fields):
+    """Gathers requested information from directory.
+
+    @type path: string
+    @param path: Path to directory
+    @type fields: list
+    @param fields: Requested fields
+
+    """
+    values = []
+
+    # Pre-calculate information in case it's requested more than once
+    if constants.SF_USED in fields:
+      dirsize = utils.CalculateDirectorySize(path)
+    else:
+      dirsize = None
+
+    if constants.SF_FREE in fields or constants.SF_SIZE in fields:
+      fsstats = utils.GetFilesystemStats(path)
+    else:
+      fsstats = None
+
+    # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
+    for field_name in fields:
+      if field_name == constants.SF_NAME:
+        values.append(path)
+
+      elif field_name == constants.SF_USED:
+        values.append(dirsize)
+
+      elif field_name == constants.SF_FREE:
+        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)
+
+    return values
+
+
+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
+  LIST_FIELDS = None
+
+  def List(self, name, wanted_field_names):
+    """Returns a list of all entities within the storage unit.
+
+    See L{_Base.List}.
+
+    """
+    # Get needed LVM fields
+    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
+
+    # Build LVM command
+    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
+                                      lvm_fields, name)
+
+    # Run LVM command
+    cmd_result = self._RunListCommand(cmd_args)
+
+    # Split and rearrange LVM command output
+    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
+                                           len(lvm_fields)),
+                           self.LIST_FIELDS,
+                           wanted_field_names,
+                           lvm_fields)
+
+  @staticmethod
+  def _GetLvmFields(fields_def, wanted_field_names):
+    """Returns unique list of fields wanted from LVM command.
+
+    @type fields_def: list
+    @param fields_def: Field definitions
+    @type wanted_field_names: list
+    @param wanted_field_names: List of requested fields
+
+    """
+    field_to_idx = dict([(field_name, idx)
+                         for (idx, (field_name, _, _)) in
+                         enumerate(fields_def)])
+
+    lvm_fields = []
+
+    for field_name in wanted_field_names:
+      try:
+        idx = field_to_idx[field_name]
+      except IndexError:
+        raise errors.StorageError("Unknown field: %r" % field_name)
+
+      (_, lvm_names, _) = fields_def[idx]
+
+      lvm_fields.extend(lvm_names)
+
+    return utils.UniqueSequence(lvm_fields)
+
+  @classmethod
+  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
+    """Builds the final result list.
+
+    @type cmd_result: iterable
+    @param cmd_result: Iterable of LVM command output (iterable of lists)
+    @type fields_def: list
+    @param fields_def: Field definitions
+    @type wanted_field_names: list
+    @param wanted_field_names: List of requested fields
+    @type lvm_fields: list
+    @param lvm_fields: LVM fields
+
+    """
+    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)])
+
+    data = []
+    for raw_data in cmd_result:
+      row = []
+
+      for field_name in wanted_field_names:
+        (_, 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)
+
+    return data
+
+  @staticmethod
+  def _BuildListCommand(cmd, sep, options, name):
+    """Builds LVM command line.
+
+    @type cmd: string
+    @param cmd: Command name
+    @type sep: string
+    @param sep: Field separator character
+    @type options: list of strings
+    @param options: Wanted LVM fields
+    @type name: name or None
+    @param name: Name of requested entity
+
+    """
+    args = [cmd,
+            "--noheadings", "--units=m", "--nosuffix",
+            "--separator", sep,
+            "--options", ",".join(options)]
+
+    if name is not None:
+      args.append(name)
+
+    return args
+
+  @staticmethod
+  def _RunListCommand(args):
+    """Run LVM command.
+
+    """
+    result = utils.RunCmd(args)
+
+    if result.failed:
+      raise errors.StorageError("Failed to run %r, command output: %s" %
+                                (args[0], result.output))
+
+    return result.stdout
+
+  @staticmethod
+  def _SplitList(data, sep, fieldcount):
+    """Splits LVM command output into rows and fields.
+
+    @type data: string
+    @param data: LVM command output
+    @type sep: string
+    @param sep: Field separator character
+    @type fieldcount: int
+    @param fieldcount: Expected number of fields
+
+    """
+    for line in data.splitlines():
+      fields = line.strip().split(sep)
+
+      if len(fields) != fieldcount:
+        logging.warning("Invalid line returned from lvm command: %s", line)
+        continue
+
+      yield fields
+
+
+def _LvmPvGetAllocatable(attr):
+  """Determines whether LVM PV is allocatable.
+
+  @rtype: bool
+
+  """
+  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"], _LvmPvGetAllocatable),
+    ]
+
+  def _SetAllocatable(self, name, allocatable):
+    """Sets the "allocatable" flag on a physical volume.
+
+    @type name: string
+    @param name: Physical volume name
+    @type allocatable: bool
+    @param allocatable: Whether to set the "allocatable" flag
+
+    """
+    args = ["pvchange", "--allocatable"]
+
+    if allocatable:
+      args.append("y")
+    else:
+      args.append("n")
+
+    args.append(name)
+
+    result = utils.RunCmd(args)
+    if result.failed:
+      raise errors.StorageError("Failed to modify physical volume,"
+                                " pvchange output: %s" %
+                                result.output)
+
+  def Modify(self, name, changes):
+    """Modifies flags on a physical volume.
+
+    See L{_Base.Modify}.
+
+    """
+    if constants.SF_ALLOCATABLE in changes:
+      self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
+      del changes[constants.SF_ALLOCATABLE]
+
+    # Other changes will be handled (and maybe refused) by the base class.
+    return _LvmBase.Modify(self, name, changes)
+
+
+class LvmVgStorage(_LvmBase):
+  """LVM Volume Group storage unit.
+
+  """
+  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_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 = {
+  constants.ST_FILE: FileStorage,
+  constants.ST_LVM_PV: LvmPvStorage,
+  constants.ST_LVM_VG: LvmVgStorage,
+  }
+
+
+def GetStorageClass(name):
+  """Returns the class for a storage type.
+
+  @type name: string
+  @param name: Storage type
+
+  """
+  try:
+    return _STORAGE_TYPES[name]
+  except KeyError:
+    raise errors.StorageError("Unknown storage type: %r" % name)
+
+
+def GetStorage(name, *args):
+  """Factory function for storage methods.
+
+  @type name: string
+  @param name: Storage type
+
+  """
+  return GetStorageClass(name)(*args)
diff --git a/lib/server/noded.py b/lib/server/noded.py
index a7d7a88..48dc305 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -45,7 +45,7 @@ from ganeti import jstore
 from ganeti import daemon
 from ganeti import http
 from ganeti import utils
-from ganeti import storage
+from ganeti import container
 from ganeti import serializer
 from ganeti import netutils
 from ganeti import pathutils
@@ -532,7 +532,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (su_name, su_args, name, fields) = params
-    return storage.GetStorage(su_name, *su_args).List(name, fields)
+    return container.GetStorage(su_name, *su_args).List(name, fields)
 
   @staticmethod
   def perspective_storage_modify(params):
@@ -540,7 +540,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (su_name, su_args, name, changes) = params
-    return storage.GetStorage(su_name, *su_args).Modify(name, changes)
+    return container.GetStorage(su_name, *su_args).Modify(name, changes)
 
   @staticmethod
   def perspective_storage_execute(params):
@@ -548,7 +548,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (su_name, su_args, name, op) = params
-    return storage.GetStorage(su_name, *su_args).Execute(name, op)
+    return container.GetStorage(su_name, *su_args).Execute(name, op)
 
   # bridge  --------------------------
 
diff --git a/lib/storage.py b/lib/storage.py
deleted file mode 100644
index d77d80b..0000000
--- a/lib/storage.py
+++ /dev/null
@@ -1,494 +0,0 @@
-#
-#
-
-# 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Storage container abstraction.
-
-"""
-
-# 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
-
-from ganeti import errors
-from ganeti import constants
-from ganeti import utils
-
-
-def _ParseSize(value):
-  return int(round(float(value), 0))
-
-
-class _Base:
-  """Base class for storage abstraction.
-
-  """
-  def List(self, name, fields):
-    """Returns a list of all entities within the storage unit.
-
-    @type name: string or None
-    @param name: Entity name or None for all
-    @type fields: list
-    @param fields: List with all requested result fields (order is preserved)
-
-    """
-    raise NotImplementedError()
-
-  def Modify(self, name, changes): # pylint: disable=W0613
-    """Modifies an entity within the storage unit.
-
-    @type name: string
-    @param name: Entity name
-    @type changes: dict
-    @param changes: New field values
-
-    """
-    # Don't raise an error if no changes are requested
-    if changes:
-      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): # pylint: disable=W0223
-  """File storage unit.
-
-  """
-  def __init__(self, paths):
-    """Initializes this class.
-
-    @type paths: list
-    @param paths: List of file storage paths
-
-    """
-    self._paths = paths
-
-  def List(self, name, fields):
-    """Returns a list of all entities within the storage unit.
-
-    See L{_Base.List}.
-
-    """
-    rows = []
-
-    if name is None:
-      paths = self._paths
-    else:
-      paths = [name]
-
-    for path in paths:
-      rows.append(self._ListInner(path, fields))
-
-    return rows
-
-  @staticmethod
-  def _ListInner(path, fields):
-    """Gathers requested information from directory.
-
-    @type path: string
-    @param path: Path to directory
-    @type fields: list
-    @param fields: Requested fields
-
-    """
-    values = []
-
-    # Pre-calculate information in case it's requested more than once
-    if constants.SF_USED in fields:
-      dirsize = utils.CalculateDirectorySize(path)
-    else:
-      dirsize = None
-
-    if constants.SF_FREE in fields or constants.SF_SIZE in fields:
-      fsstats = utils.GetFilesystemStats(path)
-    else:
-      fsstats = None
-
-    # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
-    for field_name in fields:
-      if field_name == constants.SF_NAME:
-        values.append(path)
-
-      elif field_name == constants.SF_USED:
-        values.append(dirsize)
-
-      elif field_name == constants.SF_FREE:
-        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)
-
-    return values
-
-
-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
-  LIST_FIELDS = None
-
-  def List(self, name, wanted_field_names):
-    """Returns a list of all entities within the storage unit.
-
-    See L{_Base.List}.
-
-    """
-    # Get needed LVM fields
-    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
-
-    # Build LVM command
-    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
-                                      lvm_fields, name)
-
-    # Run LVM command
-    cmd_result = self._RunListCommand(cmd_args)
-
-    # Split and rearrange LVM command output
-    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
-                                           len(lvm_fields)),
-                           self.LIST_FIELDS,
-                           wanted_field_names,
-                           lvm_fields)
-
-  @staticmethod
-  def _GetLvmFields(fields_def, wanted_field_names):
-    """Returns unique list of fields wanted from LVM command.
-
-    @type fields_def: list
-    @param fields_def: Field definitions
-    @type wanted_field_names: list
-    @param wanted_field_names: List of requested fields
-
-    """
-    field_to_idx = dict([(field_name, idx)
-                         for (idx, (field_name, _, _)) in
-                         enumerate(fields_def)])
-
-    lvm_fields = []
-
-    for field_name in wanted_field_names:
-      try:
-        idx = field_to_idx[field_name]
-      except IndexError:
-        raise errors.StorageError("Unknown field: %r" % field_name)
-
-      (_, lvm_names, _) = fields_def[idx]
-
-      lvm_fields.extend(lvm_names)
-
-    return utils.UniqueSequence(lvm_fields)
-
-  @classmethod
-  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
-    """Builds the final result list.
-
-    @type cmd_result: iterable
-    @param cmd_result: Iterable of LVM command output (iterable of lists)
-    @type fields_def: list
-    @param fields_def: Field definitions
-    @type wanted_field_names: list
-    @param wanted_field_names: List of requested fields
-    @type lvm_fields: list
-    @param lvm_fields: LVM fields
-
-    """
-    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)])
-
-    data = []
-    for raw_data in cmd_result:
-      row = []
-
-      for field_name in wanted_field_names:
-        (_, 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)
-
-    return data
-
-  @staticmethod
-  def _BuildListCommand(cmd, sep, options, name):
-    """Builds LVM command line.
-
-    @type cmd: string
-    @param cmd: Command name
-    @type sep: string
-    @param sep: Field separator character
-    @type options: list of strings
-    @param options: Wanted LVM fields
-    @type name: name or None
-    @param name: Name of requested entity
-
-    """
-    args = [cmd,
-            "--noheadings", "--units=m", "--nosuffix",
-            "--separator", sep,
-            "--options", ",".join(options)]
-
-    if name is not None:
-      args.append(name)
-
-    return args
-
-  @staticmethod
-  def _RunListCommand(args):
-    """Run LVM command.
-
-    """
-    result = utils.RunCmd(args)
-
-    if result.failed:
-      raise errors.StorageError("Failed to run %r, command output: %s" %
-                                (args[0], result.output))
-
-    return result.stdout
-
-  @staticmethod
-  def _SplitList(data, sep, fieldcount):
-    """Splits LVM command output into rows and fields.
-
-    @type data: string
-    @param data: LVM command output
-    @type sep: string
-    @param sep: Field separator character
-    @type fieldcount: int
-    @param fieldcount: Expected number of fields
-
-    """
-    for line in data.splitlines():
-      fields = line.strip().split(sep)
-
-      if len(fields) != fieldcount:
-        logging.warning("Invalid line returned from lvm command: %s", line)
-        continue
-
-      yield fields
-
-
-def _LvmPvGetAllocatable(attr):
-  """Determines whether LVM PV is allocatable.
-
-  @rtype: bool
-
-  """
-  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"], _LvmPvGetAllocatable),
-    ]
-
-  def _SetAllocatable(self, name, allocatable):
-    """Sets the "allocatable" flag on a physical volume.
-
-    @type name: string
-    @param name: Physical volume name
-    @type allocatable: bool
-    @param allocatable: Whether to set the "allocatable" flag
-
-    """
-    args = ["pvchange", "--allocatable"]
-
-    if allocatable:
-      args.append("y")
-    else:
-      args.append("n")
-
-    args.append(name)
-
-    result = utils.RunCmd(args)
-    if result.failed:
-      raise errors.StorageError("Failed to modify physical volume,"
-                                " pvchange output: %s" %
-                                result.output)
-
-  def Modify(self, name, changes):
-    """Modifies flags on a physical volume.
-
-    See L{_Base.Modify}.
-
-    """
-    if constants.SF_ALLOCATABLE in changes:
-      self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
-      del changes[constants.SF_ALLOCATABLE]
-
-    # Other changes will be handled (and maybe refused) by the base class.
-    return _LvmBase.Modify(self, name, changes)
-
-
-class LvmVgStorage(_LvmBase):
-  """LVM Volume Group storage unit.
-
-  """
-  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_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 = {
-  constants.ST_FILE: FileStorage,
-  constants.ST_LVM_PV: LvmPvStorage,
-  constants.ST_LVM_VG: LvmVgStorage,
-  }
-
-
-def GetStorageClass(name):
-  """Returns the class for a storage type.
-
-  @type name: string
-  @param name: Storage type
-
-  """
-  try:
-    return _STORAGE_TYPES[name]
-  except KeyError:
-    raise errors.StorageError("Unknown storage type: %r" % name)
-
-
-def GetStorage(name, *args):
-  """Factory function for storage methods.
-
-  @type name: string
-  @param name: Storage type
-
-  """
-  return GetStorageClass(name)(*args)
diff --git a/test/py/ganeti.container_unittest.py 
b/test/py/ganeti.container_unittest.py
new file mode 100755
index 0000000..808c1cc
--- /dev/null
+++ b/test/py/ganeti.container_unittest.py
@@ -0,0 +1,113 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for testing ganeti.storage.container"""
+
+import re
+import unittest
+import random
+
+from ganeti import constants
+from ganeti import utils
+from ganeti import compat
+from ganeti import errors
+from ganeti import container
+
+import testutils
+
+
+class TestVGReduce(testutils.GanetiTestCase):
+  VGNAME = "xenvg"
+  LIST_CMD = container.LvmVgStorage.LIST_COMMAND
+  VGREDUCE_CMD = container.LvmVgStorage.VGREDUCE_COMMAND
+
+  def _runCmd(self, cmd, **kwargs):
+    if not self.run_history:
+      self.fail("Empty run results")
+    exp_cmd, result = self.run_history.pop(0)
+    self.assertEqual(cmd, exp_cmd)
+    return result
+
+  def testOldVersion(self):
+    lvmvg = container.LvmVgStorage()
+    stdout = testutils.ReadTestData("vgreduce-removemissing-2.02.02.txt")
+    vgs_fail = testutils.ReadTestData("vgs-missing-pvs-2.02.02.txt")
+    self.run_history = [
+      ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
+       utils.RunResult(0, None, stdout, "", "", None, None)),
+      ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
+       utils.RunResult(0, None, "", "", "", None, None)),
+      ]
+    lvmvg._RemoveMissing(self.VGNAME, _runcmd_fn=self._runCmd)
+    self.assertEqual(self.run_history, [])
+    for ecode, out in [(1, ""), (0, vgs_fail)]:
+      self.run_history = [
+        ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
+         utils.RunResult(0, None, stdout, "", "", None, None)),
+        ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
+         utils.RunResult(ecode, None, out, "", "", None, None)),
+        ]
+      self.assertRaises(errors.StorageError, lvmvg._RemoveMissing, self.VGNAME,
+                        _runcmd_fn=self._runCmd)
+      self.assertEqual(self.run_history, [])
+
+  def testNewVersion(self):
+    lvmvg = container.LvmVgStorage()
+    stdout1 = testutils.ReadTestData("vgreduce-removemissing-2.02.66-fail.txt")
+    stdout2 = testutils.ReadTestData("vgreduce-removemissing-2.02.66-ok.txt")
+    vgs_fail = testutils.ReadTestData("vgs-missing-pvs-2.02.66.txt")
+    # first: require --fail, check that it's used
+    self.run_history = [
+      ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
+       utils.RunResult(0, None, stdout1, "", "", None, None)),
+      ([self.VGREDUCE_CMD, "--removemissing", "--force", self.VGNAME],
+       utils.RunResult(0, None, stdout2, "", "", None, None)),
+      ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
+       utils.RunResult(0, None, "", "", "", None, None)),
+      ]
+    lvmvg._RemoveMissing(self.VGNAME, _runcmd_fn=self._runCmd)
+    self.assertEqual(self.run_history, [])
+    # second: make sure --fail is not used if not needed
+    self.run_history = [
+      ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
+       utils.RunResult(0, None, stdout2, "", "", None, None)),
+      ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
+       utils.RunResult(0, None, "", "", "", None, None)),
+      ]
+    lvmvg._RemoveMissing(self.VGNAME, _runcmd_fn=self._runCmd)
+    self.assertEqual(self.run_history, [])
+    # third: make sure we error out if vgs doesn't find the volume
+    for ecode, out in [(1, ""), (0, vgs_fail)]:
+      self.run_history = [
+        ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
+         utils.RunResult(0, None, stdout1, "", "", None, None)),
+        ([self.VGREDUCE_CMD, "--removemissing", "--force", self.VGNAME],
+         utils.RunResult(0, None, stdout2, "", "", None, None)),
+        ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
+         utils.RunResult(ecode, None, out, "", "", None, None)),
+        ]
+      self.assertRaises(errors.StorageError, lvmvg._RemoveMissing, self.VGNAME,
+                        _runcmd_fn=self._runCmd)
+      self.assertEqual(self.run_history, [])
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.storage_unittest.py 
b/test/py/ganeti.storage_unittest.py
deleted file mode 100755
index 0c62abc..0000000
--- a/test/py/ganeti.storage_unittest.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Script for testing ganeti.storage"""
-
-import re
-import unittest
-import random
-
-from ganeti import constants
-from ganeti import utils
-from ganeti import compat
-from ganeti import errors
-from ganeti import storage
-
-import testutils
-
-
-class TestVGReduce(testutils.GanetiTestCase):
-  VGNAME = "xenvg"
-  LIST_CMD = storage.LvmVgStorage.LIST_COMMAND
-  VGREDUCE_CMD = storage.LvmVgStorage.VGREDUCE_COMMAND
-
-  def _runCmd(self, cmd, **kwargs):
-    if not self.run_history:
-      self.fail("Empty run results")
-    exp_cmd, result = self.run_history.pop(0)
-    self.assertEqual(cmd, exp_cmd)
-    return result
-
-  def testOldVersion(self):
-    lvmvg = storage.LvmVgStorage()
-    stdout = testutils.ReadTestData("vgreduce-removemissing-2.02.02.txt")
-    vgs_fail = testutils.ReadTestData("vgs-missing-pvs-2.02.02.txt")
-    self.run_history = [
-      ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
-       utils.RunResult(0, None, stdout, "", "", None, None)),
-      ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
-       utils.RunResult(0, None, "", "", "", None, None)),
-      ]
-    lvmvg._RemoveMissing(self.VGNAME, _runcmd_fn=self._runCmd)
-    self.assertEqual(self.run_history, [])
-    for ecode, out in [(1, ""), (0, vgs_fail)]:
-      self.run_history = [
-        ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
-         utils.RunResult(0, None, stdout, "", "", None, None)),
-        ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
-         utils.RunResult(ecode, None, out, "", "", None, None)),
-        ]
-      self.assertRaises(errors.StorageError, lvmvg._RemoveMissing, self.VGNAME,
-                        _runcmd_fn=self._runCmd)
-      self.assertEqual(self.run_history, [])
-
-  def testNewVersion(self):
-    lvmvg = storage.LvmVgStorage()
-    stdout1 = testutils.ReadTestData("vgreduce-removemissing-2.02.66-fail.txt")
-    stdout2 = testutils.ReadTestData("vgreduce-removemissing-2.02.66-ok.txt")
-    vgs_fail = testutils.ReadTestData("vgs-missing-pvs-2.02.66.txt")
-    # first: require --fail, check that it's used
-    self.run_history = [
-      ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
-       utils.RunResult(0, None, stdout1, "", "", None, None)),
-      ([self.VGREDUCE_CMD, "--removemissing", "--force", self.VGNAME],
-       utils.RunResult(0, None, stdout2, "", "", None, None)),
-      ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
-       utils.RunResult(0, None, "", "", "", None, None)),
-      ]
-    lvmvg._RemoveMissing(self.VGNAME, _runcmd_fn=self._runCmd)
-    self.assertEqual(self.run_history, [])
-    # second: make sure --fail is not used if not needed
-    self.run_history = [
-      ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
-       utils.RunResult(0, None, stdout2, "", "", None, None)),
-      ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
-       utils.RunResult(0, None, "", "", "", None, None)),
-      ]
-    lvmvg._RemoveMissing(self.VGNAME, _runcmd_fn=self._runCmd)
-    self.assertEqual(self.run_history, [])
-    # third: make sure we error out if vgs doesn't find the volume
-    for ecode, out in [(1, ""), (0, vgs_fail)]:
-      self.run_history = [
-        ([self.VGREDUCE_CMD, "--removemissing", self.VGNAME],
-         utils.RunResult(0, None, stdout1, "", "", None, None)),
-        ([self.VGREDUCE_CMD, "--removemissing", "--force", self.VGNAME],
-         utils.RunResult(0, None, stdout2, "", "", None, None)),
-        ([self.LIST_CMD, "--noheadings", "--nosuffix", self.VGNAME],
-         utils.RunResult(ecode, None, out, "", "", None, None)),
-        ]
-      self.assertRaises(errors.StorageError, lvmvg._RemoveMissing, self.VGNAME,
-                        _runcmd_fn=self._runCmd)
-      self.assertEqual(self.run_history, [])
-
-
-if __name__ == "__main__":
-  testutils.GanetiTestProgram()
-- 
1.8.2.1

Reply via email to