Add new spindle_count node parameter
[ganeti-local] / lib / rapi / rlib2.py
index ace53cf..46e0029 100644 (file)
@@ -51,7 +51,7 @@ PUT should be prefered over POST.
 
 """
 
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 # C0103: Invalid name, since the R_* names are not conforming
 
 
 # C0103: Invalid name, since the R_* names are not conforming
 
@@ -94,6 +94,7 @@ G_FIELDS = [
   "name",
   "node_cnt",
   "node_list",
   "name",
   "node_cnt",
   "node_list",
+  "ipolicy",
   ] + _COMMON_FIELDS
 
 J_FIELDS_BULK = [
   ] + _COMMON_FIELDS
 
 J_FIELDS_BULK = [
@@ -108,14 +109,14 @@ J_FIELDS = J_FIELDS_BULK + [
   ]
 
 _NR_DRAINED = "drained"
   ]
 
 _NR_DRAINED = "drained"
-_NR_MASTER_CANDIATE = "master-candidate"
+_NR_MASTER_CANDIDATE = "master-candidate"
 _NR_MASTER = "master"
 _NR_OFFLINE = "offline"
 _NR_REGULAR = "regular"
 
 _NR_MAP = {
   constants.NR_MASTER: _NR_MASTER,
 _NR_MASTER = "master"
 _NR_OFFLINE = "offline"
 _NR_REGULAR = "regular"
 
 _NR_MAP = {
   constants.NR_MASTER: _NR_MASTER,
-  constants.NR_MCANDIDATE: _NR_MASTER_CANDIATE,
+  constants.NR_MCANDIDATE: _NR_MASTER_CANDIDATE,
   constants.NR_DRAINED: _NR_DRAINED,
   constants.NR_OFFLINE: _NR_OFFLINE,
   constants.NR_REGULAR: _NR_REGULAR,
   constants.NR_DRAINED: _NR_DRAINED,
   constants.NR_OFFLINE: _NR_OFFLINE,
   constants.NR_REGULAR: _NR_REGULAR,
@@ -161,6 +162,12 @@ class R_root(baserlib.ResourceBase):
     return None
 
 
     return None
 
 
+class R_2(R_root):
+  """/2 resource.
+
+  """
+
+
 class R_version(baserlib.ResourceBase):
   """/version resource.
 
 class R_version(baserlib.ResourceBase):
   """/version resource.
 
@@ -176,10 +183,12 @@ class R_version(baserlib.ResourceBase):
     return constants.RAPI_VERSION
 
 
     return constants.RAPI_VERSION
 
 
-class R_2_info(baserlib.ResourceBase):
+class R_2_info(baserlib.OpcodeResource):
   """/2/info resource.
 
   """
   """/2/info resource.
 
   """
+  GET_OPCODE = opcodes.OpClusterQuery
+
   def GET(self):
     """Returns cluster information.
 
   def GET(self):
     """Returns cluster information.
 
@@ -200,10 +209,12 @@ class R_2_features(baserlib.ResourceBase):
     return list(ALL_FEATURES)
 
 
     return list(ALL_FEATURES)
 
 
-class R_2_os(baserlib.ResourceBase):
+class R_2_os(baserlib.OpcodeResource):
   """/2/os resource.
 
   """
   """/2/os resource.
 
   """
+  GET_OPCODE = opcodes.OpOsDiagnose
+
   def GET(self):
     """Return a list of all OSes.
 
   def GET(self):
     """Return a list of all OSes.
 
@@ -345,10 +356,12 @@ class R_2_jobs_id_wait(baserlib.ResourceBase):
       }
 
 
       }
 
 
-class R_2_nodes(baserlib.ResourceBase):
+class R_2_nodes(baserlib.OpcodeResource):
   """/2/nodes resource.
 
   """
   """/2/nodes resource.
 
   """
+  GET_OPCODE = opcodes.OpNodeQuery
+
   def GET(self):
     """Returns a list of all nodes.
 
   def GET(self):
     """Returns a list of all nodes.
 
@@ -365,10 +378,12 @@ class R_2_nodes(baserlib.ResourceBase):
                                    uri_fields=("id", "uri"))
 
 
                                    uri_fields=("id", "uri"))
 
 
-class R_2_nodes_name(baserlib.ResourceBase):
+class R_2_nodes_name(baserlib.OpcodeResource):
   """/2/nodes/[node_name] resource.
 
   """
   """/2/nodes/[node_name] resource.
 
   """
+  GET_OPCODE = opcodes.OpNodeQuery
+
   def GET(self):
     """Send information about a node.
 
   def GET(self):
     """Send information about a node.
 
@@ -383,10 +398,28 @@ class R_2_nodes_name(baserlib.ResourceBase):
     return baserlib.MapFields(N_FIELDS, result[0])
 
 
     return baserlib.MapFields(N_FIELDS, result[0])
 
 
-class R_2_nodes_name_role(baserlib.ResourceBase):
-  """ /2/nodes/[node_name]/role resource.
+class R_2_nodes_name_powercycle(baserlib.OpcodeResource):
+  """/2/nodes/[node_name]/powercycle resource.
 
   """
 
   """
+  POST_OPCODE = opcodes.OpNodePowercycle
+
+  def GetPostOpInput(self):
+    """Tries to powercycle a node.
+
+    """
+    return (self.request_body, {
+      "node_name": self.items[0],
+      "force": self.useForce(),
+      })
+
+
+class R_2_nodes_name_role(baserlib.OpcodeResource):
+  """/2/nodes/[node_name]/role resource.
+
+  """
+  PUT_OPCODE = opcodes.OpNodeSetParams
+
   def GET(self):
     """Returns the current node role.
 
   def GET(self):
     """Returns the current node role.
 
@@ -400,16 +433,12 @@ class R_2_nodes_name_role(baserlib.ResourceBase):
 
     return _NR_MAP[result[0][0]]
 
 
     return _NR_MAP[result[0][0]]
 
-  def PUT(self):
+  def GetPutOpInput(self):
     """Sets the node role.
 
     """Sets the node role.
 
-    @return: a job id
-
     """
     """
-    if not isinstance(self.request_body, basestring):
-      raise http.HttpBadRequest("Invalid body contents, not a string")
+    baserlib.CheckType(self.request_body, basestring, "Body contents")
 
 
-    node_name = self.items[0]
     role = self.request_body
 
     if role == _NR_REGULAR:
     role = self.request_body
 
     if role == _NR_REGULAR:
@@ -417,7 +446,7 @@ class R_2_nodes_name_role(baserlib.ResourceBase):
       offline = False
       drained = False
 
       offline = False
       drained = False
 
-    elif role == _NR_MASTER_CANDIATE:
+    elif role == _NR_MASTER_CANDIDATE:
       candidate = True
       offline = drained = None
 
       candidate = True
       offline = drained = None
 
@@ -432,13 +461,16 @@ class R_2_nodes_name_role(baserlib.ResourceBase):
     else:
       raise http.HttpBadRequest("Can't set '%s' role" % role)
 
     else:
       raise http.HttpBadRequest("Can't set '%s' role" % role)
 
-    op = opcodes.OpNodeSetParams(node_name=node_name,
-                                 master_candidate=candidate,
-                                 offline=offline,
-                                 drained=drained,
-                                 force=bool(self.useForce()))
+    assert len(self.items) == 1
 
 
-    return self.SubmitJob([op])
+    return ({}, {
+      "node_name": self.items[0],
+      "master_candidate": candidate,
+      "offline": offline,
+      "drained": drained,
+      "force": self.useForce(),
+      "auto_promote": bool(self._checkIntVariable("auto-promote", default=0)),
+      })
 
 
 class R_2_nodes_name_evacuate(baserlib.OpcodeResource):
 
 
 class R_2_nodes_name_evacuate(baserlib.OpcodeResource):
@@ -492,45 +524,62 @@ class R_2_nodes_name_migrate(baserlib.OpcodeResource):
       })
 
 
       })
 
 
-class R_2_nodes_name_storage(baserlib.ResourceBase):
+class R_2_nodes_name_modify(baserlib.OpcodeResource):
+  """/2/nodes/[node_name]/modify resource.
+
+  """
+  POST_OPCODE = opcodes.OpNodeSetParams
+
+  def GetPostOpInput(self):
+    """Changes parameters of a node.
+
+    """
+    assert len(self.items) == 1
+
+    return (self.request_body, {
+      "node_name": self.items[0],
+      })
+
+
+class R_2_nodes_name_storage(baserlib.OpcodeResource):
   """/2/nodes/[node_name]/storage resource.
 
   """
   # LUNodeQueryStorage acquires locks, hence restricting access to GET
   GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
   """/2/nodes/[node_name]/storage resource.
 
   """
   # LUNodeQueryStorage acquires locks, hence restricting access to GET
   GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
+  GET_OPCODE = opcodes.OpNodeQueryStorage
 
 
-  def GET(self):
-    node_name = self.items[0]
+  def GetGetOpInput(self):
+    """List storage available on a node.
 
 
+    """
     storage_type = self._checkStringVariable("storage_type", None)
     storage_type = self._checkStringVariable("storage_type", None)
-    if not storage_type:
-      raise http.HttpBadRequest("Missing the required 'storage_type'"
-                                " parameter")
-
     output_fields = self._checkStringVariable("output_fields", None)
     output_fields = self._checkStringVariable("output_fields", None)
+
     if not output_fields:
       raise http.HttpBadRequest("Missing the required 'output_fields'"
                                 " parameter")
 
     if not output_fields:
       raise http.HttpBadRequest("Missing the required 'output_fields'"
                                 " parameter")
 
-    op = opcodes.OpNodeQueryStorage(nodes=[node_name],
-                                    storage_type=storage_type,
-                                    output_fields=output_fields.split(","))
-    return self.SubmitJob([op])
+    return ({}, {
+      "nodes": [self.items[0]],
+      "storage_type": storage_type,
+      "output_fields": output_fields.split(","),
+      })
 
 
 
 
-class R_2_nodes_name_storage_modify(baserlib.ResourceBase):
+class R_2_nodes_name_storage_modify(baserlib.OpcodeResource):
   """/2/nodes/[node_name]/storage/modify resource.
 
   """
   """/2/nodes/[node_name]/storage/modify resource.
 
   """
-  def PUT(self):
-    node_name = self.items[0]
+  PUT_OPCODE = opcodes.OpNodeModifyStorage
 
 
-    storage_type = self._checkStringVariable("storage_type", None)
-    if not storage_type:
-      raise http.HttpBadRequest("Missing the required 'storage_type'"
-                                " parameter")
+  def GetPutOpInput(self):
+    """Modifies a storage volume on a node.
 
 
+    """
+    storage_type = self._checkStringVariable("storage_type", None)
     name = self._checkStringVariable("name", None)
     name = self._checkStringVariable("name", None)
+
     if not name:
       raise http.HttpBadRequest("Missing the required 'name'"
                                 " parameter")
     if not name:
       raise http.HttpBadRequest("Missing the required 'name'"
                                 " parameter")
@@ -541,40 +590,42 @@ class R_2_nodes_name_storage_modify(baserlib.ResourceBase):
       changes[constants.SF_ALLOCATABLE] = \
         bool(self._checkIntVariable("allocatable", default=1))
 
       changes[constants.SF_ALLOCATABLE] = \
         bool(self._checkIntVariable("allocatable", default=1))
 
-    op = opcodes.OpNodeModifyStorage(node_name=node_name,
-                                     storage_type=storage_type,
-                                     name=name,
-                                     changes=changes)
-    return self.SubmitJob([op])
+    return ({}, {
+      "node_name": self.items[0],
+      "storage_type": storage_type,
+      "name": name,
+      "changes": changes,
+      })
 
 
 
 
-class R_2_nodes_name_storage_repair(baserlib.ResourceBase):
+class R_2_nodes_name_storage_repair(baserlib.OpcodeResource):
   """/2/nodes/[node_name]/storage/repair resource.
 
   """
   """/2/nodes/[node_name]/storage/repair resource.
 
   """
-  def PUT(self):
-    node_name = self.items[0]
+  PUT_OPCODE = opcodes.OpRepairNodeStorage
 
 
-    storage_type = self._checkStringVariable("storage_type", None)
-    if not storage_type:
-      raise http.HttpBadRequest("Missing the required 'storage_type'"
-                                " parameter")
+  def GetPutOpInput(self):
+    """Repairs a storage volume on a node.
 
 
+    """
+    storage_type = self._checkStringVariable("storage_type", None)
     name = self._checkStringVariable("name", None)
     if not name:
       raise http.HttpBadRequest("Missing the required 'name'"
                                 " parameter")
 
     name = self._checkStringVariable("name", None)
     if not name:
       raise http.HttpBadRequest("Missing the required 'name'"
                                 " parameter")
 
-    op = opcodes.OpRepairNodeStorage(node_name=node_name,
-                                     storage_type=storage_type,
-                                     name=name)
-    return self.SubmitJob([op])
+    return ({}, {
+      "node_name": self.items[0],
+      "storage_type": storage_type,
+      "name": name,
+      })
 
 
 class R_2_groups(baserlib.OpcodeResource):
   """/2/groups resource.
 
   """
 
 
 class R_2_groups(baserlib.OpcodeResource):
   """/2/groups resource.
 
   """
+  GET_OPCODE = opcodes.OpGroupQuery
   POST_OPCODE = opcodes.OpGroupAdd
   POST_RENAME = {
     "name": "group_name",
   POST_OPCODE = opcodes.OpGroupAdd
   POST_RENAME = {
     "name": "group_name",
@@ -605,10 +656,12 @@ class R_2_groups(baserlib.OpcodeResource):
                                    uri_fields=("name", "uri"))
 
 
                                    uri_fields=("name", "uri"))
 
 
-class R_2_groups_name(baserlib.ResourceBase):
+class R_2_groups_name(baserlib.OpcodeResource):
   """/2/groups/[group_name] resource.
 
   """
   """/2/groups/[group_name] resource.
 
   """
+  DELETE_OPCODE = opcodes.OpGroupRemove
+
   def GET(self):
     """Send information about a node group.
 
   def GET(self):
     """Send information about a node group.
 
@@ -622,14 +675,15 @@ class R_2_groups_name(baserlib.ResourceBase):
 
     return baserlib.MapFields(G_FIELDS, result[0])
 
 
     return baserlib.MapFields(G_FIELDS, result[0])
 
-  def DELETE(self):
+  def GetDeleteOpInput(self):
     """Delete a node group.
 
     """
     """Delete a node group.
 
     """
-    op = opcodes.OpGroupRemove(group_name=self.items[0],
-                               dry_run=bool(self.dryRun()))
-
-    return self.SubmitJob([op])
+    assert len(self.items) == 1
+    return ({}, {
+      "group_name": self.items[0],
+      "dry_run": self.dryRun(),
+      })
 
 
 class R_2_groups_name_modify(baserlib.OpcodeResource):
 
 
 class R_2_groups_name_modify(baserlib.OpcodeResource):
@@ -665,49 +719,35 @@ class R_2_groups_name_rename(baserlib.OpcodeResource):
       })
 
 
       })
 
 
-class R_2_groups_name_assign_nodes(baserlib.ResourceBase):
+class R_2_groups_name_assign_nodes(baserlib.OpcodeResource):
   """/2/groups/[group_name]/assign-nodes resource.
 
   """
   """/2/groups/[group_name]/assign-nodes resource.
 
   """
-  def PUT(self):
-    """Assigns nodes to a group.
+  PUT_OPCODE = opcodes.OpGroupAssignNodes
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Assigns nodes to a group.
 
     """
 
     """
-    op = baserlib.FillOpcode(opcodes.OpGroupAssignNodes, self.request_body, {
+    assert len(self.items) == 1
+    return (self.request_body, {
       "group_name": self.items[0],
       "dry_run": self.dryRun(),
       "force": self.useForce(),
       })
 
       "group_name": self.items[0],
       "dry_run": self.dryRun(),
       "force": self.useForce(),
       })
 
-    return self.SubmitJob([op])
-
 
 
-def _ParseInstanceCreateRequestVersion1(data, dry_run):
-  """Parses an instance creation request version 1.
-
-  @rtype: L{opcodes.OpInstanceCreate}
-  @return: Instance creation opcode
+class R_2_instances(baserlib.OpcodeResource):
+  """/2/instances resource.
 
   """
 
   """
-  override = {
-    "dry_run": dry_run,
-    }
-
-  rename = {
+  GET_OPCODE = opcodes.OpInstanceQuery
+  POST_OPCODE = opcodes.OpInstanceCreate
+  POST_RENAME = {
     "os": "os_type",
     "name": "instance_name",
     }
 
     "os": "os_type",
     "name": "instance_name",
     }
 
-  return baserlib.FillOpcode(opcodes.OpInstanceCreate, data, override,
-                             rename=rename)
-
-
-class R_2_instances(baserlib.ResourceBase):
-  """/2/instances resource.
-
-  """
   def GET(self):
     """Returns a list of all available instances.
 
   def GET(self):
     """Returns a list of all available instances.
 
@@ -724,14 +764,13 @@ class R_2_instances(baserlib.ResourceBase):
       return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
                                    uri_fields=("id", "uri"))
 
       return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
                                    uri_fields=("id", "uri"))
 
-  def POST(self):
+  def GetPostOpInput(self):
     """Create an instance.
 
     @return: a job id
 
     """
     """Create an instance.
 
     @return: a job id
 
     """
-    if not isinstance(self.request_body, dict):
-      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
+    baserlib.CheckType(self.request_body, dict, "Body contents")
 
     # Default to request data version 0
     data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
 
     # Default to request data version 0
     data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
@@ -739,22 +778,26 @@ class R_2_instances(baserlib.ResourceBase):
     if data_version == 0:
       raise http.HttpBadRequest("Instance creation request version 0 is no"
                                 " longer supported")
     if data_version == 0:
       raise http.HttpBadRequest("Instance creation request version 0 is no"
                                 " longer supported")
-    elif data_version == 1:
-      data = self.request_body.copy()
-      # Remove "__version__"
-      data.pop(_REQ_DATA_VERSION, None)
-      op = _ParseInstanceCreateRequestVersion1(data, self.dryRun())
-    else:
+    elif data_version != 1:
       raise http.HttpBadRequest("Unsupported request data version %s" %
                                 data_version)
 
       raise http.HttpBadRequest("Unsupported request data version %s" %
                                 data_version)
 
-    return self.SubmitJob([op])
+    data = self.request_body.copy()
+    # Remove "__version__"
+    data.pop(_REQ_DATA_VERSION, None)
+
+    return (data, {
+      "dry_run": self.dryRun(),
+      })
 
 
 
 
-class R_2_instances_name(baserlib.ResourceBase):
+class R_2_instances_name(baserlib.OpcodeResource):
   """/2/instances/[instance_name] resource.
 
   """
   """/2/instances/[instance_name] resource.
 
   """
+  GET_OPCODE = opcodes.OpInstanceQuery
+  DELETE_OPCODE = opcodes.OpInstanceRemove
+
   def GET(self):
     """Send information about an instance.
 
   def GET(self):
     """Send information about an instance.
 
@@ -769,114 +812,99 @@ class R_2_instances_name(baserlib.ResourceBase):
 
     return baserlib.MapFields(I_FIELDS, result[0])
 
 
     return baserlib.MapFields(I_FIELDS, result[0])
 
-  def DELETE(self):
+  def GetDeleteOpInput(self):
     """Delete an instance.
 
     """
     """Delete an instance.
 
     """
-    op = opcodes.OpInstanceRemove(instance_name=self.items[0],
-                                  ignore_failures=False,
-                                  dry_run=bool(self.dryRun()))
-    return self.SubmitJob([op])
+    assert len(self.items) == 1
+    return ({}, {
+      "instance_name": self.items[0],
+      "ignore_failures": False,
+      "dry_run": self.dryRun(),
+      })
 
 
 
 
-class R_2_instances_name_info(baserlib.ResourceBase):
+class R_2_instances_name_info(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/info resource.
 
   """
   """/2/instances/[instance_name]/info resource.
 
   """
-  def GET(self):
+  GET_OPCODE = opcodes.OpInstanceQueryData
+
+  def GetGetOpInput(self):
     """Request detailed instance information.
 
     """
     """Request detailed instance information.
 
     """
-    instance_name = self.items[0]
-    static = bool(self._checkIntVariable("static", default=0))
-
-    op = opcodes.OpInstanceQueryData(instances=[instance_name],
-                                     static=static)
-    return self.SubmitJob([op])
+    assert len(self.items) == 1
+    return ({}, {
+      "instances": [self.items[0]],
+      "static": bool(self._checkIntVariable("static", default=0)),
+      })
 
 
 
 
-class R_2_instances_name_reboot(baserlib.ResourceBase):
+class R_2_instances_name_reboot(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/reboot resource.
 
   Implements an instance reboot.
 
   """
   """/2/instances/[instance_name]/reboot resource.
 
   Implements an instance reboot.
 
   """
-  def POST(self):
+  POST_OPCODE = opcodes.OpInstanceReboot
+
+  def GetPostOpInput(self):
     """Reboot an instance.
 
     The URI takes type=[hard|soft|full] and
     ignore_secondaries=[False|True] parameters.
 
     """
     """Reboot an instance.
 
     The URI takes type=[hard|soft|full] and
     ignore_secondaries=[False|True] parameters.
 
     """
-    instance_name = self.items[0]
-    reboot_type = self.queryargs.get("type",
-                                     [constants.INSTANCE_REBOOT_HARD])[0]
-    ignore_secondaries = bool(self._checkIntVariable("ignore_secondaries"))
-    op = opcodes.OpInstanceReboot(instance_name=instance_name,
-                                  reboot_type=reboot_type,
-                                  ignore_secondaries=ignore_secondaries,
-                                  dry_run=bool(self.dryRun()))
-
-    return self.SubmitJob([op])
+    return ({}, {
+      "instance_name": self.items[0],
+      "reboot_type":
+        self.queryargs.get("type", [constants.INSTANCE_REBOOT_HARD])[0],
+      "ignore_secondaries": bool(self._checkIntVariable("ignore_secondaries")),
+      "dry_run": self.dryRun(),
+      })
 
 
 
 
-class R_2_instances_name_startup(baserlib.ResourceBase):
+class R_2_instances_name_startup(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/startup resource.
 
   Implements an instance startup.
 
   """
   """/2/instances/[instance_name]/startup resource.
 
   Implements an instance startup.
 
   """
-  def PUT(self):
+  PUT_OPCODE = opcodes.OpInstanceStartup
+
+  def GetPutOpInput(self):
     """Startup an instance.
 
     The URI takes force=[False|True] parameter to start the instance
     if even if secondary disks are failing.
 
     """
     """Startup an instance.
 
     The URI takes force=[False|True] parameter to start the instance
     if even if secondary disks are failing.
 
     """
-    instance_name = self.items[0]
-    force_startup = bool(self._checkIntVariable("force"))
-    no_remember = bool(self._checkIntVariable("no_remember"))
-    op = opcodes.OpInstanceStartup(instance_name=instance_name,
-                                   force=force_startup,
-                                   dry_run=bool(self.dryRun()),
-                                   no_remember=no_remember)
-
-    return self.SubmitJob([op])
-
-
-def _ParseShutdownInstanceRequest(name, data, dry_run, no_remember):
-  """Parses a request for an instance shutdown.
-
-  @rtype: L{opcodes.OpInstanceShutdown}
-  @return: Instance shutdown opcode
-
-  """
-  return baserlib.FillOpcode(opcodes.OpInstanceShutdown, data, {
-    "instance_name": name,
-    "dry_run": dry_run,
-    "no_remember": no_remember,
-    })
+    return ({}, {
+      "instance_name": self.items[0],
+      "force": self.useForce(),
+      "dry_run": self.dryRun(),
+      "no_remember": bool(self._checkIntVariable("no_remember")),
+      })
 
 
 
 
-class R_2_instances_name_shutdown(baserlib.ResourceBase):
+class R_2_instances_name_shutdown(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/shutdown resource.
 
   Implements an instance shutdown.
 
   """
   """/2/instances/[instance_name]/shutdown resource.
 
   Implements an instance shutdown.
 
   """
-  def PUT(self):
-    """Shutdown an instance.
+  PUT_OPCODE = opcodes.OpInstanceShutdown
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Shutdown an instance.
 
     """
 
     """
-    baserlib.CheckType(self.request_body, dict, "Body contents")
-
-    no_remember = bool(self._checkIntVariable("no_remember"))
-    op = _ParseShutdownInstanceRequest(self.items[0], self.request_body,
-                                       bool(self.dryRun()), no_remember)
-
-    return self.SubmitJob([op])
+    return (self.request_body, {
+      "instance_name": self.items[0],
+      "no_remember": bool(self._checkIntVariable("no_remember")),
+      "dry_run": self.dryRun(),
+      })
 
 
 def _ParseInstanceReinstallRequest(name, data):
 
 
 def _ParseInstanceReinstallRequest(name, data):
@@ -903,12 +931,14 @@ def _ParseInstanceReinstallRequest(name, data):
   return ops
 
 
   return ops
 
 
-class R_2_instances_name_reinstall(baserlib.ResourceBase):
+class R_2_instances_name_reinstall(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/reinstall resource.
 
   Implements an instance reinstall.
 
   """
   """/2/instances/[instance_name]/reinstall resource.
 
   Implements an instance reinstall.
 
   """
+  POST_OPCODE = opcodes.OpInstanceReinstall
+
   def POST(self):
     """Reinstall an instance.
 
   def POST(self):
     """Reinstall an instance.
 
@@ -936,264 +966,216 @@ class R_2_instances_name_reinstall(baserlib.ResourceBase):
     return self.SubmitJob(ops)
 
 
     return self.SubmitJob(ops)
 
 
-def _ParseInstanceReplaceDisksRequest(name, data):
-  """Parses a request for an instance export.
-
-  @rtype: L{opcodes.OpInstanceReplaceDisks}
-  @return: Instance export opcode
-
-  """
-  override = {
-    "instance_name": name,
-    }
-
-  # Parse disks
-  try:
-    raw_disks = data["disks"]
-  except KeyError:
-    pass
-  else:
-    if not ht.TListOf(ht.TInt)(raw_disks): # pylint: disable-msg=E1102
-      # Backwards compatibility for strings of the format "1, 2, 3"
-      try:
-        data["disks"] = [int(part) for part in raw_disks.split(",")]
-      except (TypeError, ValueError), err:
-        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
-
-  return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override)
-
-
-class R_2_instances_name_replace_disks(baserlib.ResourceBase):
+class R_2_instances_name_replace_disks(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/replace-disks resource.
 
   """
   """/2/instances/[instance_name]/replace-disks resource.
 
   """
-  def POST(self):
+  POST_OPCODE = opcodes.OpInstanceReplaceDisks
+
+  def GetPostOpInput(self):
     """Replaces disks on an instance.
 
     """
     """Replaces disks on an instance.
 
     """
-    op = _ParseInstanceReplaceDisksRequest(self.items[0], self.request_body)
+    static = {
+      "instance_name": self.items[0],
+      }
 
 
-    return self.SubmitJob([op])
+    if self.request_body:
+      data = self.request_body
+    elif self.queryargs:
+      # Legacy interface, do not modify/extend
+      data = {
+        "remote_node": self._checkStringVariable("remote_node", default=None),
+        "mode": self._checkStringVariable("mode", default=None),
+        "disks": self._checkStringVariable("disks", default=None),
+        "iallocator": self._checkStringVariable("iallocator", default=None),
+        }
+    else:
+      data = {}
+
+    # Parse disks
+    try:
+      raw_disks = data.pop("disks")
+    except KeyError:
+      pass
+    else:
+      if raw_disks:
+        if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
+          data["disks"] = raw_disks
+        else:
+          # Backwards compatibility for strings of the format "1, 2, 3"
+          try:
+            data["disks"] = [int(part) for part in raw_disks.split(",")]
+          except (TypeError, ValueError), err:
+            raise http.HttpBadRequest("Invalid disk index passed: %s" % err)
+
+    return (data, static)
 
 
 
 
-class R_2_instances_name_activate_disks(baserlib.ResourceBase):
+class R_2_instances_name_activate_disks(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/activate-disks resource.
 
   """
   """/2/instances/[instance_name]/activate-disks resource.
 
   """
-  def PUT(self):
+  PUT_OPCODE = opcodes.OpInstanceActivateDisks
+
+  def GetPutOpInput(self):
     """Activate disks for an instance.
 
     The URI might contain ignore_size to ignore current recorded size.
 
     """
     """Activate disks for an instance.
 
     The URI might contain ignore_size to ignore current recorded size.
 
     """
-    instance_name = self.items[0]
-    ignore_size = bool(self._checkIntVariable("ignore_size"))
-
-    op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
-                                         ignore_size=ignore_size)
-
-    return self.SubmitJob([op])
+    return ({}, {
+      "instance_name": self.items[0],
+      "ignore_size": bool(self._checkIntVariable("ignore_size")),
+      })
 
 
 
 
-class R_2_instances_name_deactivate_disks(baserlib.ResourceBase):
+class R_2_instances_name_deactivate_disks(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/deactivate-disks resource.
 
   """
   """/2/instances/[instance_name]/deactivate-disks resource.
 
   """
-  def PUT(self):
+  PUT_OPCODE = opcodes.OpInstanceDeactivateDisks
+
+  def GetPutOpInput(self):
     """Deactivate disks for an instance.
 
     """
     """Deactivate disks for an instance.
 
     """
-    instance_name = self.items[0]
-
-    op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name)
-
-    return self.SubmitJob([op])
+    return ({}, {
+      "instance_name": self.items[0],
+      })
 
 
 
 
-class R_2_instances_name_prepare_export(baserlib.ResourceBase):
-  """/2/instances/[instance_name]/prepare-export resource.
+class R_2_instances_name_recreate_disks(baserlib.OpcodeResource):
+  """/2/instances/[instance_name]/recreate-disks resource.
 
   """
 
   """
-  def PUT(self):
-    """Prepares an export for an instance.
+  POST_OPCODE = opcodes.OpInstanceRecreateDisks
 
 
-    @return: a job id
+  def GetPostOpInput(self):
+    """Recreate disks for an instance.
 
     """
 
     """
-    instance_name = self.items[0]
-    mode = self._checkStringVariable("mode")
-
-    op = opcodes.OpBackupPrepare(instance_name=instance_name,
-                                 mode=mode)
-
-    return self.SubmitJob([op])
-
+    return ({}, {
+      "instance_name": self.items[0],
+      })
 
 
-def _ParseExportInstanceRequest(name, data):
-  """Parses a request for an instance export.
 
 
-  @rtype: L{opcodes.OpBackupExport}
-  @return: Instance export opcode
+class R_2_instances_name_prepare_export(baserlib.OpcodeResource):
+  """/2/instances/[instance_name]/prepare-export resource.
 
   """
 
   """
-  # Rename "destination" to "target_node"
-  try:
-    data["target_node"] = data.pop("destination")
-  except KeyError:
-    pass
+  PUT_OPCODE = opcodes.OpBackupPrepare
 
 
-  return baserlib.FillOpcode(opcodes.OpBackupExport, data, {
-    "instance_name": name,
-    })
+  def GetPutOpInput(self):
+    """Prepares an export for an instance.
+
+    """
+    return ({}, {
+      "instance_name": self.items[0],
+      "mode": self._checkStringVariable("mode"),
+      })
 
 
 
 
-class R_2_instances_name_export(baserlib.ResourceBase):
+class R_2_instances_name_export(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/export resource.
 
   """
   """/2/instances/[instance_name]/export resource.
 
   """
-  def PUT(self):
-    """Exports an instance.
+  PUT_OPCODE = opcodes.OpBackupExport
+  PUT_RENAME = {
+    "destination": "target_node",
+    }
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Exports an instance.
 
     """
 
     """
-    if not isinstance(self.request_body, dict):
-      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
-
-    op = _ParseExportInstanceRequest(self.items[0], self.request_body)
-
-    return self.SubmitJob([op])
-
-
-def _ParseMigrateInstanceRequest(name, data):
-  """Parses a request for an instance migration.
-
-  @rtype: L{opcodes.OpInstanceMigrate}
-  @return: Instance migration opcode
-
-  """
-  return baserlib.FillOpcode(opcodes.OpInstanceMigrate, data, {
-    "instance_name": name,
-    })
+    return (self.request_body, {
+      "instance_name": self.items[0],
+      })
 
 
 
 
-class R_2_instances_name_migrate(baserlib.ResourceBase):
+class R_2_instances_name_migrate(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/migrate resource.
 
   """
   """/2/instances/[instance_name]/migrate resource.
 
   """
-  def PUT(self):
-    """Migrates an instance.
+  PUT_OPCODE = opcodes.OpInstanceMigrate
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Migrates an instance.
 
     """
 
     """
-    baserlib.CheckType(self.request_body, dict, "Body contents")
-
-    op = _ParseMigrateInstanceRequest(self.items[0], self.request_body)
-
-    return self.SubmitJob([op])
+    return (self.request_body, {
+      "instance_name": self.items[0],
+      })
 
 
 
 
-class R_2_instances_name_failover(baserlib.ResourceBase):
+class R_2_instances_name_failover(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/failover resource.
 
   """
   """/2/instances/[instance_name]/failover resource.
 
   """
-  def PUT(self):
-    """Does a failover of an instance.
+  PUT_OPCODE = opcodes.OpInstanceFailover
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Does a failover of an instance.
 
     """
 
     """
-    baserlib.CheckType(self.request_body, dict, "Body contents")
-
-    op = baserlib.FillOpcode(opcodes.OpInstanceFailover, self.request_body, {
+    return (self.request_body, {
       "instance_name": self.items[0],
       })
 
       "instance_name": self.items[0],
       })
 
-    return self.SubmitJob([op])
 
 
-
-def _ParseRenameInstanceRequest(name, data):
-  """Parses a request for renaming an instance.
-
-  @rtype: L{opcodes.OpInstanceRename}
-  @return: Instance rename opcode
-
-  """
-  return baserlib.FillOpcode(opcodes.OpInstanceRename, data, {
-    "instance_name": name,
-    })
-
-
-class R_2_instances_name_rename(baserlib.ResourceBase):
+class R_2_instances_name_rename(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/rename resource.
 
   """
   """/2/instances/[instance_name]/rename resource.
 
   """
-  def PUT(self):
-    """Changes the name of an instance.
+  PUT_OPCODE = opcodes.OpInstanceRename
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Changes the name of an instance.
 
     """
 
     """
-    baserlib.CheckType(self.request_body, dict, "Body contents")
-
-    op = _ParseRenameInstanceRequest(self.items[0], self.request_body)
-
-    return self.SubmitJob([op])
-
-
-def _ParseModifyInstanceRequest(name, data):
-  """Parses a request for modifying an instance.
-
-  @rtype: L{opcodes.OpInstanceSetParams}
-  @return: Instance modify opcode
-
-  """
-  return baserlib.FillOpcode(opcodes.OpInstanceSetParams, data, {
-    "instance_name": name,
-    })
+    return (self.request_body, {
+      "instance_name": self.items[0],
+      })
 
 
 
 
-class R_2_instances_name_modify(baserlib.ResourceBase):
+class R_2_instances_name_modify(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/modify resource.
 
   """
   """/2/instances/[instance_name]/modify resource.
 
   """
-  def PUT(self):
-    """Changes some parameters of an instance.
+  PUT_OPCODE = opcodes.OpInstanceSetParams
 
 
-    @return: a job id
+  def GetPutOpInput(self):
+    """Changes parameters of an instance.
 
     """
 
     """
-    baserlib.CheckType(self.request_body, dict, "Body contents")
-
-    op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
-
-    return self.SubmitJob([op])
+    return (self.request_body, {
+      "instance_name": self.items[0],
+      })
 
 
 
 
-class R_2_instances_name_disk_grow(baserlib.ResourceBase):
+class R_2_instances_name_disk_grow(baserlib.OpcodeResource):
   """/2/instances/[instance_name]/disk/[disk_index]/grow resource.
 
   """
   """/2/instances/[instance_name]/disk/[disk_index]/grow resource.
 
   """
-  def POST(self):
-    """Increases the size of an instance disk.
+  POST_OPCODE = opcodes.OpInstanceGrowDisk
 
 
-    @return: a job id
+  def GetPostOpInput(self):
+    """Increases the size of an instance disk.
 
     """
 
     """
-    op = baserlib.FillOpcode(opcodes.OpInstanceGrowDisk, self.request_body, {
+    return (self.request_body, {
       "instance_name": self.items[0],
       "disk": int(self.items[1]),
       })
 
       "instance_name": self.items[0],
       "disk": int(self.items[1]),
       })
 
-    return self.SubmitJob([op])
-
 
 class R_2_instances_name_console(baserlib.ResourceBase):
   """/2/instances/[instance_name]/console resource.
 
   """
   GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
 
 class R_2_instances_name_console(baserlib.ResourceBase):
   """/2/instances/[instance_name]/console resource.
 
   """
   GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
+  GET_OPCODE = opcodes.OpInstanceConsole
 
   def GET(self):
     """Request information for connecting to instance's console.
 
   def GET(self):
     """Request information for connecting to instance's console.
@@ -1238,9 +1220,11 @@ class R_2_query(baserlib.ResourceBase):
   """
   # Results might contain sensitive information
   GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
   """
   # Results might contain sensitive information
   GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
+  GET_OPCODE = opcodes.OpQuery
+  PUT_OPCODE = opcodes.OpQuery
 
 
-  def _Query(self, fields, filter_):
-    return self.GetClient().Query(self.items[0], fields, filter_).ToDict()
+  def _Query(self, fields, qfilter):
+    return self.GetClient().Query(self.items[0], fields, qfilter).ToDict()
 
   def GET(self):
     """Returns resource information.
 
   def GET(self):
     """Returns resource information.
@@ -1265,13 +1249,20 @@ class R_2_query(baserlib.ResourceBase):
     except KeyError:
       fields = _GetQueryFields(self.queryargs)
 
     except KeyError:
       fields = _GetQueryFields(self.queryargs)
 
-    return self._Query(fields, self.request_body.get("filter", None))
+    qfilter = body.get("qfilter", None)
+    # TODO: remove this after 2.7
+    if qfilter is None:
+      qfilter = body.get("filter", None)
+
+    return self._Query(fields, qfilter)
 
 
 class R_2_query_fields(baserlib.ResourceBase):
   """/2/query/[resource]/fields resource.
 
   """
 
 
 class R_2_query_fields(baserlib.ResourceBase):
   """/2/query/[resource]/fields resource.
 
   """
+  GET_OPCODE = opcodes.OpQueryFields
+
   def GET(self):
     """Retrieves list of available fields for a resource.
 
   def GET(self):
     """Retrieves list of available fields for a resource.
 
@@ -1288,7 +1279,7 @@ class R_2_query_fields(baserlib.ResourceBase):
     return self.GetClient().QueryFields(self.items[0], fields).ToDict()
 
 
     return self.GetClient().QueryFields(self.items[0], fields).ToDict()
 
 
-class _R_Tags(baserlib.ResourceBase):
+class _R_Tags(baserlib.OpcodeResource):
   """ Quasiclass for tagging resources
 
   Manages tags. When inheriting this class you must define the
   """ Quasiclass for tagging resources
 
   Manages tags. When inheriting this class you must define the
@@ -1296,14 +1287,17 @@ class _R_Tags(baserlib.ResourceBase):
 
   """
   TAG_LEVEL = None
 
   """
   TAG_LEVEL = None
+  GET_OPCODE = opcodes.OpTagsGet
+  PUT_OPCODE = opcodes.OpTagsSet
+  DELETE_OPCODE = opcodes.OpTagsDel
 
 
-  def __init__(self, items, queryargs, req):
+  def __init__(self, items, queryargs, req, **kwargs):
     """A tag resource constructor.
 
     We have to override the default to sort out cluster naming case.
 
     """
     """A tag resource constructor.
 
     We have to override the default to sort out cluster naming case.
 
     """
-    baserlib.ResourceBase.__init__(self, items, queryargs, req)
+    baserlib.OpcodeResource.__init__(self, items, queryargs, req, **kwargs)
 
     if self.TAG_LEVEL == constants.TAG_CLUSTER:
       self.name = None
 
     if self.TAG_LEVEL == constants.TAG_CLUSTER:
       self.name = None
@@ -1344,22 +1338,21 @@ class _R_Tags(baserlib.ResourceBase):
 
     return list(tags)
 
 
     return list(tags)
 
-  def PUT(self):
+  def GetPutOpInput(self):
     """Add a set of tags.
 
     The request as a list of strings should be PUT to this URI. And
     you'll have back a job id.
 
     """
     """Add a set of tags.
 
     The request as a list of strings should be PUT to this URI. And
     you'll have back a job id.
 
     """
-    # pylint: disable-msg=W0212
-    if "tag" not in self.queryargs:
-      raise http.HttpBadRequest("Please specify tag(s) to add using the"
-                                " the 'tag' parameter")
-    op = opcodes.OpTagsSet(kind=self.TAG_LEVEL, name=self.name,
-                           tags=self.queryargs["tag"], dry_run=self.dryRun())
-    return self.SubmitJob([op])
+    return ({}, {
+      "kind": self.TAG_LEVEL,
+      "name": self.name,
+      "tags": self.queryargs.get("tag", []),
+      "dry_run": self.dryRun(),
+      })
 
 
-  def DELETE(self):
+  def GetDeleteOpInput(self):
     """Delete a tag.
 
     In order to delete a set of tags, the DELETE
     """Delete a tag.
 
     In order to delete a set of tags, the DELETE
@@ -1367,14 +1360,8 @@ class _R_Tags(baserlib.ResourceBase):
     /tags?tag=[tag]&tag=[tag]
 
     """
     /tags?tag=[tag]&tag=[tag]
 
     """
-    # pylint: disable-msg=W0212
-    if "tag" not in self.queryargs:
-      # no we not gonna delete all tags
-      raise http.HttpBadRequest("Cannot delete all tags - please specify"
-                                " tag(s) using the 'tag' parameter")
-    op = opcodes.OpTagsDel(kind=self.TAG_LEVEL, name=self.name,
-                           tags=self.queryargs["tag"], dry_run=self.dryRun())
-    return self.SubmitJob([op])
+    # Re-use code
+    return self.GetPutOpInput()
 
 
 class R_2_instances_name_tags(_R_Tags):
 
 
 class R_2_instances_name_tags(_R_Tags):