Locking fixes regarding Issue 324
[ganeti-local] / lib / opcodes.py
index 4c427d5..73d5cb3 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# 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
 #
 # 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
@@ -35,12 +35,13 @@ opcodes.
 
 import logging
 import re
 
 import logging
 import re
+import ipaddr
 
 from ganeti import constants
 from ganeti import errors
 from ganeti import ht
 from ganeti import objects
 
 from ganeti import constants
 from ganeti import errors
 from ganeti import ht
 from ganeti import objects
-from ganeti import objectutils
+from ganeti import outils
 
 
 # Common opcode attributes
 
 
 # Common opcode attributes
@@ -51,7 +52,7 @@ _POutputFields = ("output_fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
 
 #: the shutdown timeout
 _PShutdownTimeout = \
 
 #: the shutdown timeout
 _PShutdownTimeout = \
-  ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt,
+  ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TNonNegativeInt,
    "How long to wait for instance to shut down")
 
 #: the force parameter
    "How long to wait for instance to shut down")
 
 #: the force parameter
@@ -73,7 +74,7 @@ _PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString, "Group name")
 
 #: Migration type (live/non-live)
 _PMigrationMode = ("mode", None,
 
 #: Migration type (live/non-live)
 _PMigrationMode = ("mode", None,
-                   ht.TOr(ht.TNone, ht.TElemOf(constants.HT_MIGRATION_MODES)),
+                   ht.TMaybe(ht.TElemOf(constants.HT_MIGRATION_MODES)),
                    "Migration mode")
 
 #: Obsolete 'live' migration mode (boolean)
                    "Migration mode")
 
 #: Obsolete 'live' migration mode (boolean)
@@ -110,7 +111,7 @@ _PNameCheck = ("name_check", True, ht.TBool, "Whether to check name")
 
 _PNodeGroupAllocPolicy = \
   ("alloc_policy", None,
 
 _PNodeGroupAllocPolicy = \
   ("alloc_policy", None,
-   ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES)),
+   ht.TMaybe(ht.TElemOf(constants.VALID_ALLOC_POLICIES)),
    "Instance allocation policy")
 
 _PGroupNodeParams = ("ndparams", None, ht.TMaybeDict,
    "Instance allocation policy")
 
 _PGroupNodeParams = ("ndparams", None, ht.TMaybeDict,
@@ -149,16 +150,21 @@ _PIgnoreErrors = ("ignore_errors", ht.EmptyList,
                   "List of error codes that should be treated as warnings")
 
 # Disk parameters
                   "List of error codes that should be treated as warnings")
 
 # Disk parameters
-_PDiskParams = ("diskparams", None,
-                ht.TOr(
-                  ht.TDictOf(ht.TElemOf(constants.DISK_TEMPLATES), ht.TDict),
-                  ht.TNone),
-                "Disk templates' parameter defaults")
+_PDiskParams = \
+  ("diskparams", None,
+   ht.TMaybe(ht.TDictOf(ht.TElemOf(constants.DISK_TEMPLATES), ht.TDict)),
+   "Disk templates' parameter defaults")
 
 # Parameters for node resource model
 _PHvState = ("hv_state", None, ht.TMaybeDict, "Set hypervisor states")
 _PDiskState = ("disk_state", None, ht.TMaybeDict, "Set disk states")
 
 
 # Parameters for node resource model
 _PHvState = ("hv_state", None, ht.TMaybeDict, "Set hypervisor states")
 _PDiskState = ("disk_state", None, ht.TMaybeDict, "Set disk states")
 
+#: Opportunistic locking
+_POpportunisticLocking = \
+  ("opportunistic_locking", False, ht.TBool,
+   ("Whether to employ opportunistic locking for nodes, meaning nodes"
+    " already locked by another opcode won't be considered for instance"
+    " allocation (only when an iallocator is used)"))
 
 _PIgnoreIpolicy = ("ignore_ipolicy", False, ht.TBool,
                    "Whether to ignore ipolicy violations")
 
 _PIgnoreIpolicy = ("ignore_ipolicy", False, ht.TBool,
                    "Whether to ignore ipolicy violations")
@@ -167,6 +173,16 @@ _PIgnoreIpolicy = ("ignore_ipolicy", False, ht.TBool,
 _PAllowRuntimeChgs = ("allow_runtime_changes", True, ht.TBool,
                       "Allow runtime changes (eg. memory ballooning)")
 
 _PAllowRuntimeChgs = ("allow_runtime_changes", True, ht.TBool,
                       "Allow runtime changes (eg. memory ballooning)")
 
+#: IAllocator field builder
+_PIAllocFromDesc = lambda desc: ("iallocator", None, ht.TMaybeString, desc)
+
+#: a required network name
+_PNetworkName = ("network_name", ht.NoDefault, ht.TNonEmptyString,
+                 "Set network name")
+
+_PTargetGroups = \
+  ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString),
+   "Destination group names or UUIDs (defaults to \"all but current group\")")
 
 #: OP_ID conversion regular expression
 _OPID_RE = re.compile("([a-z])([A-Z])")
 
 #: OP_ID conversion regular expression
 _OPID_RE = re.compile("([a-z])([A-Z])")
@@ -184,7 +200,7 @@ _TestClusterOsList = ht.TMaybeListOf(_TestClusterOsListItem)
 #: Utility function for testing NIC definitions
 _TestNicDef = \
   ht.Comment("NIC parameters")(ht.TDictOf(ht.TElemOf(constants.INIC_PARAMS),
 #: Utility function for testing NIC definitions
 _TestNicDef = \
   ht.Comment("NIC parameters")(ht.TDictOf(ht.TElemOf(constants.INIC_PARAMS),
-                                          ht.TOr(ht.TNone, ht.TNonEmptyString)))
+                                          ht.TMaybeString))
 
 _TSetParamsResultItemItems = [
   ht.Comment("name of changed parameter")(ht.TNonEmptyString),
 
 _TSetParamsResultItemItems = [
   ht.Comment("name of changed parameter")(ht.TNonEmptyString),
@@ -195,10 +211,12 @@ _TSetParamsResult = \
   ht.TListOf(ht.TAnd(ht.TIsLength(len(_TSetParamsResultItemItems)),
                      ht.TItems(_TSetParamsResultItemItems)))
 
   ht.TListOf(ht.TAnd(ht.TIsLength(len(_TSetParamsResultItemItems)),
                      ht.TItems(_TSetParamsResultItemItems)))
 
-# TODO: Generate check from constants.IDISK_PARAMS_TYPES (however, not all users
-# of this check support all parameters)
+# In the disks option we can provide arbitrary parameters too, which
+# we may not be able to validate at this level, so we just check the
+# format of the dict here and the checks concerning IDISK_PARAMS will
+# happen at the LU level
 _TDiskParams = \
 _TDiskParams = \
-  ht.Comment("Disk parameters")(ht.TDictOf(ht.TElemOf(constants.IDISK_PARAMS),
+  ht.Comment("Disk parameters")(ht.TDictOf(ht.TNonEmptyString,
                                            ht.TOr(ht.TNonEmptyString, ht.TInt)))
 
 _TQueryRow = \
                                            ht.TOr(ht.TNonEmptyString, ht.TInt)))
 
 _TQueryRow = \
@@ -321,7 +339,7 @@ def _BuildDiskTemplateCheck(accept_none):
   template_check = ht.TElemOf(constants.DISK_TEMPLATES)
 
   if accept_none:
   template_check = ht.TElemOf(constants.DISK_TEMPLATES)
 
   if accept_none:
-    template_check = ht.TOr(template_check, ht.TNone)
+    template_check = ht.TMaybe(template_check)
 
   return ht.TAnd(template_check, _CheckFileStorage)
 
 
   return ht.TAnd(template_check, _CheckFileStorage)
 
@@ -334,6 +352,7 @@ def _CheckStorageType(storage_type):
     raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
                                errors.ECODE_INVAL)
   if storage_type == constants.ST_FILE:
     raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
                                errors.ECODE_INVAL)
   if storage_type == constants.ST_FILE:
+    # TODO: What about shared file storage?
     RequireFileStorage()
   return True
 
     RequireFileStorage()
   return True
 
@@ -343,7 +362,62 @@ _PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType,
                  "Storage type")
 
 
                  "Storage type")
 
 
-class _AutoOpParamSlots(objectutils.AutoSlots):
+@ht.WithDesc("IPv4 network")
+def _CheckCIDRNetNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv4Network(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+@ht.WithDesc("IPv4 address")
+def _CheckCIDRAddrNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv4Address(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+@ht.WithDesc("IPv6 address")
+def _CheckCIDR6AddrNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv6Address(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+@ht.WithDesc("IPv6 network")
+def _CheckCIDR6NetNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv6Network(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+_TIpAddress4 = ht.TAnd(ht.TString, _CheckCIDRAddrNotation)
+_TIpAddress6 = ht.TAnd(ht.TString, _CheckCIDR6AddrNotation)
+_TIpNetwork4 = ht.TAnd(ht.TString, _CheckCIDRNetNotation)
+_TIpNetwork6 = ht.TAnd(ht.TString, _CheckCIDR6NetNotation)
+_TMaybeAddr4List = ht.TMaybe(ht.TListOf(_TIpAddress4))
+
+
+class _AutoOpParamSlots(outils.AutoSlots):
   """Meta class for opcode definitions.
 
   """
   """Meta class for opcode definitions.
 
   """
@@ -362,10 +436,14 @@ class _AutoOpParamSlots(objectutils.AutoSlots):
     slots = mcs._GetSlots(attrs)
     assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
       "Class '%s' uses unknown field in OP_DSC_FIELD" % name
     slots = mcs._GetSlots(attrs)
     assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
       "Class '%s' uses unknown field in OP_DSC_FIELD" % name
+    assert ("OP_DSC_FORMATTER" not in attrs or
+            callable(attrs["OP_DSC_FORMATTER"])), \
+      ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
+       (name, type(attrs["OP_DSC_FORMATTER"])))
 
     attrs["OP_ID"] = _NameToId(name)
 
 
     attrs["OP_ID"] = _NameToId(name)
 
-    return objectutils.AutoSlots.__new__(mcs, name, bases, attrs)
+    return outils.AutoSlots.__new__(mcs, name, bases, attrs)
 
   @classmethod
   def _GetSlots(mcs, attrs):
 
   @classmethod
   def _GetSlots(mcs, attrs):
@@ -379,7 +457,7 @@ class _AutoOpParamSlots(objectutils.AutoSlots):
     return [pname for (pname, _, _, _) in params]
 
 
     return [pname for (pname, _, _, _) in params]
 
 
-class BaseOpCode(objectutils.ValidatedSlots):
+class BaseOpCode(outils.ValidatedSlots):
   """A simple serializable object.
 
   This object serves as a parent class for OpCode without any custom
   """A simple serializable object.
 
   This object serves as a parent class for OpCode without any custom
@@ -468,8 +546,9 @@ class BaseOpCode(objectutils.ValidatedSlots):
       if set_defaults or hasattr(self, attr_name):
         attr_val = getattr(self, attr_name)
         if not test(attr_val):
       if set_defaults or hasattr(self, attr_name):
         attr_val = getattr(self, attr_name)
         if not test(attr_val):
-          logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
-                        self.OP_ID, attr_name, type(attr_val), attr_val)
+          logging.error("OpCode %s, parameter %s, has invalid type %s/value"
+                        " '%s' expecting type %s",
+                        self.OP_ID, attr_name, type(attr_val), attr_val, test)
           raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
                                      (self.OP_ID, attr_name),
                                      errors.ECODE_INVAL)
           raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
                                      (self.OP_ID, attr_name),
                                      errors.ECODE_INVAL)
@@ -489,7 +568,8 @@ def _BuildJobDepCheck(relative):
     job_id = ht.TJobId
 
   job_dep = \
     job_id = ht.TJobId
 
   job_dep = \
-    ht.TAnd(ht.TIsLength(2),
+    ht.TAnd(ht.TOr(ht.TList, ht.TTuple),
+            ht.TIsLength(2),
             ht.TItems([job_id,
                        ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
 
             ht.TItems([job_id,
                        ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
 
@@ -524,6 +604,9 @@ class OpCode(BaseOpCode):
   @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
                       string returned by Summary(); see the docstring of that
                       method for details).
   @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
                       string returned by Summary(); see the docstring of that
                       method for details).
+  @cvar OP_DSC_FORMATTER: A callable that should format the OP_DSC_FIELD; if
+                          not present, then the field will be simply converted
+                          to string
   @cvar OP_PARAMS: List of opcode attributes, the default values they should
                    get if not already defined, and types they must match.
   @cvar OP_RESULT: Callable to verify opcode result
   @cvar OP_PARAMS: List of opcode attributes, the default values they should
                    get if not already defined, and types they must match.
   @cvar OP_RESULT: Callable to verify opcode result
@@ -539,7 +622,7 @@ class OpCode(BaseOpCode):
   WITH_LU = True
   OP_PARAMS = [
     ("dry_run", None, ht.TMaybeBool, "Run checks only, don't execute"),
   WITH_LU = True
   OP_PARAMS = [
     ("dry_run", None, ht.TMaybeBool, "Run checks only, don't execute"),
-    ("debug_level", None, ht.TOr(ht.TNone, ht.TPositiveInt), "Debug level"),
+    ("debug_level", None, ht.TMaybe(ht.TNonNegativeInt), "Debug level"),
     ("priority", constants.OP_PRIO_DEFAULT,
      ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID), "Opcode priority"),
     (DEPEND_ATTR, None, _BuildJobDepCheck(True),
     ("priority", constants.OP_PRIO_DEFAULT,
      ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID), "Opcode priority"),
     (DEPEND_ATTR, None, _BuildJobDepCheck(True),
@@ -611,7 +694,10 @@ class OpCode(BaseOpCode):
     field_name = getattr(self, "OP_DSC_FIELD", None)
     if field_name:
       field_value = getattr(self, field_name, None)
     field_name = getattr(self, "OP_DSC_FIELD", None)
     if field_name:
       field_value = getattr(self, field_name, None)
-      if isinstance(field_value, (list, tuple)):
+      field_formatter = getattr(self, "OP_DSC_FORMATTER", None)
+      if callable(field_formatter):
+        field_value = field_formatter(field_value)
+      elif isinstance(field_value, (list, tuple)):
         field_value = ",".join(str(i) for i in field_value)
       txt = "%s(%s)" % (txt, field_value)
     return txt
         field_value = ",".join(str(i) for i in field_value)
       txt = "%s(%s)" % (txt, field_value)
     return txt
@@ -668,7 +754,7 @@ class OpClusterVerify(OpCode):
     _PSkipChecks,
     _PIgnoreErrors,
     _PVerbose,
     _PSkipChecks,
     _PIgnoreErrors,
     _PVerbose,
-    ("group_name", None, ht.TMaybeString, "Group to verify")
+    ("group_name", None, ht.TMaybeString, "Group to verify"),
     ]
   OP_RESULT = TJobIdListOnly
 
     ]
   OP_RESULT = TJobIdListOnly
 
@@ -766,14 +852,14 @@ class OpClusterRepairDiskSizes(OpCode):
     ]
   OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(3),
                                  ht.TItems([ht.TNonEmptyString,
     ]
   OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(3),
                                  ht.TItems([ht.TNonEmptyString,
-                                            ht.TPositiveInt,
-                                            ht.TPositiveInt])))
+                                            ht.TNonNegativeInt,
+                                            ht.TNonNegativeInt])))
 
 
 class OpClusterConfigQuery(OpCode):
   """Query cluster configuration values."""
   OP_PARAMS = [
 
 
 class OpClusterConfigQuery(OpCode):
   """Query cluster configuration values."""
   OP_PARAMS = [
-    _POutputFields
+    _POutputFields,
     ]
   OP_RESULT = ht.TListOf(ht.TAny)
 
     ]
   OP_RESULT = ht.TListOf(ht.TAny)
 
@@ -804,24 +890,23 @@ class OpClusterSetParams(OpCode):
   OP_PARAMS = [
     _PHvState,
     _PDiskState,
   OP_PARAMS = [
     _PHvState,
     _PDiskState,
-    ("vg_name", None, ht.TMaybeString, "Volume group name"),
+    ("vg_name", None, ht.TMaybe(ht.TString), "Volume group name"),
     ("enabled_hypervisors", None,
     ("enabled_hypervisors", None,
-     ht.TOr(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)), ht.TTrue),
-            ht.TNone),
+     ht.TMaybe(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)),
+                       ht.TTrue)),
      "List of enabled hypervisors"),
      "List of enabled hypervisors"),
-    ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
-                              ht.TNone),
+    ("hvparams", None,
+     ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
      "Cluster-wide hypervisor parameter defaults, hypervisor-dependent"),
      "Cluster-wide hypervisor parameter defaults, hypervisor-dependent"),
-    ("beparams", None, ht.TOr(ht.TDict, ht.TNone),
+    ("beparams", None, ht.TMaybeDict,
      "Cluster-wide backend parameter defaults"),
      "Cluster-wide backend parameter defaults"),
-    ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
-                            ht.TNone),
+    ("os_hvp", None, ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
      "Cluster-wide per-OS hypervisor parameter defaults"),
      "Cluster-wide per-OS hypervisor parameter defaults"),
-    ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
-                              ht.TNone),
+    ("osparams", None,
+     ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
      "Cluster-wide OS parameter defaults"),
     _PDiskParams,
      "Cluster-wide OS parameter defaults"),
     _PDiskParams,
-    ("candidate_pool_size", None, ht.TOr(ht.TStrictPositiveInt, ht.TNone),
+    ("candidate_pool_size", None, ht.TMaybe(ht.TPositiveInt),
      "Master candidate pool size"),
     ("uid_pool", None, ht.NoType,
      "Set UID pool, must be list of lists describing UID ranges (two items,"
      "Master candidate pool size"),
     ("uid_pool", None, ht.NoType,
      "Set UID pool, must be list of lists describing UID ranges (two items,"
@@ -840,12 +925,12 @@ class OpClusterSetParams(OpCode):
     ("ndparams", None, ht.TMaybeDict, "Cluster-wide node parameter defaults"),
     ("ipolicy", None, ht.TMaybeDict,
      "Cluster-wide :ref:`instance policy <rapi-ipolicy>` specs"),
     ("ndparams", None, ht.TMaybeDict, "Cluster-wide node parameter defaults"),
     ("ipolicy", None, ht.TMaybeDict,
      "Cluster-wide :ref:`instance policy <rapi-ipolicy>` specs"),
-    ("drbd_helper", None, ht.TOr(ht.TString, ht.TNone), "DRBD helper program"),
-    ("default_iallocator", None, ht.TOr(ht.TString, ht.TNone),
+    ("drbd_helper", None, ht.TMaybe(ht.TString), "DRBD helper program"),
+    ("default_iallocator", None, ht.TMaybe(ht.TString),
      "Default iallocator for cluster"),
      "Default iallocator for cluster"),
-    ("master_netdev", None, ht.TOr(ht.TString, ht.TNone),
+    ("master_netdev", None, ht.TMaybe(ht.TString),
      "Master network device"),
      "Master network device"),
-    ("master_netmask", None, ht.TOr(ht.TInt, ht.TNone),
+    ("master_netmask", None, ht.TMaybe(ht.TNonNegativeInt),
      "Netmask of the master IP"),
     ("reserved_lvs", None, ht.TMaybeListOf(ht.TNonEmptyString),
      "List of reserved LVs"),
      "Netmask of the master IP"),
     ("reserved_lvs", None, ht.TMaybeListOf(ht.TNonEmptyString),
      "List of reserved LVs"),
@@ -898,7 +983,7 @@ class OpQuery(OpCode):
     _PUseLocking,
     ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
      "Requested fields"),
     _PUseLocking,
     ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
      "Requested fields"),
-    ("qfilter", None, ht.TOr(ht.TNone, ht.TList),
+    ("qfilter", None, ht.TMaybe(ht.TList),
      "Query filter"),
     ]
   OP_RESULT = \
      "Query filter"),
     ]
   OP_RESULT = \
@@ -932,19 +1017,41 @@ class OpOobCommand(OpCode):
   OP_PARAMS = [
     ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
      "List of nodes to run the OOB command against"),
   OP_PARAMS = [
     ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
      "List of nodes to run the OOB command against"),
-    ("command", None, ht.TElemOf(constants.OOB_COMMANDS),
+    ("command", ht.NoDefault, ht.TElemOf(constants.OOB_COMMANDS),
      "OOB command to be run"),
     ("timeout", constants.OOB_TIMEOUT, ht.TInt,
      "Timeout before the OOB helper will be terminated"),
     ("ignore_status", False, ht.TBool,
      "Ignores the node offline status for power off"),
      "OOB command to be run"),
     ("timeout", constants.OOB_TIMEOUT, ht.TInt,
      "Timeout before the OOB helper will be terminated"),
     ("ignore_status", False, ht.TBool,
      "Ignores the node offline status for power off"),
-    ("power_delay", constants.OOB_POWER_DELAY, ht.TPositiveFloat,
+    ("power_delay", constants.OOB_POWER_DELAY, ht.TNonNegativeFloat,
      "Time in seconds to wait between powering on nodes"),
     ]
   # Fixme: Make it more specific with all the special cases in LUOobCommand
   OP_RESULT = _TQueryResult
 
 
      "Time in seconds to wait between powering on nodes"),
     ]
   # Fixme: Make it more specific with all the special cases in LUOobCommand
   OP_RESULT = _TQueryResult
 
 
+class OpRestrictedCommand(OpCode):
+  """Runs a restricted command on node(s).
+
+  """
+  OP_PARAMS = [
+    _PUseLocking,
+    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
+     "Nodes on which the command should be run (at least one)"),
+    ("command", ht.NoDefault, ht.TNonEmptyString,
+     "Command name (no parameters)"),
+    ]
+
+  _RESULT_ITEMS = [
+    ht.Comment("success")(ht.TBool),
+    ht.Comment("output or error message")(ht.TString),
+    ]
+
+  OP_RESULT = \
+    ht.TListOf(ht.TAnd(ht.TIsLength(len(_RESULT_ITEMS)),
+                       ht.TItems(_RESULT_ITEMS)))
+
+
 # node opcodes
 
 class OpNodeRemove(OpCode):
 # node opcodes
 
 class OpNodeRemove(OpCode):
@@ -1113,8 +1220,8 @@ class OpNodeMigrate(OpCode):
     _PMigrationTargetNode,
     _PAllowRuntimeChgs,
     _PIgnoreIpolicy,
     _PMigrationTargetNode,
     _PAllowRuntimeChgs,
     _PIgnoreIpolicy,
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator for deciding the target node for shared-storage instances"),
+    _PIAllocFromDesc("Iallocator for deciding the target node"
+                     " for shared-storage instances"),
     ]
   OP_RESULT = TJobIdListOnly
 
     ]
   OP_RESULT = TJobIdListOnly
 
@@ -1126,7 +1233,7 @@ class OpNodeEvacuate(OpCode):
     _PEarlyRelease,
     _PNodeName,
     ("remote_node", None, ht.TMaybeString, "New secondary node"),
     _PEarlyRelease,
     _PNodeName,
     ("remote_node", None, ht.TMaybeString, "New secondary node"),
-    ("iallocator", None, ht.TMaybeString, "Iallocator for computing solution"),
+    _PIAllocFromDesc("Iallocator for computing solution"),
     ("mode", ht.NoDefault, ht.TElemOf(constants.NODE_EVAC_MODES),
      "Node evacuation mode"),
     ]
     ("mode", ht.NoDefault, ht.TElemOf(constants.NODE_EVAC_MODES),
      "Node evacuation mode"),
     ]
@@ -1154,6 +1261,7 @@ class OpInstanceCreate(OpCode):
     _PWaitForSync,
     _PNameCheck,
     _PIgnoreIpolicy,
     _PWaitForSync,
     _PNameCheck,
     _PIgnoreIpolicy,
+    _POpportunisticLocking,
     ("beparams", ht.EmptyDict, ht.TDict, "Backend parameters for instance"),
     ("disks", ht.NoDefault, ht.TListOf(_TDiskParams),
      "Disk descriptions, for example ``[{\"%s\": 100}, {\"%s\": 5}]``;"
     ("beparams", ht.EmptyDict, ht.TDict, "Backend parameters for instance"),
     ("disks", ht.NoDefault, ht.TListOf(_TDiskParams),
      "Disk descriptions, for example ``[{\"%s\": 100}, {\"%s\": 5}]``;"
@@ -1165,18 +1273,18 @@ class OpInstanceCreate(OpCode):
       " or ".join("``%s``" % i for i in sorted(constants.DISK_ACCESS_SET)))),
     ("disk_template", ht.NoDefault, _BuildDiskTemplateCheck(True),
      "Disk template"),
       " or ".join("``%s``" % i for i in sorted(constants.DISK_ACCESS_SET)))),
     ("disk_template", ht.NoDefault, _BuildDiskTemplateCheck(True),
      "Disk template"),
-    ("file_driver", None, ht.TOr(ht.TNone, ht.TElemOf(constants.FILE_DRIVER)),
+    ("file_driver", None, ht.TMaybe(ht.TElemOf(constants.FILE_DRIVER)),
      "Driver for file-backed disks"),
     ("file_storage_dir", None, ht.TMaybeString,
      "Directory for storing file-backed disks"),
     ("hvparams", ht.EmptyDict, ht.TDict,
      "Hypervisor parameters for instance, hypervisor-dependent"),
     ("hypervisor", None, ht.TMaybeString, "Hypervisor"),
      "Driver for file-backed disks"),
     ("file_storage_dir", None, ht.TMaybeString,
      "Directory for storing file-backed disks"),
     ("hvparams", ht.EmptyDict, ht.TDict,
      "Hypervisor parameters for instance, hypervisor-dependent"),
     ("hypervisor", None, ht.TMaybeString, "Hypervisor"),
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator for deciding which node(s) to use"),
+    _PIAllocFromDesc("Iallocator for deciding which node(s) to use"),
     ("identify_defaults", False, ht.TBool,
      "Reset instance parameters to default if equal"),
     ("ip_check", True, ht.TBool, _PIpCheckDoc),
     ("identify_defaults", False, ht.TBool,
      "Reset instance parameters to default if equal"),
     ("ip_check", True, ht.TBool, _PIpCheckDoc),
+    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
     ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES),
      "Instance creation mode"),
     ("nics", ht.NoDefault, ht.TListOf(_TestNicDef),
     ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES),
      "Instance creation mode"),
     ("nics", ht.NoDefault, ht.TListOf(_TestNicDef),
@@ -1191,12 +1299,12 @@ class OpInstanceCreate(OpCode):
     ("os_type", None, ht.TMaybeString, "Operating system"),
     ("pnode", None, ht.TMaybeString, "Primary node"),
     ("snode", None, ht.TMaybeString, "Secondary node"),
     ("os_type", None, ht.TMaybeString, "Operating system"),
     ("pnode", None, ht.TMaybeString, "Primary node"),
     ("snode", None, ht.TMaybeString, "Secondary node"),
-    ("source_handshake", None, ht.TOr(ht.TList, ht.TNone),
+    ("source_handshake", None, ht.TMaybe(ht.TList),
      "Signed handshake from source (remote import only)"),
     ("source_instance_name", None, ht.TMaybeString,
      "Source instance name (remote import only)"),
     ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
      "Signed handshake from source (remote import only)"),
     ("source_instance_name", None, ht.TMaybeString,
      "Source instance name (remote import only)"),
     ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
-     ht.TPositiveInt,
+     ht.TNonNegativeInt,
      "How long source instance was given to shut down (remote import only)"),
     ("source_x509_ca", None, ht.TMaybeString,
      "Source X509 CA in PEM format (remote import only)"),
      "How long source instance was given to shut down (remote import only)"),
     ("source_x509_ca", None, ht.TMaybeString,
      "Source X509 CA in PEM format (remote import only)"),
@@ -1213,9 +1321,9 @@ class OpInstanceMultiAlloc(OpCode):
 
   """
   OP_PARAMS = [
 
   """
   OP_PARAMS = [
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator used to allocate all the instances"),
-    ("instances", [], ht.TListOf(ht.TInstanceOf(OpInstanceCreate)),
+    _POpportunisticLocking,
+    _PIAllocFromDesc("Iallocator used to allocate all the instances"),
+    ("instances", ht.EmptyList, ht.TListOf(ht.TInstanceOf(OpInstanceCreate)),
      "List of instance create opcodes describing the instances to allocate"),
     ]
   _JOB_LIST = ht.Comment("List of submitted jobs")(TJobIdList)
      "List of instance create opcodes describing the instances to allocate"),
     ]
   _JOB_LIST = ht.Comment("List of submitted jobs")(TJobIdList)
@@ -1224,7 +1332,7 @@ class OpInstanceMultiAlloc(OpCode):
   OP_RESULT = ht.TStrictDict(True, True, {
     constants.JOB_IDS_KEY: _JOB_LIST,
     ALLOCATABLE_KEY: ht.TListOf(ht.TNonEmptyString),
   OP_RESULT = ht.TStrictDict(True, True, {
     constants.JOB_IDS_KEY: _JOB_LIST,
     ALLOCATABLE_KEY: ht.TListOf(ht.TNonEmptyString),
-    FAILED_KEY: ht.TListOf(ht.TNonEmptyString)
+    FAILED_KEY: ht.TListOf(ht.TNonEmptyString),
     })
 
   def __getstate__(self):
     })
 
   def __getstate__(self):
@@ -1252,10 +1360,21 @@ class OpInstanceMultiAlloc(OpCode):
                        type(state))
 
     if "instances" in state:
                        type(state))
 
     if "instances" in state:
-      insts = [OpCode.LoadOpCode(inst) for inst in state["instances"]]
-      state["instances"] = insts
+      state["instances"] = map(OpCode.LoadOpCode, state["instances"])
+
     return OpCode.__setstate__(self, state)
 
     return OpCode.__setstate__(self, state)
 
+  def Validate(self, set_defaults):
+    """Validates this opcode.
+
+    We do this recursively.
+
+    """
+    OpCode.Validate(self, set_defaults)
+
+    for inst in self.instances: # pylint: disable=E1101
+      inst.Validate(set_defaults)
+
 
 class OpInstanceReinstall(OpCode):
   """Reinstall an instance's OS."""
 
 class OpInstanceReinstall(OpCode):
   """Reinstall an instance's OS."""
@@ -1313,8 +1432,9 @@ class OpInstanceShutdown(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PForce,
     _PIgnoreOfflineNodes,
     _PIgnoreOfflineNodes,
-    ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt,
+    ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TNonNegativeInt,
      "How long to wait for instance to shut down"),
     _PNoRemember,
     ]
      "How long to wait for instance to shut down"),
     _PNoRemember,
     ]
@@ -1344,11 +1464,10 @@ class OpInstanceReplaceDisks(OpCode):
     _PIgnoreIpolicy,
     ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES),
      "Replacement mode"),
     _PIgnoreIpolicy,
     ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES),
      "Replacement mode"),
-    ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt),
+    ("disks", ht.EmptyList, ht.TListOf(ht.TNonNegativeInt),
      "Disk indexes"),
     ("remote_node", None, ht.TMaybeString, "New secondary node"),
      "Disk indexes"),
     ("remote_node", None, ht.TMaybeString, "New secondary node"),
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator for deciding new secondary node"),
+    _PIAllocFromDesc("Iallocator for deciding new secondary node"),
     ]
   OP_RESULT = ht.TNone
 
     ]
   OP_RESULT = ht.TNone
 
@@ -1362,8 +1481,8 @@ class OpInstanceFailover(OpCode):
     _PIgnoreConsistency,
     _PMigrationTargetNode,
     _PIgnoreIpolicy,
     _PIgnoreConsistency,
     _PMigrationTargetNode,
     _PIgnoreIpolicy,
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator for deciding the target node for shared-storage instances"),
+    _PIAllocFromDesc("Iallocator for deciding the target node for"
+                     " shared-storage instances"),
     ]
   OP_RESULT = ht.TNone
 
     ]
   OP_RESULT = ht.TNone
 
@@ -1388,8 +1507,8 @@ class OpInstanceMigrate(OpCode):
     _PIgnoreIpolicy,
     ("cleanup", False, ht.TBool,
      "Whether a previously failed migration should be cleaned up"),
     _PIgnoreIpolicy,
     ("cleanup", False, ht.TBool,
      "Whether a previously failed migration should be cleaned up"),
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator for deciding the target node for shared-storage instances"),
+    _PIAllocFromDesc("Iallocator for deciding the target node for"
+                     " shared-storage instances"),
     ("allow_failover", False, ht.TBool,
      "Whether we can fallback to failover if migration is not possible"),
     ]
     ("allow_failover", False, ht.TBool,
      "Whether we can fallback to failover if migration is not possible"),
     ]
@@ -1421,7 +1540,7 @@ class OpInstanceConsole(OpCode):
   """Connect to an instance's console."""
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
   """Connect to an instance's console."""
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
-    _PInstanceName
+    _PInstanceName,
     ]
   OP_RESULT = ht.TDict
 
     ]
   OP_RESULT = ht.TDict
 
@@ -1454,20 +1573,19 @@ class OpInstanceRecreateDisks(OpCode):
   """Recreate an instance's disks."""
   _TDiskChanges = \
     ht.TAnd(ht.TIsLength(2),
   """Recreate an instance's disks."""
   _TDiskChanges = \
     ht.TAnd(ht.TIsLength(2),
-            ht.TItems([ht.Comment("Disk index")(ht.TPositiveInt),
+            ht.TItems([ht.Comment("Disk index")(ht.TNonNegativeInt),
                        ht.Comment("Parameters")(_TDiskParams)]))
 
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
     ("disks", ht.EmptyList,
                        ht.Comment("Parameters")(_TDiskParams)]))
 
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
     ("disks", ht.EmptyList,
-     ht.TOr(ht.TListOf(ht.TPositiveInt), ht.TListOf(_TDiskChanges)),
+     ht.TOr(ht.TListOf(ht.TNonNegativeInt), ht.TListOf(_TDiskChanges)),
      "List of disk indexes (deprecated) or a list of tuples containing a disk"
      " index and a possibly empty dictionary with disk parameter changes"),
     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
      "New instance nodes, if relocation is desired"),
      "List of disk indexes (deprecated) or a list of tuples containing a disk"
      " index and a possibly empty dictionary with disk parameter changes"),
     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
      "New instance nodes, if relocation is desired"),
-    ("iallocator", None, ht.TMaybeString,
-     "Iallocator for deciding new nodes"),
+    _PIAllocFromDesc("Iallocator for deciding new nodes"),
     ]
   OP_RESULT = ht.TNone
 
     ]
   OP_RESULT = ht.TNone
 
@@ -1504,7 +1622,7 @@ def _TestInstSetParamsModList(fn):
   # TODO: Remove in version 2.8 including support in LUInstanceSetParams
   old_mod_item_fn = \
     ht.TAnd(ht.TIsLength(2), ht.TItems([
   # TODO: Remove in version 2.8 including support in LUInstanceSetParams
   old_mod_item_fn = \
     ht.TAnd(ht.TIsLength(2), ht.TItems([
-      ht.TOr(ht.TElemOf(constants.DDMS_VALUES), ht.TPositiveInt),
+      ht.TOr(ht.TElemOf(constants.DDMS_VALUES), ht.TNonNegativeInt),
       fn,
       ]))
 
       fn,
       ]))
 
@@ -1546,10 +1664,10 @@ class OpInstanceSetParams(OpCode):
     ("disks", ht.EmptyList, TestDiskModifications,
      "List of disk changes; see ``nics``"),
     ("beparams", ht.EmptyDict, ht.TDict, "Per-instance backend parameters"),
     ("disks", ht.EmptyList, TestDiskModifications,
      "List of disk changes; see ``nics``"),
     ("beparams", ht.EmptyDict, ht.TDict, "Per-instance backend parameters"),
-    ("runtime_mem", None, ht.TMaybeStrictPositiveInt, "New runtime memory"),
+    ("runtime_mem", None, ht.TMaybePositiveInt, "New runtime memory"),
     ("hvparams", ht.EmptyDict, ht.TDict,
      "Per-instance hypervisor parameters, hypervisor-dependent"),
     ("hvparams", ht.EmptyDict, ht.TDict,
      "Per-instance hypervisor parameters, hypervisor-dependent"),
-    ("disk_template", None, ht.TOr(ht.TNone, _BuildDiskTemplateCheck(False)),
+    ("disk_template", None, ht.TMaybe(_BuildDiskTemplateCheck(False)),
      "Disk template for instance"),
     ("remote_node", None, ht.TMaybeString,
      "Secondary node (used when changing disk template)"),
      "Disk template for instance"),
     ("remote_node", None, ht.TMaybeString,
      "Secondary node (used when changing disk template)"),
@@ -1559,6 +1677,7 @@ class OpInstanceSetParams(OpCode):
     ("wait_for_sync", True, ht.TBool,
      "Whether to wait for the disk to synchronize, when changing template"),
     ("offline", None, ht.TMaybeBool, "Whether to mark instance as offline"),
     ("wait_for_sync", True, ht.TBool,
      "Whether to wait for the disk to synchronize, when changing template"),
     ("offline", None, ht.TMaybeBool, "Whether to mark instance as offline"),
+    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
     ]
   OP_RESULT = _TSetParamsResult
 
     ]
   OP_RESULT = _TSetParamsResult
 
@@ -1570,7 +1689,7 @@ class OpInstanceGrowDisk(OpCode):
     _PInstanceName,
     _PWaitForSync,
     ("disk", ht.NoDefault, ht.TInt, "Disk index"),
     _PInstanceName,
     _PWaitForSync,
     ("disk", ht.NoDefault, ht.TInt, "Disk index"),
-    ("amount", ht.NoDefault, ht.TPositiveInt,
+    ("amount", ht.NoDefault, ht.TNonNegativeInt,
      "Amount of disk space to add (megabytes)"),
     ("absolute", False, ht.TBool,
      "Whether the amount parameter is an absolute target or a relative one"),
      "Amount of disk space to add (megabytes)"),
     ("absolute", False, ht.TBool,
      "Whether the amount parameter is an absolute target or a relative one"),
@@ -1584,9 +1703,8 @@ class OpInstanceChangeGroup(OpCode):
   OP_PARAMS = [
     _PInstanceName,
     _PEarlyRelease,
   OP_PARAMS = [
     _PInstanceName,
     _PEarlyRelease,
-    ("iallocator", None, ht.TMaybeString, "Iallocator for computing solution"),
-    ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "Destination group names or UUIDs (defaults to \"all but current group\""),
+    _PIAllocFromDesc("Iallocator for computing solution"),
+    _PTargetGroups,
     ]
   OP_RESULT = TJobIdListOnly
 
     ]
   OP_RESULT = TJobIdListOnly
 
@@ -1670,9 +1788,8 @@ class OpGroupEvacuate(OpCode):
   OP_PARAMS = [
     _PGroupName,
     _PEarlyRelease,
   OP_PARAMS = [
     _PGroupName,
     _PEarlyRelease,
-    ("iallocator", None, ht.TMaybeString, "Iallocator for computing solution"),
-    ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "Destination group names or UUIDs"),
+    _PIAllocFromDesc("Iallocator for computing solution"),
+    _PTargetGroups,
     ]
   OP_RESULT = TJobIdListOnly
 
     ]
   OP_RESULT = TJobIdListOnly
 
@@ -1688,6 +1805,17 @@ class OpOsDiagnose(OpCode):
   OP_RESULT = _TOldQueryResult
 
 
   OP_RESULT = _TOldQueryResult
 
 
+# ExtStorage opcodes
+class OpExtStorageDiagnose(OpCode):
+  """Compute the list of external storage providers."""
+  OP_PARAMS = [
+    _POutputFields,
+    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
+     "Which ExtStorage Provider to diagnose"),
+    ]
+  OP_RESULT = _TOldQueryResult
+
+
 # Exports opcodes
 class OpBackupQuery(OpCode):
   """Compute the list of exported images."""
 # Exports opcodes
 class OpBackupQuery(OpCode):
   """Compute the list of exported images."""
@@ -1714,17 +1842,18 @@ class OpBackupPrepare(OpCode):
     ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES),
      "Export mode"),
     ]
     ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES),
      "Export mode"),
     ]
-  OP_RESULT = ht.TOr(ht.TNone, ht.TDict)
+  OP_RESULT = ht.TMaybeDict
 
 
 class OpBackupExport(OpCode):
   """Export an instance.
 
 
 
 class OpBackupExport(OpCode):
   """Export an instance.
 
-  For local exports, the export destination is the node name. For remote
-  exports, the export destination is a list of tuples, each consisting of
-  hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
-  the cluster domain secret over the value "${index}:${hostname}:${port}". The
-  destination X509 CA must be a signed certificate.
+  For local exports, the export destination is the node name. For
+  remote exports, the export destination is a list of tuples, each
+  consisting of hostname/IP address, port, magic, HMAC and HMAC
+  salt. The HMAC is calculated using the cluster domain secret over
+  the value "${index}:${hostname}:${port}". The destination X509 CA
+  must be a signed certificate.
 
   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
   @ivar target_node: Export destination
 
   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
   @ivar target_node: Export destination
@@ -1748,7 +1877,7 @@ class OpBackupExport(OpCode):
      "Whether to ignore failures while removing instances"),
     ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES),
      "Export mode"),
      "Whether to ignore failures while removing instances"),
     ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES),
      "Export mode"),
-    ("x509_key_name", None, ht.TOr(ht.TList, ht.TNone),
+    ("x509_key_name", None, ht.TMaybe(ht.TList),
      "Name of X509 key (remote export only)"),
     ("destination_x509_ca", None, ht.TMaybeString,
      "Destination X509 CA (remote export only)"),
      "Name of X509 key (remote export only)"),
     ("destination_x509_ca", None, ht.TMaybeString,
      "Destination X509 CA (remote export only)"),
@@ -1802,7 +1931,7 @@ class OpTagsSet(OpCode):
   OP_PARAMS = [
     _PTagKind,
     _PTags,
   OP_PARAMS = [
     _PTagKind,
     _PTags,
-    # Name is only meaningful for nodes and instances
+    # Name is only meaningful for groups, nodes and instances
     ("name", ht.NoDefault, ht.TMaybeString,
      "Name of object where tag(s) should be added"),
     ]
     ("name", ht.NoDefault, ht.TMaybeString,
      "Name of object where tag(s) should be added"),
     ]
@@ -1814,7 +1943,7 @@ class OpTagsDel(OpCode):
   OP_PARAMS = [
     _PTagKind,
     _PTags,
   OP_PARAMS = [
     _PTagKind,
     _PTags,
-    # Name is only meaningful for nodes and instances
+    # Name is only meaningful for groups, nodes and instances
     ("name", ht.NoDefault, ht.TMaybeString,
      "Name of object where tag(s) should be deleted"),
     ]
     ("name", ht.NoDefault, ht.TMaybeString,
      "Name of object where tag(s) should be deleted"),
     ]
@@ -1848,9 +1977,19 @@ class OpTestDelay(OpCode):
     ("duration", ht.NoDefault, ht.TNumber, None),
     ("on_master", True, ht.TBool, None),
     ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
     ("duration", ht.NoDefault, ht.TNumber, None),
     ("on_master", True, ht.TBool, None),
     ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
-    ("repeat", 0, ht.TPositiveInt, None),
+    ("repeat", 0, ht.TNonNegativeInt, None),
     ]
 
     ]
 
+  def OP_DSC_FORMATTER(self, value): # pylint: disable=C0103,R0201
+    """Custom formatter for duration.
+
+    """
+    try:
+      v = float(value)
+    except TypeError:
+      v = value
+    return str(v)
+
 
 class OpTestAllocator(OpCode):
   """Allocator framework testing.
 
 class OpTestAllocator(OpCode):
   """Allocator framework testing.
@@ -1863,7 +2002,7 @@ class OpTestAllocator(OpCode):
       return the allocator output (direction 'out')
 
   """
       return the allocator output (direction 'out')
 
   """
-  OP_DSC_FIELD = "allocator"
+  OP_DSC_FIELD = "iallocator"
   OP_PARAMS = [
     ("direction", ht.NoDefault,
      ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS), None),
   OP_PARAMS = [
     ("direction", ht.NoDefault,
      ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS), None),
@@ -1873,22 +2012,22 @@ class OpTestAllocator(OpCode):
      ht.TMaybeListOf(ht.TDictOf(ht.TElemOf([constants.INIC_MAC,
                                             constants.INIC_IP,
                                             "bridge"]),
      ht.TMaybeListOf(ht.TDictOf(ht.TElemOf([constants.INIC_MAC,
                                             constants.INIC_IP,
                                             "bridge"]),
-                                ht.TOr(ht.TNone, ht.TNonEmptyString))),
+                                ht.TMaybeString)),
      None),
      None),
-    ("disks", ht.NoDefault, ht.TOr(ht.TNone, ht.TList), None),
+    ("disks", ht.NoDefault, ht.TMaybe(ht.TList), None),
     ("hypervisor", None, ht.TMaybeString, None),
     ("hypervisor", None, ht.TMaybeString, None),
-    ("allocator", None, ht.TMaybeString, None),
+    _PIAllocFromDesc(None),
     ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
     ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
-    ("memory", None, ht.TOr(ht.TNone, ht.TPositiveInt), None),
-    ("vcpus", None, ht.TOr(ht.TNone, ht.TPositiveInt), None),
+    ("memory", None, ht.TMaybe(ht.TNonNegativeInt), None),
+    ("vcpus", None, ht.TMaybe(ht.TNonNegativeInt), None),
     ("os", None, ht.TMaybeString, None),
     ("disk_template", None, ht.TMaybeString, None),
     ("instances", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
     ("evac_mode", None,
     ("os", None, ht.TMaybeString, None),
     ("disk_template", None, ht.TMaybeString, None),
     ("instances", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
     ("evac_mode", None,
-     ht.TOr(ht.TNone, ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)), None),
+     ht.TMaybe(ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)), None),
     ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
     ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
-    ("spindle_use", 1, ht.TPositiveInt, None),
-    ("count", 1, ht.TPositiveInt, None),
+    ("spindle_use", 1, ht.TNonNegativeInt, None),
+    ("count", 1, ht.TNonNegativeInt, None),
     ]
 
 
     ]
 
 
@@ -1917,6 +2056,103 @@ class OpTestDummy(OpCode):
   WITH_LU = False
 
 
   WITH_LU = False
 
 
+# Network opcodes
+# Add a new network in the cluster
+class OpNetworkAdd(OpCode):
+  """Add an IP network to the cluster."""
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PNetworkName,
+    ("network", ht.NoDefault, _TIpNetwork4, "IPv4 subnet"),
+    ("gateway", None, ht.TMaybe(_TIpAddress4), "IPv4 gateway"),
+    ("network6", None, ht.TMaybe(_TIpNetwork6), "IPv6 subnet"),
+    ("gateway6", None, ht.TMaybe(_TIpAddress6), "IPv6 gateway"),
+    ("mac_prefix", None, ht.TMaybeString,
+     "MAC address prefix that overrides cluster one"),
+    ("add_reserved_ips", None, _TMaybeAddr4List,
+     "Which IP addresses to reserve"),
+    ("conflicts_check", True, ht.TBool,
+     "Whether to check for conflicting IP addresses"),
+    ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "Network tags"),
+    ]
+  OP_RESULT = ht.TNone
+
+
+class OpNetworkRemove(OpCode):
+  """Remove an existing network from the cluster.
+     Must not be connected to any nodegroup.
+
+  """
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PNetworkName,
+    _PForce,
+    ]
+  OP_RESULT = ht.TNone
+
+
+class OpNetworkSetParams(OpCode):
+  """Modify Network's parameters except for IPv4 subnet"""
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PNetworkName,
+    ("gateway", None, ht.TMaybeValueNone(_TIpAddress4), "IPv4 gateway"),
+    ("network6", None, ht.TMaybeValueNone(_TIpNetwork6), "IPv6 subnet"),
+    ("gateway6", None, ht.TMaybeValueNone(_TIpAddress6), "IPv6 gateway"),
+    ("mac_prefix", None, ht.TMaybeValueNone(ht.TString),
+     "MAC address prefix that overrides cluster one"),
+    ("add_reserved_ips", None, _TMaybeAddr4List,
+     "Which external IP addresses to reserve"),
+    ("remove_reserved_ips", None, _TMaybeAddr4List,
+     "Which external IP addresses to release"),
+    ]
+  OP_RESULT = ht.TNone
+
+
+class OpNetworkConnect(OpCode):
+  """Connect a Network to a specific Nodegroup with the defined netparams
+     (mode, link). Nics in this Network will inherit those params.
+     Produce errors if a NIC (that its not already assigned to a network)
+     has an IP that is contained in the Network this will produce error unless
+     --no-conflicts-check is passed.
+
+  """
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PGroupName,
+    _PNetworkName,
+    ("network_mode", ht.NoDefault, ht.TElemOf(constants.NIC_VALID_MODES),
+     "Connectivity mode"),
+    ("network_link", ht.NoDefault, ht.TString, "Connectivity link"),
+    ("conflicts_check", True, ht.TBool, "Whether to check for conflicting IPs"),
+    ]
+  OP_RESULT = ht.TNone
+
+
+class OpNetworkDisconnect(OpCode):
+  """Disconnect a Network from a Nodegroup. Produce errors if NICs are
+     present in the Network unless --no-conficts-check option is passed.
+
+  """
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PGroupName,
+    _PNetworkName,
+    ]
+  OP_RESULT = ht.TNone
+
+
+class OpNetworkQuery(OpCode):
+  """Compute the list of networks."""
+  OP_PARAMS = [
+    _POutputFields,
+    _PUseLocking,
+    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
+     "Empty list to query all groups, group names otherwise"),
+    ]
+  OP_RESULT = _TOldQueryResult
+
+
 def _GetOpList():
   """Returns list of all defined opcodes.
 
 def _GetOpList():
   """Returns list of all defined opcodes.