From 3636400fad7f99122f93995c51fde801f78e9df9 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Fri, 18 Jun 2010 17:45:15 +0200
Subject: [PATCH] Introduce a micro type system for opcodes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, we have one structual validation for opcode attributes: the
_OP_REQP, which checks that a given attribute is not 'None', and the
rest of the checks are done at runtime. This means our type system has
two types: None versus Not-None.

We have been hit many times by small, trivial bugs in this area, and
only a huge amount of unittest and/or hand-written checks would ensure
that we cover all possibilities. This patch attempts to redress the
needs for manual checks by introducing a micro-type system for the
validation of the opcode attributes. What we lose, from the start, are
the custom error messages (e.g. "Invalid reboot mode, choose one of …",
or "The disk index must be a positive integer"). What we gain is the
ability to express easily things as:

- this parameter must be None or an int
- this parameter must be a non-empty list
- this parameter must be either none or a list of dictionaries with keys
  from the list of valid hypervisors and the values dictionaries with
  keys strings and values either None or strings; furthermore, the list
  must be non-empty

These examples show that we have a composable (as opposed to just a few
static types) system, and that we can nest it a few times (just for
sanity; we could nest it up to stack depth).

We also gain lots of ))))))), which is not that nice :)

The current patch moves the existing _OP_REQP to the new framework, but
if accepted, a lot more validations should move to it. In the end, we
definitely should declare a type for all the opcode parameters
(eventually moving _OP_REQP directly to opcodes.py and validating in the
load/init case, and build __slots__ from it).

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>
---
 lib/cmdlib.py    | 485 ++++++++++++++++++++++++++++++-----------------
 lib/constants.py |  20 ++
 2 files changed, 327 insertions(+), 178 deletions(-)

diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index f61051531..b0a1a5f4a 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -53,7 +53,8 @@ from ganeti import masterd
 import ganeti.masterd.instance # pylint: disable-msg=W0611
 
 
-# need to define these here before the actual LUs
+# Modifiable default values; need to define these here before the
+# actual LUs
 
 def _EmptyList():
   """Returns an empty list.
@@ -69,6 +70,125 @@ def _EmptyDict():
   return {}
 
 
+# Some basic types
+def _TNotNone(val):
+  """Checks if the given value is not None.
+
+  """
+  return val is not None
+
+
+def _TNone(val):
+  """Checks if the given value is None.
+
+  """
+  return val is None
+
+
+def _TBool(val):
+  """Checks if the given value is a boolean.
+
+  """
+  return isinstance(val, bool)
+
+
+def _TInt(val):
+  """Checks if the given value is an integer.
+
+  """
+  return isinstance(val, int)
+
+
+def _TFloat(val):
+  """Checks if the given value is a float.
+
+  """
+  return isinstance(val, float)
+
+
+def _TString(val):
+  """Checks if the given value is a string.
+
+  """
+  return isinstance(val, basestring)
+
+
+def _TTrue(val):
+  """Checks if a given value evaluates to a boolean True value.
+
+  """
+  return bool(val)
+
+
+def _TElemOf(target_list):
+  """Builds a function that checks if a given value is a member of a list.
+
+  """
+  return lambda val: val in target_list
+
+
+# Container types
+def _TList(val):
+  """Checks if the given value is a list.
+
+  """
+  return isinstance(val, list)
+
+
+def _TDict(val):
+  """Checks if the given value is a dictionary.
+
+  """
+  return isinstance(val, dict)
+
+
+# Combinator types
+def _TAnd(*args):
+  """Combine multiple functions using an AND operation.
+
+  """
+  def fn(val):
+    return compat.all(t(val) for t in args)
+  return fn
+
+
+def _TOr(*args):
+  """Combine multiple functions using an AND operation.
+
+  """
+  def fn(val):
+    return compat.any(t(val) for t in args)
+  return fn
+
+
+# Type aliases
+
+# non-empty string
+_TNEString = _TAnd(_TString, _TTrue)
+
+
+# positive integer
+_TPInt = _TAnd(_TInt, lambda v: v >= 0)
+
+
+def _TListOf(my_type):
+  """Checks if a given value is a list with all elements of the same type.
+
+  """
+  return _TAnd(_TList,
+               lambda lst: compat.all(lst, my_type))
+
+
+def _TDictOf(key_type, val_type):
+  """Checks a dict type for the type of its key/values.
+
+  """
+  return _TAnd(_TDict,
+               lambda my_dict: (compat.all(my_dict.keys(), key_type) and
+                                compat.all(my_dict.values(), val_type)))
+
+
+# End types
 class LogicalUnit(object):
   """Logical Unit base class.
 
@@ -138,11 +258,18 @@ class LogicalUnit(object):
           dval = aval
         setattr(self.op, aname, dval)
 
-    for attr_name in self._OP_REQP:
-      attr_val = getattr(op, attr_name, None)
-      if attr_val is None:
+    for attr_name, test in self._OP_REQP:
+      if not hasattr(op, attr_name):
         raise errors.OpPrereqError("Required parameter '%s' missing" %
                                    attr_name, errors.ECODE_INVAL)
+      attr_val = getattr(op, attr_name, None)
+      if not callable(test):
+        raise errors.ProgrammerError("Validation for parameter '%s' failed,"
+                                     " given type is not a proper type (%s)" %
+                                     (attr_name, test))
+      if not test(attr_val):
+        raise errors.OpPrereqError("Parameter '%s' has invalid type" %
+                                   attr_name, errors.ECODE_INVAL)
 
     self.CheckArguments()
 
@@ -466,10 +593,6 @@ def _GetWantedNodes(lu, nodes):
   @raise errors.ProgrammerError: if the nodes parameter is wrong type
 
   """
-  if not isinstance(nodes, list):
-    raise errors.OpPrereqError("Invalid argument type 'nodes'",
-                               errors.ECODE_INVAL)
-
   if not nodes:
     raise errors.ProgrammerError("_GetWantedNodes should only be called with a"
       " non-empty list of nodes whose name is to be expanded.")
@@ -491,10 +614,6 @@ def _GetWantedInstances(lu, instances):
   @raise errors.OpPrereqError: if any of the passed instances is not found
 
   """
-  if not isinstance(instances, list):
-    raise errors.OpPrereqError("Invalid argument type 'instances'",
-                               errors.ECODE_INVAL)
-
   if instances:
     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
   else:
@@ -659,6 +778,7 @@ def _CheckStorageType(storage_type):
                                errors.ECODE_INVAL)
   if storage_type == constants.ST_FILE:
     _RequireFileStorage()
+  return True
 
 
 def _GetClusterDomainSecret():
@@ -685,18 +805,6 @@ def _CheckInstanceDown(lu, instance, reason):
                                (instance.name, reason), errors.ECODE_STATE)
 
 
-def _CheckExportMode(mode):
-  """Ensures that a given export mode is valid.
-
-  @param mode: the export mode to check
-  @raise errors.OpPrereqError: when the export mode is not valid
-
-  """
-  if mode not in constants.EXPORT_MODES:
-    raise errors.OpPrereqError("Invalid export mode %r" % mode,
-                               errors.ECODE_INVAL)
-
-
 def _ExpandItemName(fn, name, kind):
   """Expand an item name.
 
@@ -1126,7 +1234,12 @@ class LUVerifyCluster(LogicalUnit):
   """
   HPATH = "cluster-verify"
   HTYPE = constants.HTYPE_CLUSTER
-  _OP_REQP = ["skip_checks", "verbose", "error_codes", "debug_simulate_errors"]
+  _OP_REQP = [
+    ("skip_checks", _TListOf(_TElemOf(constants.VERIFY_OPTIONAL_CHECKS))),
+    ("verbose", _TBool),
+    ("error_codes", _TBool),
+    ("debug_simulate_errors", _TBool),
+    ]
   REQ_BGL = False
 
   TCLUSTER = "cluster"
@@ -1800,18 +1913,6 @@ class LUVerifyCluster(LogicalUnit):
           _ErrorIf(True, self.ENODERPC, node,
                    "node returned invalid LVM info, check LVM status")
 
-  def CheckArguments(self):
-    """Check arguments.
-
-    Transform the list of checks we're going to skip into a set and check that
-    all its members are valid.
-
-    """
-    self.skip_set = frozenset(self.op.skip_checks)
-    if not constants.VERIFY_OPTIONAL_CHECKS.issuperset(self.skip_set):
-      raise errors.OpPrereqError("Invalid checks to be skipped specified",
-                                 errors.ECODE_INVAL)
-
   def BuildHooksEnv(self):
     """Build hooks env.
 
@@ -2044,7 +2145,7 @@ class LUVerifyCluster(LogicalUnit):
     feedback_fn("* Verifying orphan instances")
     self._VerifyOrphanInstances(instancelist, node_image)
 
-    if constants.VERIFY_NPLUSONE_MEM not in self.skip_set:
+    if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
       feedback_fn("* Verifying N+1 Memory redundancy")
       self._VerifyNPlusOneMemory(node_image, instanceinfo)
 
@@ -2191,14 +2292,9 @@ class LURepairDiskSizes(NoHooksLU):
   """Verifies the cluster disks sizes.
 
   """
-  _OP_REQP = ["instances"]
+  _OP_REQP = [("instances", _TListOf(_TNEString))]
   REQ_BGL = False
 
-  def CheckArguments(self):
-    if not isinstance(self.op.instances, list):
-      raise errors.OpPrereqError("Invalid argument type 'instances'",
-                                 errors.ECODE_INVAL)
-
   def ExpandNames(self):
     if self.op.instances:
       self.wanted_names = []
@@ -2314,7 +2410,7 @@ class LURenameCluster(LogicalUnit):
   """
   HPATH = "cluster-rename"
   HTYPE = constants.HTYPE_CLUSTER
-  _OP_REQP = ["name"]
+  _OP_REQP = [("name", _TNEString)]
 
   def BuildHooksEnv(self):
     """Build hooks env.
@@ -2414,12 +2510,20 @@ class LUSetClusterParams(LogicalUnit):
   """
   HPATH = "cluster-modify"
   HTYPE = constants.HTYPE_CLUSTER
-  _OP_REQP = []
+  _OP_REQP = [
+    ("hvparams", _TOr(_TDictOf(_TNEString, _TDict), _TNone)),
+    ("os_hvp", _TOr(_TDictOf(_TNEString, _TDict), _TNone)),
+    ("osparams", _TOr(_TDictOf(_TNEString, _TDict), _TNone)),
+    ("enabled_hypervisors",
+     _TOr(_TAnd(_TListOf(_TElemOf(constants.HYPER_TYPES)), _TTrue), _TNone)),
+    ]
   _OP_DEFS = [
     ("candidate_pool_size", None),
     ("uid_pool", None),
     ("add_uids", None),
     ("remove_uids", None),
+    ("hvparams", None),
+    ("ov_hvp", None),
     ]
   REQ_BGL = False
 
@@ -2539,9 +2643,6 @@ class LUSetClusterParams(LogicalUnit):
     # hypervisor list/parameters
     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
     if self.op.hvparams:
-      if not isinstance(self.op.hvparams, dict):
-        raise errors.OpPrereqError("Invalid 'hvparams' parameter on input",
-                                   errors.ECODE_INVAL)
       for hv_name, hv_dict in self.op.hvparams.items():
         if hv_name not in self.new_hvparams:
           self.new_hvparams[hv_name] = hv_dict
@@ -2551,13 +2652,7 @@ class LUSetClusterParams(LogicalUnit):
     # os hypervisor parameters
     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
     if self.op.os_hvp:
-      if not isinstance(self.op.os_hvp, dict):
-        raise errors.OpPrereqError("Invalid 'os_hvp' parameter on input",
-                                   errors.ECODE_INVAL)
       for os_name, hvs in self.op.os_hvp.items():
-        if not isinstance(hvs, dict):
-          raise errors.OpPrereqError(("Invalid 'os_hvp' parameter on"
-                                      " input"), errors.ECODE_INVAL)
         if os_name not in self.new_os_hvp:
           self.new_os_hvp[os_name] = hvs
         else:
@@ -2570,13 +2665,7 @@ class LUSetClusterParams(LogicalUnit):
     # os parameters
     self.new_osp = objects.FillDict(cluster.osparams, {})
     if self.op.osparams:
-      if not isinstance(self.op.osparams, dict):
-        raise errors.OpPrereqError("Invalid 'osparams' parameter on input",
-                                   errors.ECODE_INVAL)
       for os_name, osp in self.op.osparams.items():
-        if not isinstance(osp, dict):
-          raise errors.OpPrereqError(("Invalid 'osparams' parameter on"
-                                      " input"), errors.ECODE_INVAL)
         if os_name not in self.new_osp:
           self.new_osp[os_name] = {}
 
@@ -2594,16 +2683,6 @@ class LUSetClusterParams(LogicalUnit):
     # changes to the hypervisor list
     if self.op.enabled_hypervisors is not None:
       self.hv_list = self.op.enabled_hypervisors
-      if not self.hv_list:
-        raise errors.OpPrereqError("Enabled hypervisors list must contain at"
-                                   " least one member",
-                                   errors.ECODE_INVAL)
-      invalid_hvs = set(self.hv_list) - constants.HYPER_TYPES
-      if invalid_hvs:
-        raise errors.OpPrereqError("Enabled hypervisors contains invalid"
-                                   " entries: %s" %
-                                   utils.CommaJoin(invalid_hvs),
-                                   errors.ECODE_INVAL)
       for hv in self.hv_list:
         # if the hypervisor doesn't already exist in the cluster
         # hvparams, we initialize it to empty, and then (in both
@@ -2871,7 +2950,10 @@ class LUDiagnoseOS(NoHooksLU):
   """Logical unit for OS diagnose/query.
 
   """
-  _OP_REQP = ["output_fields", "names"]
+  _OP_REQP = [
+    ("output_fields", _TListOf(_TNEString)),
+    ("names", _TListOf(_TNEString)),
+    ]
   REQ_BGL = False
   _FIELDS_STATIC = utils.FieldSet()
   _FIELDS_DYNAMIC = utils.FieldSet("name", "valid", "node_status", "variants",
@@ -2992,7 +3074,7 @@ class LURemoveNode(LogicalUnit):
   """
   HPATH = "node-remove"
   HTYPE = constants.HTYPE_NODE
-  _OP_REQP = ["node_name"]
+  _OP_REQP = [("node_name", _TNEString)]
 
   def BuildHooksEnv(self):
     """Build hooks env.
@@ -3085,7 +3167,11 @@ class LUQueryNodes(NoHooksLU):
 
   """
   # pylint: disable-msg=W0142
-  _OP_REQP = ["output_fields", "names", "use_locking"]
+  _OP_REQP = [
+    ("output_fields", _TListOf(_TNEString)),
+    ("names", _TListOf(_TNEString)),
+    ("use_locking", _TBool),
+    ]
   REQ_BGL = False
 
   _SIMPLE_FIELDS = ["name", "serial_no", "ctime", "mtime", "uuid",
@@ -3238,7 +3324,10 @@ class LUQueryNodeVolumes(NoHooksLU):
   """Logical unit for getting volumes on node(s).
 
   """
-  _OP_REQP = ["nodes", "output_fields"]
+  _OP_REQP = [
+    ("nodes", _TListOf(_TNEString)),
+    ("output_fields", _TListOf(_TNEString)),
+    ]
   REQ_BGL = False
   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
   _FIELDS_STATIC = utils.FieldSet("node")
@@ -3317,14 +3406,16 @@ class LUQueryNodeStorage(NoHooksLU):
   """Logical unit for getting information on storage units on node(s).
 
   """
-  _OP_REQP = ["nodes", "storage_type", "output_fields"]
+  _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
+  _OP_REQP = [
+    ("nodes", _TListOf(_TNEString)),
+    ("storage_type", _CheckStorageType),
+    ("output_fields", _TListOf(_TNEString)),
+    ]
   _OP_DEFS = [("name", None)]
   REQ_BGL = False
-  _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
 
   def CheckArguments(self):
-    _CheckStorageType(self.op.storage_type)
-
     _CheckOutputFields(static=self._FIELDS_STATIC,
                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
                        selected=self.op.output_fields)
@@ -3404,14 +3495,17 @@ class LUModifyNodeStorage(NoHooksLU):
   """Logical unit for modifying a storage volume on a node.
 
   """
-  _OP_REQP = ["node_name", "storage_type", "name", "changes"]
+  _OP_REQP = [
+    ("node_name", _TNEString),
+    ("storage_type", _CheckStorageType),
+    ("name", _TNEString),
+    ("changes", _TDict),
+    ]
   REQ_BGL = False
 
   def CheckArguments(self):
     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
 
-    _CheckStorageType(self.op.storage_type)
-
     storage_type = self.op.storage_type
 
     try:
@@ -3451,7 +3545,9 @@ class LUAddNode(LogicalUnit):
   """
   HPATH = "node-add"
   HTYPE = constants.HTYPE_NODE
-  _OP_REQP = ["node_name"]
+  _OP_REQP = [
+    ("node_name", _TNEString),
+    ]
   _OP_DEFS = [("secondary_ip", None)]
 
   def CheckArguments(self):
@@ -3682,7 +3778,7 @@ class LUSetNodeParams(LogicalUnit):
   """
   HPATH = "node-modify"
   HTYPE = constants.HTYPE_NODE
-  _OP_REQP = ["node_name"]
+  _OP_REQP = [("node_name", _TNEString)]
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -3843,7 +3939,10 @@ class LUPowercycleNode(NoHooksLU):
   """Powercycles a node.
 
   """
-  _OP_REQP = ["node_name", "force"]
+  _OP_REQP = [
+    ("node_name", _TNEString),
+    ("force", _TBool),
+    ]
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -3970,7 +4069,7 @@ class LUActivateInstanceDisks(NoHooksLU):
   """Bring up an instance's disks.
 
   """
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   _OP_DEFS = [("ignore_size", False)]
   REQ_BGL = False
 
@@ -4114,7 +4213,7 @@ class LUDeactivateInstanceDisks(NoHooksLU):
   """Shutdown an instance's disks.
 
   """
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -4277,7 +4376,12 @@ class LUStartupInstance(LogicalUnit):
   """
   HPATH = "instance-start"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "force"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("force", _TBool),
+    ("beparams", _TDict),
+    ("hvparams", _TDict),
+    ]
   _OP_DEFS = [
     ("beparams", _EmptyDict),
     ("hvparams", _EmptyDict),
@@ -4287,19 +4391,9 @@ class LUStartupInstance(LogicalUnit):
   def CheckArguments(self):
     # extra beparams
     if self.op.beparams:
-      if not isinstance(self.op.beparams, dict):
-        raise errors.OpPrereqError("Invalid beparams passed: %s, expected"
-                                   " dict" % (type(self.op.beparams), ),
-                                   errors.ECODE_INVAL)
       # fill the beparams dict
       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
 
-    if self.op.hvparams:
-      if not isinstance(self.op.hvparams, dict):
-        raise errors.OpPrereqError("Invalid hvparams passed: %s, expected"
-                                   " dict" % (type(self.op.hvparams), ),
-                                   errors.ECODE_INVAL)
-
   def ExpandNames(self):
     self._ExpandAndLockInstance()
 
@@ -4380,17 +4474,14 @@ class LURebootInstance(LogicalUnit):
   """
   HPATH = "instance-reboot"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "ignore_secondaries", "reboot_type"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("ignore_secondaries", _TBool),
+    ("reboot_type", _TElemOf(constants.REBOOT_TYPES)),
+    ]
   _OP_DEFS = [("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT)]
   REQ_BGL = False
 
-  def CheckArguments(self):
-    if self.op.reboot_type not in constants.REBOOT_TYPES:
-      raise errors.OpPrereqError("Invalid reboot type '%s', not one of %s" %
-                                  (self.op.reboot_type,
-                                   utils.CommaJoin(constants.REBOOT_TYPES)),
-                                 errors.ECODE_INVAL)
-
   def ExpandNames(self):
     self._ExpandAndLockInstance()
 
@@ -4464,7 +4555,7 @@ class LUShutdownInstance(LogicalUnit):
   """
   HPATH = "instance-stop"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   _OP_DEFS = [("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT)]
   REQ_BGL = False
 
@@ -4515,7 +4606,7 @@ class LUReinstallInstance(LogicalUnit):
   """
   HPATH = "instance-reinstall"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   _OP_DEFS = [
     ("os_type", None),
     ("force_variant", False),
@@ -4588,21 +4679,12 @@ class LURecreateInstanceDisks(LogicalUnit):
   """
   HPATH = "instance-recreate-disks"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "disks"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("disks", _TListOf(_TPInt)),
+    ]
   REQ_BGL = False
 
-  def CheckArguments(self):
-    """Check the arguments.
-
-    """
-    if not isinstance(self.op.disks, list):
-      raise errors.OpPrereqError("Invalid disks parameter", errors.ECODE_INVAL)
-    for item in self.op.disks:
-      if (not isinstance(item, int) or
-          item < 0):
-        raise errors.OpPrereqError("Invalid disk specification '%s'" %
-                                   str(item), errors.ECODE_INVAL)
-
   def ExpandNames(self):
     self._ExpandAndLockInstance()
 
@@ -4661,7 +4743,10 @@ class LURenameInstance(LogicalUnit):
   """
   HPATH = "instance-rename"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "new_name"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("new_name", _TNEString),
+    ]
   _OP_DEFS = [("ignore_ip", False)]
 
   def BuildHooksEnv(self):
@@ -4752,7 +4837,10 @@ class LURemoveInstance(LogicalUnit):
   """
   HPATH = "instance-remove"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "ignore_failures"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("ignore_failures", _TBool),
+    ]
   _OP_DEFS = [("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT)]
   REQ_BGL = False
 
@@ -4836,7 +4924,11 @@ class LUQueryInstances(NoHooksLU):
 
   """
   # pylint: disable-msg=W0142
-  _OP_REQP = ["output_fields", "names", "use_locking"]
+  _OP_REQP = [
+    ("output_fields", _TListOf(_TNEString)),
+    ("names", _TListOf(_TNEString)),
+    ("use_locking", _TBool),
+    ]
   REQ_BGL = False
   _SIMPLE_FIELDS = ["name", "os", "network_port", "hypervisor",
                     "serial_no", "ctime", "mtime", "uuid"]
@@ -5114,7 +5206,10 @@ class LUFailoverInstance(LogicalUnit):
   """
   HPATH = "instance-failover"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "ignore_consistency"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("ignore_consistency", _TBool),
+    ]
   _OP_DEFS = [("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT)]
   REQ_BGL = False
 
@@ -5265,7 +5360,11 @@ class LUMigrateInstance(LogicalUnit):
   """
   HPATH = "instance-migrate"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "live", "cleanup"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("live", _TBool),
+    ("cleanup", _TBool),
+    ]
 
   REQ_BGL = False
 
@@ -5313,7 +5412,10 @@ class LUMoveInstance(LogicalUnit):
   """
   HPATH = "instance-move"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "target_node"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("target_node", _TNEString),
+    ]
   _OP_DEFS = [("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT)]
   REQ_BGL = False
 
@@ -5489,7 +5591,10 @@ class LUMigrateNode(LogicalUnit):
   """
   HPATH = "node-migrate"
   HTYPE = constants.HTYPE_NODE
-  _OP_REQP = ["node_name", "live"]
+  _OP_REQP = [
+    ("node_name", _TNEString),
+    ("live", _TBool),
+    ]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -6223,10 +6328,18 @@ class LUCreateInstance(LogicalUnit):
   """
   HPATH = "instance-add"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "disks",
-              "mode", "start",
-              "wait_for_sync", "ip_check", "nics",
-              "hvparams", "beparams", "osparams"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("mode", _TElemOf(constants.INSTANCE_CREATE_MODES)),
+    ("start", _TBool),
+    ("wait_for_sync", _TBool),
+    ("ip_check", _TBool),
+    ("disks", _TListOf(_TDict)),
+    ("nics", _TListOf(_TDict)),
+    ("hvparams", _TDict),
+    ("beparams", _TDict),
+    ("osparams", _TDict),
+    ]
   _OP_DEFS = [
     ("name_check", True),
     ("no_install", False),
@@ -6291,11 +6404,6 @@ class LUCreateInstance(LogicalUnit):
 
     self.adopt_disks = has_adopt
 
-    # verify creation mode
-    if self.op.mode not in constants.INSTANCE_CREATE_MODES:
-      raise errors.OpPrereqError("Invalid instance creation mode '%s'" %
-                                 self.op.mode, errors.ECODE_INVAL)
-
     # instance name verification
     if self.op.name_check:
       self.hostname1 = utils.GetHostInfo(self.op.instance_name)
@@ -7128,7 +7236,7 @@ class LUConnectConsole(NoHooksLU):
   console.
 
   """
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -7179,7 +7287,11 @@ class LUReplaceDisks(LogicalUnit):
   """
   HPATH = "mirrors-replace"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "mode", "disks"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("mode", _TElemOf(constants.REPLACE_MODES)),
+    ("disks", _TListOf(_TPInt)),
+    ]
   _OP_DEFS = [
     ("remote_node", None),
     ("iallocator", None),
@@ -7920,7 +8032,7 @@ class LURepairNodeStorage(NoHooksLU):
   """Repairs the volume group on a node.
 
   """
-  _OP_REQP = ["node_name"]
+  _OP_REQP = [("node_name", _TNEString)]
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -7985,7 +8097,7 @@ class LUNodeEvacuationStrategy(NoHooksLU):
   """Computes the node evacuation strategy.
 
   """
-  _OP_REQP = ["nodes"]
+  _OP_REQP = [("nodes", _TListOf(_TNEString))]
   _OP_DEFS = [
     ("remote_node", None),
     ("iallocator", None),
@@ -8038,7 +8150,12 @@ class LUGrowDisk(LogicalUnit):
   """
   HPATH = "disk-grow"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "disk", "amount", "wait_for_sync"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("disk", _TInt),
+    ("amount", _TInt),
+    ("wait_for_sync", _TBool),
+    ]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -8132,14 +8249,12 @@ class LUQueryInstanceData(NoHooksLU):
   """Query runtime instance data.
 
   """
-  _OP_REQP = ["instances", "static"]
+  _OP_REQP = [
+    ("instances", _TListOf(_TNEString)),
+    ("static", _TBool),
+    ]
   REQ_BGL = False
 
-  def CheckArguments(self):
-    if not isinstance(self.op.instances, list):
-      raise errors.OpPrereqError("Invalid argument type 'instances'",
-                                 errors.ECODE_INVAL)
-
   def ExpandNames(self):
     self.needed_locks = {}
     self.share_locks = dict.fromkeys(locking.LEVELS, 1)
@@ -8294,7 +8409,7 @@ class LUSetInstanceParams(LogicalUnit):
   """
   HPATH = "instance-modify"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   _OP_DEFS = [
     ("nics", _EmptyList),
     ("disks", _EmptyList),
@@ -8956,7 +9071,7 @@ class LUQueryExports(NoHooksLU):
   """Query the exports list
 
   """
-  _OP_REQP = ['nodes']
+  _OP_REQP = [("nodes", _TListOf(_TNEString))]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -8993,15 +9108,12 @@ class LUPrepareExport(NoHooksLU):
   """Prepares an instance for an export and returns useful information.
 
   """
-  _OP_REQP = ["instance_name", "mode"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("mode", _TElemOf(constants.EXPORT_MODES)),
+    ]
   REQ_BGL = False
 
-  def CheckArguments(self):
-    """Check the arguments.
-
-    """
-    _CheckExportMode(self.op.mode)
-
   def ExpandNames(self):
     self._ExpandAndLockInstance()
 
@@ -9053,7 +9165,12 @@ class LUExportInstance(LogicalUnit):
   """
   HPATH = "instance-export"
   HTYPE = constants.HTYPE_INSTANCE
-  _OP_REQP = ["instance_name", "target_node", "shutdown"]
+  _OP_REQP = [
+    ("instance_name", _TNEString),
+    ("target_node", _TNEString),
+    ("shutdown", _TBool),
+    ("mode", _TElemOf(constants.EXPORT_MODES)),
+    ]
   _OP_DEFS = [
     ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT),
     ("remove_instance", False),
@@ -9075,8 +9192,6 @@ class LUExportInstance(LogicalUnit):
       raise errors.OpPrereqError("Can not remove instance without shutting it"
                                  " down before")
 
-    _CheckExportMode(self.op.mode)
-
     if self.op.mode == constants.EXPORT_MODE_REMOTE:
       if not self.x509_key_name:
         raise errors.OpPrereqError("Missing X509 key name for encryption",
@@ -9339,7 +9454,7 @@ class LURemoveExport(NoHooksLU):
   """Remove exports related to the named instance.
 
   """
-  _OP_REQP = ["instance_name"]
+  _OP_REQP = [("instance_name", _TNEString)]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -9418,7 +9533,10 @@ class LUGetTags(TagsLU):
   """Returns the tags of a given object.
 
   """
-  _OP_REQP = ["kind", "name"]
+  _OP_REQP = [
+    ("kind", _TElemOf(constants.VALID_TAG_TYPES)),
+    ("name", _TNEString),
+    ]
   REQ_BGL = False
 
   def Exec(self, feedback_fn):
@@ -9432,7 +9550,7 @@ class LUSearchTags(NoHooksLU):
   """Searches the tags for a given pattern.
 
   """
-  _OP_REQP = ["pattern"]
+  _OP_REQP = [("pattern", _TNEString)]
   REQ_BGL = False
 
   def ExpandNames(self):
@@ -9472,7 +9590,11 @@ class LUAddTags(TagsLU):
   """Sets a tag on a given object.
 
   """
-  _OP_REQP = ["kind", "name", "tags"]
+  _OP_REQP = [
+    ("kind", _TElemOf(constants.VALID_TAG_TYPES)),
+    ("name", _TNEString),
+    ("tags", _TListOf(objects.TaggableObject.ValidateTag)),
+    ]
   REQ_BGL = False
 
   def CheckPrereq(self):
@@ -9501,7 +9623,11 @@ class LUDelTags(TagsLU):
   """Delete a list of tags from a given object.
 
   """
-  _OP_REQP = ["kind", "name", "tags"]
+  _OP_REQP = [
+    ("kind", _TElemOf(constants.VALID_TAG_TYPES)),
+    ("name", _TNEString),
+    ("tags", _TListOf(objects.TaggableObject.ValidateTag)),
+    ]
   REQ_BGL = False
 
   def CheckPrereq(self):
@@ -9538,7 +9664,11 @@ class LUTestDelay(NoHooksLU):
   time.
 
   """
-  _OP_REQP = ["duration", "on_master", "on_nodes"]
+  _OP_REQP = [
+    ("duration", _TFloat),
+    ("on_master", _TBool),
+    ("on_nodes", _TListOf(_TNEString)),
+    ]
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -9918,10 +10048,19 @@ class LUTestAllocator(NoHooksLU):
   This LU runs the allocator tests
 
   """
-  _OP_REQP = ["direction", "mode", "name"]
+  _OP_REQP = [
+    ("direction", _TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)),
+    ("mode", _TElemOf(constants.VALID_IALLOCATOR_MODES)),
+    ("name", _TNEString),
+    ("nics", _TOr(_TNone, _TListOf(
+      _TDictOf(_TElemOf(["mac", "ip", "bridge"]), _TNEString)))),
+    ("disks", _TOr(_TNone, _TList)),
+    ]
   _OP_DEFS = [
     ("hypervisor", None),
     ("allocator", None),
+    ("nics", None),
+    ("disks", None),
     ]
 
   def CheckPrereq(self):
@@ -9931,7 +10070,7 @@ class LUTestAllocator(NoHooksLU):
 
     """
     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
-      for attr in ["name", "mem_size", "disks", "disk_template",
+      for attr in ["mem_size", "disks", "disk_template",
                    "os", "tags", "nics", "vcpus"]:
         if not hasattr(self.op, attr):
           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
@@ -9943,13 +10082,6 @@ class LUTestAllocator(NoHooksLU):
       if not isinstance(self.op.nics, list):
         raise errors.OpPrereqError("Invalid parameter 'nics'",
                                    errors.ECODE_INVAL)
-      for row in self.op.nics:
-        if (not isinstance(row, dict) or
-            "mac" not in row or
-            "ip" not in row or
-            "bridge" not in row):
-          raise errors.OpPrereqError("Invalid contents of the 'nics'"
-                                     " parameter", errors.ECODE_INVAL)
       if not isinstance(self.op.disks, list):
         raise errors.OpPrereqError("Invalid parameter 'disks'",
                                    errors.ECODE_INVAL)
@@ -9964,9 +10096,6 @@ class LUTestAllocator(NoHooksLU):
       if self.op.hypervisor is None:
         self.op.hypervisor = self.cfg.GetHypervisorType()
     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
-      if not hasattr(self.op, "name"):
-        raise errors.OpPrereqError("Missing attribute 'name' on opcode input",
-                                   errors.ECODE_INVAL)
       fname = _ExpandInstanceName(self.cfg, self.op.name)
       self.op.name = fname
       self.relocate_from = self.cfg.GetInstanceInfo(fname).secondary_nodes
diff --git a/lib/constants.py b/lib/constants.py
index 8c04d8bae..1f91ce54d 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -353,6 +353,12 @@ REPLACE_DISK_PRI = "replace_on_primary"    # replace disks on primary
 REPLACE_DISK_SEC = "replace_on_secondary"  # replace disks on secondary
 REPLACE_DISK_CHG = "replace_new_secondary" # change secondary node
 REPLACE_DISK_AUTO = "replace_auto"
+REPLACE_MODES = frozenset([
+  REPLACE_DISK_PRI,
+  REPLACE_DISK_SEC,
+  REPLACE_DISK_CHG,
+  REPLACE_DISK_AUTO,
+  ])
 
 # Instance export mode
 EXPORT_MODE_LOCAL = "local"
@@ -414,6 +420,11 @@ EXIT_CONFIRMATION = 13 # need user confirmation
 TAG_CLUSTER = "cluster"
 TAG_NODE = "node"
 TAG_INSTANCE = "instance"
+VALID_TAG_TYPES = frozenset([
+  TAG_CLUSTER,
+  TAG_NODE,
+  TAG_INSTANCE,
+  ])
 MAX_TAG_LEN = 128
 MAX_TAGS_PER_OBJ = 4096
 
@@ -718,9 +729,18 @@ SSL_CERT_EXPIRATION_ERROR = 7
 IALLOCATOR_VERSION = 2
 IALLOCATOR_DIR_IN = "in"
 IALLOCATOR_DIR_OUT = "out"
+VALID_IALLOCATOR_DIRECTIONS = frozenset([
+  IALLOCATOR_DIR_IN,
+  IALLOCATOR_DIR_OUT,
+  ])
 IALLOCATOR_MODE_ALLOC = "allocate"
 IALLOCATOR_MODE_RELOC = "relocate"
 IALLOCATOR_MODE_MEVAC = "multi-evacuate"
+VALID_IALLOCATOR_MODES = frozenset([
+  IALLOCATOR_MODE_ALLOC,
+  IALLOCATOR_MODE_RELOC,
+  IALLOCATOR_MODE_MEVAC,
+  ])
 IALLOCATOR_SEARCH_PATH = _autoconf.IALLOCATOR_SEARCH_PATH
 
 # Job queue
-- 
GitLab