Base classes holding common functionality is extracted into base.py.
Utility functions used by both base classes and subclasses is moved to
common.py.

Signed-off-by: Thomas Thrainer <[email protected]>
---
 Makefile.am            |   4 +-
 lib/cmdlib/__init__.py | 538 +------------------------------------------------
 lib/cmdlib/base.py     | 536 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/cmdlib/common.py   |  51 +++++
 4 files changed, 595 insertions(+), 534 deletions(-)
 create mode 100644 lib/cmdlib/base.py
 create mode 100644 lib/cmdlib/common.py

diff --git a/Makefile.am b/Makefile.am
index 4a68bdc..db39e7e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -308,7 +308,9 @@ client_PYTHON = \
        lib/client/gnt_storage.py
 
 cmdlib_PYTHON = \
-       lib/cmdlib/__init__.py
+       lib/cmdlib/__init__.py \
+       lib/cmdlib/common.py \
+       lib/cmdlib/base.py
 
 hypervisor_PYTHON = \
        lib/hypervisor/__init__.py \
diff --git a/lib/cmdlib/__init__.py b/lib/cmdlib/__init__.py
index 64b8c47..0166528 100644
--- a/lib/cmdlib/__init__.py
+++ b/lib/cmdlib/__init__.py
@@ -29,7 +29,6 @@
 # C0302: since we have waaaay too many lines in this module
 
 import os
-import os.path
 import time
 import re
 import logging
@@ -64,6 +63,11 @@ from ganeti import vcluster
 from ganeti import network
 from ganeti.masterd import iallocator
 
+from ganeti.cmdlib.base import ResultWithJobs, LogicalUnit, NoHooksLU, \
+  Tasklet, _QueryBase
+from ganeti.cmdlib.common import _ExpandInstanceName, _ExpandItemName, \
+  _ExpandNodeName
+
 import ganeti.masterd.instance # pylint: disable=W0611
 
 
@@ -78,511 +82,6 @@ CAN_CHANGE_INSTANCE_OFFLINE = (frozenset(INSTANCE_DOWN) | 
frozenset([
   ]))
 
 
-class ResultWithJobs:
-  """Data container for LU results with jobs.
-
-  Instances of this class returned from L{LogicalUnit.Exec} will be recognized
-  by L{mcpu._ProcessResult}. The latter will then submit the jobs
-  contained in the C{jobs} attribute and include the job IDs in the opcode
-  result.
-
-  """
-  def __init__(self, jobs, **kwargs):
-    """Initializes this class.
-
-    Additional return values can be specified as keyword arguments.
-
-    @type jobs: list of lists of L{opcode.OpCode}
-    @param jobs: A list of lists of opcode objects
-
-    """
-    self.jobs = jobs
-    self.other = kwargs
-
-
-class LogicalUnit(object):
-  """Logical Unit base class.
-
-  Subclasses must follow these rules:
-    - implement ExpandNames
-    - implement CheckPrereq (except when tasklets are used)
-    - implement Exec (except when tasklets are used)
-    - implement BuildHooksEnv
-    - implement BuildHooksNodes
-    - redefine HPATH and HTYPE
-    - optionally redefine their run requirements:
-        REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
-
-  Note that all commands require root permissions.
-
-  @ivar dry_run_result: the value (if any) that will be returned to the caller
-      in dry-run mode (signalled by opcode dry_run parameter)
-
-  """
-  HPATH = None
-  HTYPE = None
-  REQ_BGL = True
-
-  def __init__(self, processor, op, context, rpc_runner):
-    """Constructor for LogicalUnit.
-
-    This needs to be overridden in derived classes in order to check op
-    validity.
-
-    """
-    self.proc = processor
-    self.op = op
-    self.cfg = context.cfg
-    self.glm = context.glm
-    # readability alias
-    self.owned_locks = context.glm.list_owned
-    self.context = context
-    self.rpc = rpc_runner
-
-    # Dictionaries used to declare locking needs to mcpu
-    self.needed_locks = None
-    self.share_locks = dict.fromkeys(locking.LEVELS, 0)
-    self.opportunistic_locks = dict.fromkeys(locking.LEVELS, False)
-
-    self.add_locks = {}
-    self.remove_locks = {}
-
-    # Used to force good behavior when calling helper functions
-    self.recalculate_locks = {}
-
-    # logging
-    self.Log = processor.Log # pylint: disable=C0103
-    self.LogWarning = processor.LogWarning # pylint: disable=C0103
-    self.LogInfo = processor.LogInfo # pylint: disable=C0103
-    self.LogStep = processor.LogStep # pylint: disable=C0103
-    # support for dry-run
-    self.dry_run_result = None
-    # support for generic debug attribute
-    if (not hasattr(self.op, "debug_level") or
-        not isinstance(self.op.debug_level, int)):
-      self.op.debug_level = 0
-
-    # Tasklets
-    self.tasklets = None
-
-    # Validate opcode parameters and set defaults
-    self.op.Validate(True)
-
-    self.CheckArguments()
-
-  def CheckArguments(self):
-    """Check syntactic validity for the opcode arguments.
-
-    This method is for doing a simple syntactic check and ensure
-    validity of opcode parameters, without any cluster-related
-    checks. While the same can be accomplished in ExpandNames and/or
-    CheckPrereq, doing these separate is better because:
-
-      - ExpandNames is left as as purely a lock-related function
-      - CheckPrereq is run after we have acquired locks (and possible
-        waited for them)
-
-    The function is allowed to change the self.op attribute so that
-    later methods can no longer worry about missing parameters.
-
-    """
-    pass
-
-  def ExpandNames(self):
-    """Expand names for this LU.
-
-    This method is called before starting to execute the opcode, and it should
-    update all the parameters of the opcode to their canonical form (e.g. a
-    short node name must be fully expanded after this method has successfully
-    completed). This way locking, hooks, logging, etc. can work correctly.
-
-    LUs which implement this method must also populate the self.needed_locks
-    member, as a dict with lock levels as keys, and a list of needed lock names
-    as values. Rules:
-
-      - use an empty dict if you don't need any lock
-      - if you don't need any lock at a particular level omit that
-        level (note that in this case C{DeclareLocks} won't be called
-        at all for that level)
-      - if you need locks at a level, but you can't calculate it in
-        this function, initialise that level with an empty list and do
-        further processing in L{LogicalUnit.DeclareLocks} (see that
-        function's docstring)
-      - don't put anything for the BGL level
-      - if you want all locks at a level use L{locking.ALL_SET} as a value
-
-    If you need to share locks (rather than acquire them exclusively) at one
-    level you can modify self.share_locks, setting a true value (usually 1) for
-    that level. By default locks are not shared.
-
-    This function can also define a list of tasklets, which then will be
-    executed in order instead of the usual LU-level CheckPrereq and Exec
-    functions, if those are not defined by the LU.
-
-    Examples::
-
-      # Acquire all nodes and one instance
-      self.needed_locks = {
-        locking.LEVEL_NODE: locking.ALL_SET,
-        locking.LEVEL_INSTANCE: ['instance1.example.com'],
-      }
-      # Acquire just two nodes
-      self.needed_locks = {
-        locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
-      }
-      # Acquire no locks
-      self.needed_locks = {} # No, you can't leave it to the default value None
-
-    """
-    # The implementation of this method is mandatory only if the new LU is
-    # concurrent, so that old LUs don't need to be changed all at the same
-    # time.
-    if self.REQ_BGL:
-      self.needed_locks = {} # Exclusive LUs don't need locks.
-    else:
-      raise NotImplementedError
-
-  def DeclareLocks(self, level):
-    """Declare LU locking needs for a level
-
-    While most LUs can just declare their locking needs at ExpandNames time,
-    sometimes there's the need to calculate some locks after having acquired
-    the ones before. This function is called just before acquiring locks at a
-    particular level, but after acquiring the ones at lower levels, and permits
-    such calculations. It can be used to modify self.needed_locks, and by
-    default it does nothing.
-
-    This function is only called if you have something already set in
-    self.needed_locks for the level.
-
-    @param level: Locking level which is going to be locked
-    @type level: member of L{ganeti.locking.LEVELS}
-
-    """
-
-  def CheckPrereq(self):
-    """Check prerequisites for this LU.
-
-    This method should check that the prerequisites for the execution
-    of this LU are fulfilled. It can do internode communication, but
-    it should be idempotent - no cluster or system changes are
-    allowed.
-
-    The method should raise errors.OpPrereqError in case something is
-    not fulfilled. Its return value is ignored.
-
-    This method should also update all the parameters of the opcode to
-    their canonical form if it hasn't been done by ExpandNames before.
-
-    """
-    if self.tasklets is not None:
-      for (idx, tl) in enumerate(self.tasklets):
-        logging.debug("Checking prerequisites for tasklet %s/%s",
-                      idx + 1, len(self.tasklets))
-        tl.CheckPrereq()
-    else:
-      pass
-
-  def Exec(self, feedback_fn):
-    """Execute the LU.
-
-    This method should implement the actual work. It should raise
-    errors.OpExecError for failures that are somewhat dealt with in
-    code, or expected.
-
-    """
-    if self.tasklets is not None:
-      for (idx, tl) in enumerate(self.tasklets):
-        logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
-        tl.Exec(feedback_fn)
-    else:
-      raise NotImplementedError
-
-  def BuildHooksEnv(self):
-    """Build hooks environment for this LU.
-
-    @rtype: dict
-    @return: Dictionary containing the environment that will be used for
-      running the hooks for this LU. The keys of the dict must not be prefixed
-      with "GANETI_"--that'll be added by the hooks runner. The hooks runner
-      will extend the environment with additional variables. If no environment
-      should be defined, an empty dictionary should be returned (not C{None}).
-    @note: If the C{HPATH} attribute of the LU class is C{None}, this function
-      will not be called.
-
-    """
-    raise NotImplementedError
-
-  def BuildHooksNodes(self):
-    """Build list of nodes to run LU's hooks.
-
-    @rtype: tuple; (list, list)
-    @return: Tuple containing a list of node names on which the hook
-      should run before the execution and a list of node names on which the
-      hook should run after the execution. No nodes should be returned as an
-      empty list (and not None).
-    @note: If the C{HPATH} attribute of the LU class is C{None}, this function
-      will not be called.
-
-    """
-    raise NotImplementedError
-
-  def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
-    """Notify the LU about the results of its hooks.
-
-    This method is called every time a hooks phase is executed, and notifies
-    the Logical Unit about the hooks' result. The LU can then use it to alter
-    its result based on the hooks.  By default the method does nothing and the
-    previous result is passed back unchanged but any LU can define it if it
-    wants to use the local cluster hook-scripts somehow.
-
-    @param phase: one of L{constants.HOOKS_PHASE_POST} or
-        L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
-    @param hook_results: the results of the multi-node hooks rpc call
-    @param feedback_fn: function used send feedback back to the caller
-    @param lu_result: the previous Exec result this LU had, or None
-        in the PRE phase
-    @return: the new Exec result, based on the previous result
-        and hook results
-
-    """
-    # API must be kept, thus we ignore the unused argument and could
-    # be a function warnings
-    # pylint: disable=W0613,R0201
-    return lu_result
-
-  def _ExpandAndLockInstance(self):
-    """Helper function to expand and lock an instance.
-
-    Many LUs that work on an instance take its name in self.op.instance_name
-    and need to expand it and then declare the expanded name for locking. This
-    function does it, and then updates self.op.instance_name to the expanded
-    name. It also initializes needed_locks as a dict, if this hasn't been done
-    before.
-
-    """
-    if self.needed_locks is None:
-      self.needed_locks = {}
-    else:
-      assert locking.LEVEL_INSTANCE not in self.needed_locks, \
-        "_ExpandAndLockInstance called with instance-level locks set"
-    self.op.instance_name = _ExpandInstanceName(self.cfg,
-                                                self.op.instance_name)
-    self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
-
-  def _LockInstancesNodes(self, primary_only=False,
-                          level=locking.LEVEL_NODE):
-    """Helper function to declare instances' nodes for locking.
-
-    This function should be called after locking one or more instances to lock
-    their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
-    with all primary or secondary nodes for instances already locked and
-    present in self.needed_locks[locking.LEVEL_INSTANCE].
-
-    It should be called from DeclareLocks, and for safety only works if
-    self.recalculate_locks[locking.LEVEL_NODE] is set.
-
-    In the future it may grow parameters to just lock some instance's nodes, or
-    to just lock primaries or secondary nodes, if needed.
-
-    If should be called in DeclareLocks in a way similar to::
-
-      if level == locking.LEVEL_NODE:
-        self._LockInstancesNodes()
-
-    @type primary_only: boolean
-    @param primary_only: only lock primary nodes of locked instances
-    @param level: Which lock level to use for locking nodes
-
-    """
-    assert level in self.recalculate_locks, \
-      "_LockInstancesNodes helper function called with no nodes to recalculate"
-
-    # TODO: check if we're really been called with the instance locks held
-
-    # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
-    # future we might want to have different behaviors depending on the value
-    # of self.recalculate_locks[locking.LEVEL_NODE]
-    wanted_nodes = []
-    locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
-    for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
-      wanted_nodes.append(instance.primary_node)
-      if not primary_only:
-        wanted_nodes.extend(instance.secondary_nodes)
-
-    if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
-      self.needed_locks[level] = wanted_nodes
-    elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
-      self.needed_locks[level].extend(wanted_nodes)
-    else:
-      raise errors.ProgrammerError("Unknown recalculation mode")
-
-    del self.recalculate_locks[level]
-
-
-class NoHooksLU(LogicalUnit): # pylint: disable=W0223
-  """Simple LU which runs no hooks.
-
-  This LU is intended as a parent for other LogicalUnits which will
-  run no hooks, in order to reduce duplicate code.
-
-  """
-  HPATH = None
-  HTYPE = None
-
-  def BuildHooksEnv(self):
-    """Empty BuildHooksEnv for NoHooksLu.
-
-    This just raises an error.
-
-    """
-    raise AssertionError("BuildHooksEnv called for NoHooksLUs")
-
-  def BuildHooksNodes(self):
-    """Empty BuildHooksNodes for NoHooksLU.
-
-    """
-    raise AssertionError("BuildHooksNodes called for NoHooksLU")
-
-
-class Tasklet:
-  """Tasklet base class.
-
-  Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
-  they can mix legacy code with tasklets. Locking needs to be done in the LU,
-  tasklets know nothing about locks.
-
-  Subclasses must follow these rules:
-    - Implement CheckPrereq
-    - Implement Exec
-
-  """
-  def __init__(self, lu):
-    self.lu = lu
-
-    # Shortcuts
-    self.cfg = lu.cfg
-    self.rpc = lu.rpc
-
-  def CheckPrereq(self):
-    """Check prerequisites for this tasklets.
-
-    This method should check whether the prerequisites for the execution of
-    this tasklet are fulfilled. It can do internode communication, but it
-    should be idempotent - no cluster or system changes are allowed.
-
-    The method should raise errors.OpPrereqError in case something is not
-    fulfilled. Its return value is ignored.
-
-    This method should also update all parameters to their canonical form if it
-    hasn't been done before.
-
-    """
-    pass
-
-  def Exec(self, feedback_fn):
-    """Execute the tasklet.
-
-    This method should implement the actual work. It should raise
-    errors.OpExecError for failures that are somewhat dealt with in code, or
-    expected.
-
-    """
-    raise NotImplementedError
-
-
-class _QueryBase:
-  """Base for query utility classes.
-
-  """
-  #: Attribute holding field definitions
-  FIELDS = None
-
-  #: Field to sort by
-  SORT_FIELD = "name"
-
-  def __init__(self, qfilter, fields, use_locking):
-    """Initializes this class.
-
-    """
-    self.use_locking = use_locking
-
-    self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
-                             namefield=self.SORT_FIELD)
-    self.requested_data = self.query.RequestedData()
-    self.names = self.query.RequestedNames()
-
-    # Sort only if no names were requested
-    self.sort_by_name = not self.names
-
-    self.do_locking = None
-    self.wanted = None
-
-  def _GetNames(self, lu, all_names, lock_level):
-    """Helper function to determine names asked for in the query.
-
-    """
-    if self.do_locking:
-      names = lu.owned_locks(lock_level)
-    else:
-      names = all_names
-
-    if self.wanted == locking.ALL_SET:
-      assert not self.names
-      # caller didn't specify names, so ordering is not important
-      return utils.NiceSort(names)
-
-    # caller specified names and we must keep the same order
-    assert self.names
-    assert not self.do_locking or lu.glm.is_owned(lock_level)
-
-    missing = set(self.wanted).difference(names)
-    if missing:
-      raise errors.OpExecError("Some items were removed before retrieving"
-                               " their data: %s" % missing)
-
-    # Return expanded names
-    return self.wanted
-
-  def ExpandNames(self, lu):
-    """Expand names for this query.
-
-    See L{LogicalUnit.ExpandNames}.
-
-    """
-    raise NotImplementedError()
-
-  def DeclareLocks(self, lu, level):
-    """Declare locks for this query.
-
-    See L{LogicalUnit.DeclareLocks}.
-
-    """
-    raise NotImplementedError()
-
-  def _GetQueryData(self, lu):
-    """Collects all data for this query.
-
-    @return: Query data object
-
-    """
-    raise NotImplementedError()
-
-  def NewStyleQuery(self, lu):
-    """Collect data and execute query.
-
-    """
-    return query.GetQueryResponse(self.query, self._GetQueryData(lu),
-                                  sort_by_name=self.sort_by_name)
-
-  def OldStyleQuery(self, lu):
-    """Collect data and execute query.
-
-    """
-    return self.query.OldStyleQuery(self._GetQueryData(lu),
-                                    sort_by_name=self.sort_by_name)
-
-
 def _ShareAll():
   """Returns a dict declaring all lock levels shared.
 
@@ -1405,33 +904,6 @@ def _ComputeNewInstanceViolations(old_ipolicy, 
new_ipolicy, instances, cfg):
           _ComputeViolatingInstances(old_ipolicy, instances, cfg))
 
 
-def _ExpandItemName(fn, name, kind):
-  """Expand an item name.
-
-  @param fn: the function to use for expansion
-  @param name: requested item name
-  @param kind: text description ('Node' or 'Instance')
-  @return: the resolved (full) name
-  @raise errors.OpPrereqError: if the item is not found
-
-  """
-  full_name = fn(name)
-  if full_name is None:
-    raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
-                               errors.ECODE_NOENT)
-  return full_name
-
-
-def _ExpandNodeName(cfg, name):
-  """Wrapper over L{_ExpandItemName} for nodes."""
-  return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
-
-
-def _ExpandInstanceName(cfg, name):
-  """Wrapper over L{_ExpandItemName} for instance."""
-  return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
-
-
 def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6,
                          mac_prefix, tags):
   """Builds network related env variables for hooks
diff --git a/lib/cmdlib/base.py b/lib/cmdlib/base.py
new file mode 100644
index 0000000..142981f
--- /dev/null
+++ b/lib/cmdlib/base.py
@@ -0,0 +1,536 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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.
+
+
+"""Base classes and functions for cmdlib."""
+
+import logging
+
+from ganeti import errors
+from ganeti import constants
+from ganeti import locking
+from ganeti import query
+from ganeti import utils
+from ganeti.cmdlib.common import _ExpandInstanceName
+
+
+class ResultWithJobs:
+  """Data container for LU results with jobs.
+
+  Instances of this class returned from L{LogicalUnit.Exec} will be recognized
+  by L{mcpu._ProcessResult}. The latter will then submit the jobs
+  contained in the C{jobs} attribute and include the job IDs in the opcode
+  result.
+
+  """
+  def __init__(self, jobs, **kwargs):
+    """Initializes this class.
+
+    Additional return values can be specified as keyword arguments.
+
+    @type jobs: list of lists of L{opcode.OpCode}
+    @param jobs: A list of lists of opcode objects
+
+    """
+    self.jobs = jobs
+    self.other = kwargs
+
+
+class LogicalUnit(object):
+  """Logical Unit base class.
+
+  Subclasses must follow these rules:
+    - implement ExpandNames
+    - implement CheckPrereq (except when tasklets are used)
+    - implement Exec (except when tasklets are used)
+    - implement BuildHooksEnv
+    - implement BuildHooksNodes
+    - redefine HPATH and HTYPE
+    - optionally redefine their run requirements:
+        REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
+
+  Note that all commands require root permissions.
+
+  @ivar dry_run_result: the value (if any) that will be returned to the caller
+      in dry-run mode (signalled by opcode dry_run parameter)
+
+  """
+  HPATH = None
+  HTYPE = None
+  REQ_BGL = True
+
+  def __init__(self, processor, op, context, rpc_runner):
+    """Constructor for LogicalUnit.
+
+    This needs to be overridden in derived classes in order to check op
+    validity.
+
+    """
+    self.proc = processor
+    self.op = op
+    self.cfg = context.cfg
+    self.glm = context.glm
+    # readability alias
+    self.owned_locks = context.glm.list_owned
+    self.context = context
+    self.rpc = rpc_runner
+
+    # Dictionaries used to declare locking needs to mcpu
+    self.needed_locks = None
+    self.share_locks = dict.fromkeys(locking.LEVELS, 0)
+    self.opportunistic_locks = dict.fromkeys(locking.LEVELS, False)
+
+    self.add_locks = {}
+    self.remove_locks = {}
+
+    # Used to force good behavior when calling helper functions
+    self.recalculate_locks = {}
+
+    # logging
+    self.Log = processor.Log # pylint: disable=C0103
+    self.LogWarning = processor.LogWarning # pylint: disable=C0103
+    self.LogInfo = processor.LogInfo # pylint: disable=C0103
+    self.LogStep = processor.LogStep # pylint: disable=C0103
+    # support for dry-run
+    self.dry_run_result = None
+    # support for generic debug attribute
+    if (not hasattr(self.op, "debug_level") or
+        not isinstance(self.op.debug_level, int)):
+      self.op.debug_level = 0
+
+    # Tasklets
+    self.tasklets = None
+
+    # Validate opcode parameters and set defaults
+    self.op.Validate(True)
+
+    self.CheckArguments()
+
+  def CheckArguments(self):
+    """Check syntactic validity for the opcode arguments.
+
+    This method is for doing a simple syntactic check and ensure
+    validity of opcode parameters, without any cluster-related
+    checks. While the same can be accomplished in ExpandNames and/or
+    CheckPrereq, doing these separate is better because:
+
+      - ExpandNames is left as as purely a lock-related function
+      - CheckPrereq is run after we have acquired locks (and possible
+        waited for them)
+
+    The function is allowed to change the self.op attribute so that
+    later methods can no longer worry about missing parameters.
+
+    """
+    pass
+
+  def ExpandNames(self):
+    """Expand names for this LU.
+
+    This method is called before starting to execute the opcode, and it should
+    update all the parameters of the opcode to their canonical form (e.g. a
+    short node name must be fully expanded after this method has successfully
+    completed). This way locking, hooks, logging, etc. can work correctly.
+
+    LUs which implement this method must also populate the self.needed_locks
+    member, as a dict with lock levels as keys, and a list of needed lock names
+    as values. Rules:
+
+      - use an empty dict if you don't need any lock
+      - if you don't need any lock at a particular level omit that
+        level (note that in this case C{DeclareLocks} won't be called
+        at all for that level)
+      - if you need locks at a level, but you can't calculate it in
+        this function, initialise that level with an empty list and do
+        further processing in L{LogicalUnit.DeclareLocks} (see that
+        function's docstring)
+      - don't put anything for the BGL level
+      - if you want all locks at a level use L{locking.ALL_SET} as a value
+
+    If you need to share locks (rather than acquire them exclusively) at one
+    level you can modify self.share_locks, setting a true value (usually 1) for
+    that level. By default locks are not shared.
+
+    This function can also define a list of tasklets, which then will be
+    executed in order instead of the usual LU-level CheckPrereq and Exec
+    functions, if those are not defined by the LU.
+
+    Examples::
+
+      # Acquire all nodes and one instance
+      self.needed_locks = {
+        locking.LEVEL_NODE: locking.ALL_SET,
+        locking.LEVEL_INSTANCE: ['instance1.example.com'],
+      }
+      # Acquire just two nodes
+      self.needed_locks = {
+        locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
+      }
+      # Acquire no locks
+      self.needed_locks = {} # No, you can't leave it to the default value None
+
+    """
+    # The implementation of this method is mandatory only if the new LU is
+    # concurrent, so that old LUs don't need to be changed all at the same
+    # time.
+    if self.REQ_BGL:
+      self.needed_locks = {} # Exclusive LUs don't need locks.
+    else:
+      raise NotImplementedError
+
+  def DeclareLocks(self, level):
+    """Declare LU locking needs for a level
+
+    While most LUs can just declare their locking needs at ExpandNames time,
+    sometimes there's the need to calculate some locks after having acquired
+    the ones before. This function is called just before acquiring locks at a
+    particular level, but after acquiring the ones at lower levels, and permits
+    such calculations. It can be used to modify self.needed_locks, and by
+    default it does nothing.
+
+    This function is only called if you have something already set in
+    self.needed_locks for the level.
+
+    @param level: Locking level which is going to be locked
+    @type level: member of L{ganeti.locking.LEVELS}
+
+    """
+
+  def CheckPrereq(self):
+    """Check prerequisites for this LU.
+
+    This method should check that the prerequisites for the execution
+    of this LU are fulfilled. It can do internode communication, but
+    it should be idempotent - no cluster or system changes are
+    allowed.
+
+    The method should raise errors.OpPrereqError in case something is
+    not fulfilled. Its return value is ignored.
+
+    This method should also update all the parameters of the opcode to
+    their canonical form if it hasn't been done by ExpandNames before.
+
+    """
+    if self.tasklets is not None:
+      for (idx, tl) in enumerate(self.tasklets):
+        logging.debug("Checking prerequisites for tasklet %s/%s",
+                      idx + 1, len(self.tasklets))
+        tl.CheckPrereq()
+    else:
+      pass
+
+  def Exec(self, feedback_fn):
+    """Execute the LU.
+
+    This method should implement the actual work. It should raise
+    errors.OpExecError for failures that are somewhat dealt with in
+    code, or expected.
+
+    """
+    if self.tasklets is not None:
+      for (idx, tl) in enumerate(self.tasklets):
+        logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
+        tl.Exec(feedback_fn)
+    else:
+      raise NotImplementedError
+
+  def BuildHooksEnv(self):
+    """Build hooks environment for this LU.
+
+    @rtype: dict
+    @return: Dictionary containing the environment that will be used for
+      running the hooks for this LU. The keys of the dict must not be prefixed
+      with "GANETI_"--that'll be added by the hooks runner. The hooks runner
+      will extend the environment with additional variables. If no environment
+      should be defined, an empty dictionary should be returned (not C{None}).
+    @note: If the C{HPATH} attribute of the LU class is C{None}, this function
+      will not be called.
+
+    """
+    raise NotImplementedError
+
+  def BuildHooksNodes(self):
+    """Build list of nodes to run LU's hooks.
+
+    @rtype: tuple; (list, list)
+    @return: Tuple containing a list of node names on which the hook
+      should run before the execution and a list of node names on which the
+      hook should run after the execution. No nodes should be returned as an
+      empty list (and not None).
+    @note: If the C{HPATH} attribute of the LU class is C{None}, this function
+      will not be called.
+
+    """
+    raise NotImplementedError
+
+  def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
+    """Notify the LU about the results of its hooks.
+
+    This method is called every time a hooks phase is executed, and notifies
+    the Logical Unit about the hooks' result. The LU can then use it to alter
+    its result based on the hooks.  By default the method does nothing and the
+    previous result is passed back unchanged but any LU can define it if it
+    wants to use the local cluster hook-scripts somehow.
+
+    @param phase: one of L{constants.HOOKS_PHASE_POST} or
+        L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
+    @param hook_results: the results of the multi-node hooks rpc call
+    @param feedback_fn: function used send feedback back to the caller
+    @param lu_result: the previous Exec result this LU had, or None
+        in the PRE phase
+    @return: the new Exec result, based on the previous result
+        and hook results
+
+    """
+    # API must be kept, thus we ignore the unused argument and could
+    # be a function warnings
+    # pylint: disable=W0613,R0201
+    return lu_result
+
+  def _ExpandAndLockInstance(self):
+    """Helper function to expand and lock an instance.
+
+    Many LUs that work on an instance take its name in self.op.instance_name
+    and need to expand it and then declare the expanded name for locking. This
+    function does it, and then updates self.op.instance_name to the expanded
+    name. It also initializes needed_locks as a dict, if this hasn't been done
+    before.
+
+    """
+    if self.needed_locks is None:
+      self.needed_locks = {}
+    else:
+      assert locking.LEVEL_INSTANCE not in self.needed_locks, \
+        "_ExpandAndLockInstance called with instance-level locks set"
+    self.op.instance_name = _ExpandInstanceName(self.cfg,
+                                                self.op.instance_name)
+    self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
+
+  def _LockInstancesNodes(self, primary_only=False,
+                          level=locking.LEVEL_NODE):
+    """Helper function to declare instances' nodes for locking.
+
+    This function should be called after locking one or more instances to lock
+    their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
+    with all primary or secondary nodes for instances already locked and
+    present in self.needed_locks[locking.LEVEL_INSTANCE].
+
+    It should be called from DeclareLocks, and for safety only works if
+    self.recalculate_locks[locking.LEVEL_NODE] is set.
+
+    In the future it may grow parameters to just lock some instance's nodes, or
+    to just lock primaries or secondary nodes, if needed.
+
+    If should be called in DeclareLocks in a way similar to::
+
+      if level == locking.LEVEL_NODE:
+        self._LockInstancesNodes()
+
+    @type primary_only: boolean
+    @param primary_only: only lock primary nodes of locked instances
+    @param level: Which lock level to use for locking nodes
+
+    """
+    assert level in self.recalculate_locks, \
+      "_LockInstancesNodes helper function called with no nodes to recalculate"
+
+    # TODO: check if we're really been called with the instance locks held
+
+    # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
+    # future we might want to have different behaviors depending on the value
+    # of self.recalculate_locks[locking.LEVEL_NODE]
+    wanted_nodes = []
+    locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
+    for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
+      wanted_nodes.append(instance.primary_node)
+      if not primary_only:
+        wanted_nodes.extend(instance.secondary_nodes)
+
+    if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
+      self.needed_locks[level] = wanted_nodes
+    elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
+      self.needed_locks[level].extend(wanted_nodes)
+    else:
+      raise errors.ProgrammerError("Unknown recalculation mode")
+
+    del self.recalculate_locks[level]
+
+
+class NoHooksLU(LogicalUnit): # pylint: disable=W0223
+  """Simple LU which runs no hooks.
+
+  This LU is intended as a parent for other LogicalUnits which will
+  run no hooks, in order to reduce duplicate code.
+
+  """
+  HPATH = None
+  HTYPE = None
+
+  def BuildHooksEnv(self):
+    """Empty BuildHooksEnv for NoHooksLu.
+
+    This just raises an error.
+
+    """
+    raise AssertionError("BuildHooksEnv called for NoHooksLUs")
+
+  def BuildHooksNodes(self):
+    """Empty BuildHooksNodes for NoHooksLU.
+
+    """
+    raise AssertionError("BuildHooksNodes called for NoHooksLU")
+
+
+class Tasklet:
+  """Tasklet base class.
+
+  Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
+  they can mix legacy code with tasklets. Locking needs to be done in the LU,
+  tasklets know nothing about locks.
+
+  Subclasses must follow these rules:
+    - Implement CheckPrereq
+    - Implement Exec
+
+  """
+  def __init__(self, lu):
+    self.lu = lu
+
+    # Shortcuts
+    self.cfg = lu.cfg
+    self.rpc = lu.rpc
+
+  def CheckPrereq(self):
+    """Check prerequisites for this tasklets.
+
+    This method should check whether the prerequisites for the execution of
+    this tasklet are fulfilled. It can do internode communication, but it
+    should be idempotent - no cluster or system changes are allowed.
+
+    The method should raise errors.OpPrereqError in case something is not
+    fulfilled. Its return value is ignored.
+
+    This method should also update all parameters to their canonical form if it
+    hasn't been done before.
+
+    """
+    pass
+
+  def Exec(self, feedback_fn):
+    """Execute the tasklet.
+
+    This method should implement the actual work. It should raise
+    errors.OpExecError for failures that are somewhat dealt with in code, or
+    expected.
+
+    """
+    raise NotImplementedError
+
+
+class _QueryBase:
+  """Base for query utility classes.
+
+  """
+  #: Attribute holding field definitions
+  FIELDS = None
+
+  #: Field to sort by
+  SORT_FIELD = "name"
+
+  def __init__(self, qfilter, fields, use_locking):
+    """Initializes this class.
+
+    """
+    self.use_locking = use_locking
+
+    self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
+                             namefield=self.SORT_FIELD)
+    self.requested_data = self.query.RequestedData()
+    self.names = self.query.RequestedNames()
+
+    # Sort only if no names were requested
+    self.sort_by_name = not self.names
+
+    self.do_locking = None
+    self.wanted = None
+
+  def _GetNames(self, lu, all_names, lock_level):
+    """Helper function to determine names asked for in the query.
+
+    """
+    if self.do_locking:
+      names = lu.owned_locks(lock_level)
+    else:
+      names = all_names
+
+    if self.wanted == locking.ALL_SET:
+      assert not self.names
+      # caller didn't specify names, so ordering is not important
+      return utils.NiceSort(names)
+
+    # caller specified names and we must keep the same order
+    assert self.names
+    assert not self.do_locking or lu.glm.is_owned(lock_level)
+
+    missing = set(self.wanted).difference(names)
+    if missing:
+      raise errors.OpExecError("Some items were removed before retrieving"
+                               " their data: %s" % missing)
+
+    # Return expanded names
+    return self.wanted
+
+  def ExpandNames(self, lu):
+    """Expand names for this query.
+
+    See L{LogicalUnit.ExpandNames}.
+
+    """
+    raise NotImplementedError()
+
+  def DeclareLocks(self, lu, level):
+    """Declare locks for this query.
+
+    See L{LogicalUnit.DeclareLocks}.
+
+    """
+    raise NotImplementedError()
+
+  def _GetQueryData(self, lu):
+    """Collects all data for this query.
+
+    @return: Query data object
+
+    """
+    raise NotImplementedError()
+
+  def NewStyleQuery(self, lu):
+    """Collect data and execute query.
+
+    """
+    return query.GetQueryResponse(self.query, self._GetQueryData(lu),
+                                  sort_by_name=self.sort_by_name)
+
+  def OldStyleQuery(self, lu):
+    """Collect data and execute query.
+
+    """
+    return self.query.OldStyleQuery(self._GetQueryData(lu),
+                                    sort_by_name=self.sort_by_name)
diff --git a/lib/cmdlib/common.py b/lib/cmdlib/common.py
new file mode 100644
index 0000000..b6f5210
--- /dev/null
+++ b/lib/cmdlib/common.py
@@ -0,0 +1,51 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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.
+
+
+"""Common functions used by multiple logical units."""
+
+from ganeti import errors
+
+
+def _ExpandInstanceName(cfg, name):
+  """Wrapper over L{_ExpandItemName} for instance."""
+  return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
+
+
+def _ExpandItemName(fn, name, kind):
+  """Expand an item name.
+
+  @param fn: the function to use for expansion
+  @param name: requested item name
+  @param kind: text description ('Node' or 'Instance')
+  @return: the resolved (full) name
+  @raise errors.OpPrereqError: if the item is not found
+
+  """
+  full_name = fn(name)
+  if full_name is None:
+    raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
+                               errors.ECODE_NOENT)
+  return full_name
+
+
+def _ExpandNodeName(cfg, name):
+  """Wrapper over L{_ExpandItemName} for nodes."""
+  return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
-- 
1.8.2.1

Reply via email to