Merge branch 'devel-2.5'
[ganeti-local] / test / ganeti.rapi.rlib2_unittest.py
index 0f62046..88a20a0 100755 (executable)
@@ -175,13 +175,618 @@ class TestRedistConfig(unittest.TestCase):
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
 
-class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
+class TestNodeMigrate(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_nodes_name_migrate, ["node1"], {}, {
+      "iallocator": "fooalloc",
+      }, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpNodeMigrate))
+    self.assertEqual(op.node_name, "node1")
+    self.assertEqual(op.iallocator, "fooalloc")
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+  def testQueryArgsConflict(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_nodes_name_migrate, ["node2"], {
+      "live": True,
+      "mode": constants.HT_MIGRATION_NONLIVE,
+      }, None, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+  def testQueryArgsMode(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    queryargs = {
+      "mode": [constants.HT_MIGRATION_LIVE],
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_migrate, ["node17292"],
+                             queryargs, None, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpNodeMigrate))
+    self.assertEqual(op.node_name, "node17292")
+    self.assertEqual(op.mode, constants.HT_MIGRATION_LIVE)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+  def testQueryArgsLive(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    for live in [False, True]:
+      queryargs = {
+        "live": [str(int(live))],
+        }
+      handler = _CreateHandler(rlib2.R_2_nodes_name_migrate, ["node6940"],
+                               queryargs, None, clfactory)
+      job_id = handler.POST()
+
+      cl = clfactory.GetNextClient()
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+      (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+      self.assertEqual(job_id, exp_job_id)
+      self.assertTrue(isinstance(op, opcodes.OpNodeMigrate))
+      self.assertEqual(op.node_name, "node6940")
+      if live:
+        self.assertEqual(op.mode, constants.HT_MIGRATION_LIVE)
+      else:
+        self.assertEqual(op.mode, constants.HT_MIGRATION_NONLIVE)
+
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestNodeEvacuate(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_nodes_name_evacuate, ["node92"], {
+      "dry-run": ["1"],
+      }, {
+      "mode": constants.IALLOCATOR_NEVAC_SEC,
+      }, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpNodeEvacuate))
+    self.assertEqual(op.node_name, "node92")
+    self.assertEqual(op.mode, constants.IALLOCATOR_NEVAC_SEC)
+    self.assertTrue(op.dry_run)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestNodePowercycle(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_nodes_name_powercycle, ["node20744"], {
+      "force": ["1"],
+      }, None, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpNodePowercycle))
+    self.assertEqual(op.node_name, "node20744")
+    self.assertTrue(op.force)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestGroupAssignNodes(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_groups_name_assign_nodes, ["grp-a"], {
+      "dry-run": ["1"],
+      "force": ["1"],
+      }, {
+      "nodes": ["n2", "n3"],
+      }, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpGroupAssignNodes))
+    self.assertEqual(op.group_name, "grp-a")
+    self.assertEqual(op.nodes, ["n2", "n3"])
+    self.assertTrue(op.dry_run)
+    self.assertTrue(op.force)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceDelete(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name, ["inst30965"], {
+      "dry-run": ["1"],
+      }, {}, clfactory)
+    job_id = handler.DELETE()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceRemove))
+    self.assertEqual(op.instance_name, "inst30965")
+    self.assertTrue(op.dry_run)
+    self.assertFalse(op.ignore_failures)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceInfo(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_info, ["inst31217"], {
+      "static": ["1"],
+      }, {}, clfactory)
+    job_id = handler.GET()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceQueryData))
+    self.assertEqual(op.instances, ["inst31217"])
+    self.assertTrue(op.static)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceReboot(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_reboot, ["inst847"], {
+      "dry-run": ["1"],
+      "ignore_secondaries": ["1"],
+      }, {}, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceReboot))
+    self.assertEqual(op.instance_name, "inst847")
+    self.assertEqual(op.reboot_type, constants.INSTANCE_REBOOT_HARD)
+    self.assertTrue(op.ignore_secondaries)
+    self.assertTrue(op.dry_run)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceStartup(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_startup, ["inst31083"], {
+      "force": ["1"],
+      "no_remember": ["1"],
+      }, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceStartup))
+    self.assertEqual(op.instance_name, "inst31083")
+    self.assertTrue(op.no_remember)
+    self.assertTrue(op.force)
+    self.assertFalse(op.dry_run)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceShutdown(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_shutdown, ["inst26791"], {
+      "no_remember": ["0"],
+      }, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceShutdown))
+    self.assertEqual(op.instance_name, "inst26791")
+    self.assertFalse(op.no_remember)
+    self.assertFalse(op.dry_run)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceActivateDisks(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_activate_disks, ["xyz"], {
+      "ignore_size": ["1"],
+      }, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceActivateDisks))
+    self.assertEqual(op.instance_name, "xyz")
+    self.assertTrue(op.ignore_size)
+    self.assertFalse(hasattr(op, "dry_run"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceDeactivateDisks(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_deactivate_disks,
+                             ["inst22357"], {}, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceDeactivateDisks))
+    self.assertEqual(op.instance_name, "inst22357")
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceRecreateDisks(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_recreate_disks,
+                             ["inst22357"], {}, {}, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceRecreateDisks))
+    self.assertEqual(op.instance_name, "inst22357")
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
-    self.Parse = rlib2._ParseInstanceCreateRequestVersion1
 
+class TestInstanceFailover(unittest.TestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_instances_name_failover,
+                             ["inst12794"], {}, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceFailover))
+    self.assertEqual(op.instance_name, "inst12794")
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceDiskGrow(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    data = {
+      "amount": 1024,
+      }
+    handler = _CreateHandler(rlib2.R_2_instances_name_disk_grow,
+                             ["inst10742", "3"], {}, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceGrowDisk))
+    self.assertEqual(op.instance_name, "inst10742")
+    self.assertEqual(op.disk, 3)
+    self.assertEqual(op.amount, 1024)
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestBackupPrepare(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    queryargs = {
+      "mode": constants.EXPORT_MODE_REMOTE,
+      }
+    handler = _CreateHandler(rlib2.R_2_instances_name_prepare_export,
+                             ["inst17925"], queryargs, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpBackupPrepare))
+    self.assertEqual(op.instance_name, "inst17925")
+    self.assertEqual(op.mode, constants.EXPORT_MODE_REMOTE)
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestGroupRemove(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    handler = _CreateHandler(rlib2.R_2_groups_name,
+                             ["grp28575"], {}, {}, clfactory)
+    job_id = handler.DELETE()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpGroupRemove))
+    self.assertEqual(op.group_name, "grp28575")
+    self.assertFalse(op.dry_run)
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestStorageQuery(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    queryargs = {
+      "storage_type": constants.ST_LVM_PV,
+      "output_fields": "name,other",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage,
+                             ["node21075"], queryargs, {}, clfactory)
+    job_id = handler.GET()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpNodeQueryStorage))
+    self.assertEqual(op.nodes, ["node21075"])
+    self.assertEqual(op.storage_type, constants.ST_LVM_PV)
+    self.assertEqual(op.output_fields, ["name", "other"])
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+  def testErrors(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    queryargs = {
+      "output_fields": "name,other",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage,
+                             ["node10538"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.GET)
+
+    queryargs = {
+      "storage_type": constants.ST_LVM_VG,
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage,
+                             ["node21273"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.GET)
+
+    queryargs = {
+      "storage_type": "##unknown_storage##",
+      "output_fields": "name,other",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage,
+                             ["node10315"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.GET)
+
+
+class TestStorageModify(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    for allocatable in [None, "1", "0"]:
+      queryargs = {
+        "storage_type": constants.ST_LVM_VG,
+        "name": "pv-a",
+        }
+
+      if allocatable is not None:
+        queryargs["allocatable"] = allocatable
+
+      handler = _CreateHandler(rlib2.R_2_nodes_name_storage_modify,
+                               ["node9292"], queryargs, {}, clfactory)
+      job_id = handler.PUT()
+
+      cl = clfactory.GetNextClient()
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+      (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+      self.assertEqual(job_id, exp_job_id)
+      self.assertTrue(isinstance(op, opcodes.OpNodeModifyStorage))
+      self.assertEqual(op.node_name, "node9292")
+      self.assertEqual(op.storage_type, constants.ST_LVM_VG)
+      self.assertEqual(op.name, "pv-a")
+      if allocatable is None:
+        self.assertFalse(op.changes)
+      else:
+        assert allocatable in ("0", "1")
+        self.assertEqual(op.changes, {
+          constants.SF_ALLOCATABLE: (allocatable == "1"),
+          })
+      self.assertFalse(hasattr(op, "dry_run"))
+      self.assertFalse(hasattr(op, "force"))
+
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+  def testErrors(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    # No storage type
+    queryargs = {
+      "name": "xyz",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage_modify,
+                             ["node26016"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
+    # No name
+    queryargs = {
+      "storage_type": constants.ST_LVM_VG,
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage_modify,
+                             ["node21218"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
+    # Invalid value
+    queryargs = {
+      "storage_type": constants.ST_LVM_VG,
+      "name": "pv-b",
+      "allocatable": "noint",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage_modify,
+                             ["node30685"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
+
+class TestStorageRepair(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+    queryargs = {
+      "storage_type": constants.ST_LVM_PV,
+      "name": "pv16611",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage_repair,
+                             ["node19265"], queryargs, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpRepairNodeStorage))
+    self.assertEqual(op.node_name, "node19265")
+    self.assertEqual(op.storage_type, constants.ST_LVM_PV)
+    self.assertEqual(op.name, "pv16611")
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+  def testErrors(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    # No storage type
+    queryargs = {
+      "name": "xyz",
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage_repair,
+                             ["node11275"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
+    # No name
+    queryargs = {
+      "storage_type": constants.ST_LVM_VG,
+      }
+    handler = _CreateHandler(rlib2.R_2_nodes_name_storage_repair,
+                             ["node21218"], queryargs, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
+
+class TestTags(unittest.TestCase):
+  TAG_HANDLERS = [
+    rlib2.R_2_instances_name_tags,
+    rlib2.R_2_nodes_name_tags,
+    rlib2.R_2_groups_name_tags,
+    rlib2.R_2_tags,
+    ]
+
+  def testSetAndDelete(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    for method, opcls in [("PUT", opcodes.OpTagsSet),
+                          ("DELETE", opcodes.OpTagsDel)]:
+      for idx, handler in enumerate(self.TAG_HANDLERS):
+        dry_run = bool(idx % 2)
+        name = "test%s" % idx
+        queryargs = {
+          "tag": ["foo", "bar", "baz"],
+          "dry-run": str(int(dry_run)),
+          }
+
+        handler = _CreateHandler(handler, [name], queryargs, {}, clfactory)
+        job_id = getattr(handler, method)()
+
+        cl = clfactory.GetNextClient()
+        self.assertRaises(IndexError, clfactory.GetNextClient)
+
+        (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+        self.assertEqual(job_id, exp_job_id)
+        self.assertTrue(isinstance(op, opcls))
+        self.assertEqual(op.kind, handler.TAG_LEVEL)
+        if handler.TAG_LEVEL == constants.TAG_CLUSTER:
+          self.assertTrue(op.name is None)
+        else:
+          self.assertEqual(op.name, name)
+        self.assertEqual(op.tags, ["foo", "bar", "baz"])
+        self.assertEqual(op.dry_run, dry_run)
+        self.assertFalse(hasattr(op, "force"))
+
+        self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestInstanceCreation(testutils.GanetiTestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    name = "inst863.example.com"
+
     disk_variants = [
       # No disks
       [],
@@ -232,25 +837,41 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
           for disks in disk_variants:
             for beparams in beparam_variants:
               for hvparams in hvparam_variants:
-                data = {
-                  "name": "inst1.example.com",
-                  "hypervisor": constants.HT_FAKE,
-                  "disks": disks,
-                  "nics": nics,
-                  "mode": mode,
-                  "disk_template": disk_template,
-                  "os": "debootstrap",
-                  }
-
-                if beparams is not None:
-                  data["beparams"] = beparams
-
-                if hvparams is not None:
-                  data["hvparams"] = hvparams
-
                 for dry_run in [False, True]:
-                  op = self.Parse(data, dry_run)
-                  self.assert_(isinstance(op, opcodes.OpInstanceCreate))
+                  queryargs = {
+                    "dry-run": str(int(dry_run)),
+                    }
+
+                  data = {
+                    rlib2._REQ_DATA_VERSION: 1,
+                    "name": name,
+                    "hypervisor": constants.HT_FAKE,
+                    "disks": disks,
+                    "nics": nics,
+                    "mode": mode,
+                    "disk_template": disk_template,
+                    "os": "debootstrap",
+                    }
+
+                  if beparams is not None:
+                    data["beparams"] = beparams
+
+                  if hvparams is not None:
+                    data["hvparams"] = hvparams
+
+                  handler = _CreateHandler(rlib2.R_2_instances, [],
+                                           queryargs, data, clfactory)
+                  job_id = handler.POST()
+
+                  cl = clfactory.GetNextClient()
+                  self.assertRaises(IndexError, clfactory.GetNextClient)
+
+                  (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+                  self.assertEqual(job_id, exp_job_id)
+                  self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+                  self.assertTrue(isinstance(op, opcodes.OpInstanceCreate))
+                  self.assertEqual(op.instance_name, name)
                   self.assertEqual(op.mode, mode)
                   self.assertEqual(op.disk_template, disk_template)
                   self.assertEqual(op.dry_run, dry_run)
@@ -279,34 +900,47 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
                     self.assertEqualValues(op.hvparams, hvparams)
 
   def testLegacyName(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "inst29128.example.com"
     data = {
+      rlib2._REQ_DATA_VERSION: 1,
       "name": name,
       "disks": [],
       "nics": [],
       "mode": constants.INSTANCE_CREATE,
       "disk_template": constants.DT_PLAIN,
       }
-    op = self.Parse(data, False)
-    self.assert_(isinstance(op, opcodes.OpInstanceCreate))
+
+    handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceCreate))
     self.assertEqual(op.instance_name, name)
     self.assertFalse(hasattr(op, "name"))
+    self.assertFalse(op.dry_run)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
     # Define both
-    data = {
-      "name": name,
-      "instance_name": "other.example.com",
-      "disks": [],
-      "nics": [],
-      "mode": constants.INSTANCE_CREATE,
-      "disk_template": constants.DT_PLAIN,
-      }
-    self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
+    data["instance_name"] = "other.example.com"
+    assert "name" in data and "instance_name" in data
+    handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
+    self.assertRaises(IndexError, clfactory.GetNextClient)
 
   def testLegacyOs(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "inst4673.example.com"
     os = "linux29206"
     data = {
+      rlib2._REQ_DATA_VERSION: 1,
       "name": name,
       "os_type": os,
       "disks": [],
@@ -314,27 +948,35 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
       "mode": constants.INSTANCE_CREATE,
       "disk_template": constants.DT_PLAIN,
       }
-    op = self.Parse(data, False)
-    self.assert_(isinstance(op, opcodes.OpInstanceCreate))
+
+    handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceCreate))
     self.assertEqual(op.instance_name, name)
     self.assertEqual(op.os_type, os)
     self.assertFalse(hasattr(op, "os"))
+    self.assertFalse(op.dry_run)
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
     # Define both
-    data = {
-      "instance_name": name,
-      "os": os,
-      "os_type": "linux9584",
-      "disks": [],
-      "nics": [],
-      "mode": constants.INSTANCE_CREATE,
-      "disk_template": constants.DT_PLAIN,
-      }
-    self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
+    data["os"] = "linux9584"
+    assert "os" in data and "os_type" in data
+    handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
 
   def testErrors(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     # Test all required fields
     reqfields = {
+      rlib2._REQ_DATA_VERSION: 1,
       "name": "inst1.example.com",
       "disks": [],
       "nics": [],
@@ -343,9 +985,11 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
       }
 
     for name in reqfields.keys():
-      self.assertRaises(http.HttpBadRequest, self.Parse,
-                        dict(i for i in reqfields.iteritems() if i[0] != name),
-                        False)
+      data = dict(i for i in reqfields.iteritems() if i[0] != name)
+
+      handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+      self.assertRaises(http.HttpBadRequest, handler.POST)
+      self.assertRaises(IndexError, clfactory.GetNextClient)
 
     # Invalid disks and nics
     for field in ["disks", "nics"]:
@@ -355,16 +999,52 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
       for invvalue in invalid_values:
         data = reqfields.copy()
         data[field] = invvalue
-        self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
+        handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+        self.assertRaises(http.HttpBadRequest, handler.POST)
+        self.assertRaises(IndexError, clfactory.GetNextClient)
 
+  def testVersion(self):
+    clfactory = _FakeClientFactory(_FakeClient)
 
-class TestParseExportInstanceRequest(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
+    # No version field
+    data = {
+      "name": "inst1.example.com",
+      "disks": [],
+      "nics": [],
+      "mode": constants.INSTANCE_CREATE,
+      "disk_template": constants.DT_PLAIN,
+      }
+
+    handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
+
+    # Old and incorrect versions
+    for version in [0, -1, 10483, "Hello World"]:
+      data[rlib2._REQ_DATA_VERSION] = version
+
+      handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+      self.assertRaises(http.HttpBadRequest, handler.POST)
+
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    # Correct version
+    data[rlib2._REQ_DATA_VERSION] = 1
+    handler = _CreateHandler(rlib2.R_2_instances, [], {}, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceCreate))
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
-    self.Parse = rlib2._ParseExportInstanceRequest
 
+class TestBackupExport(unittest.TestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instmoo"
     data = {
       "mode": constants.EXPORT_MODE_REMOTE,
@@ -374,43 +1054,72 @@ class TestParseExportInstanceRequest(testutils.GanetiTestCase):
       "x509_key_name": ["name", "hash"],
       "destination_x509_ca": "---cert---"
       }
-    op = self.Parse(name, data)
-    self.assert_(isinstance(op, opcodes.OpBackupExport))
+
+    handler = _CreateHandler(rlib2.R_2_instances_name_export, [name], {},
+                             data, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpBackupExport))
     self.assertEqual(op.instance_name, name)
     self.assertEqual(op.mode, constants.EXPORT_MODE_REMOTE)
+    self.assertEqual(op.target_node, [(1, 2, 3), (99, 99, 99)])
     self.assertEqual(op.shutdown, True)
     self.assertEqual(op.remove_instance, True)
-    self.assertEqualValues(op.x509_key_name, ("name", "hash"))
+    self.assertEqual(op.x509_key_name, ["name", "hash"])
     self.assertEqual(op.destination_x509_ca, "---cert---")
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "inst1"
     data = {
       "destination": "node2",
       "shutdown": False,
       }
-    op = self.Parse(name, data)
-    self.assert_(isinstance(op, opcodes.OpBackupExport))
+
+    handler = _CreateHandler(rlib2.R_2_instances_name_export, [name], {},
+                             data, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpBackupExport))
     self.assertEqual(op.instance_name, name)
     self.assertEqual(op.target_node, "node2")
     self.assertFalse(hasattr(op, "mode"))
     self.assertFalse(hasattr(op, "remove_instance"))
     self.assertFalse(hasattr(op, "destination"))
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
 
-  def testErrors(self):
-    self.assertRaises(http.HttpBadRequest, self.Parse, "err1",
-                      { "remove_instance": "True", })
-    self.assertRaises(http.HttpBadRequest, self.Parse, "err1",
-                      { "remove_instance": "False", })
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
+  def testErrors(self):
+    clfactory = _FakeClientFactory(_FakeClient)
 
-class TestParseMigrateInstanceRequest(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
+    for value in ["True", "False"]:
+      handler = _CreateHandler(rlib2.R_2_instances_name_export, ["err1"], {}, {
+        "remove_instance": value,
+        }, clfactory)
+      self.assertRaises(http.HttpBadRequest, handler.PUT)
 
-    self.Parse = rlib2._ParseMigrateInstanceRequest
 
+class TestInstanceMigrate(testutils.GanetiTestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instYooho6ek"
 
     for cleanup in [False, True]:
@@ -419,29 +1128,53 @@ class TestParseMigrateInstanceRequest(testutils.GanetiTestCase):
           "cleanup": cleanup,
           "mode": mode,
           }
-        op = self.Parse(name, data)
-        self.assert_(isinstance(op, opcodes.OpInstanceMigrate))
+
+        handler = _CreateHandler(rlib2.R_2_instances_name_migrate, [name], {},
+                                 data, clfactory)
+        job_id = handler.PUT()
+
+        cl = clfactory.GetNextClient()
+        self.assertRaises(IndexError, clfactory.GetNextClient)
+
+        (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+        self.assertEqual(job_id, exp_job_id)
+        self.assertTrue(isinstance(op, opcodes.OpInstanceMigrate))
         self.assertEqual(op.instance_name, name)
         self.assertEqual(op.mode, mode)
         self.assertEqual(op.cleanup, cleanup)
+        self.assertFalse(hasattr(op, "dry_run"))
+        self.assertFalse(hasattr(op, "force"))
+
+        self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instnohZeex0"
 
-    op = self.Parse(name, {})
-    self.assert_(isinstance(op, opcodes.OpInstanceMigrate))
+    handler = _CreateHandler(rlib2.R_2_instances_name_migrate, [name], {}, {},
+                             clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceMigrate))
     self.assertEqual(op.instance_name, name)
     self.assertFalse(hasattr(op, "mode"))
     self.assertFalse(hasattr(op, "cleanup"))
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertFalse(hasattr(op, "force"))
 
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
-class TestParseRenameInstanceRequest(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-
-    self.Parse = rlib2._ParseRenameInstanceRequest
 
+class TestParseRenameInstanceRequest(testutils.GanetiTestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instij0eeph7"
 
     for new_name in ["ua0aiyoo", "fai3ongi"]:
@@ -453,14 +1186,28 @@ class TestParseRenameInstanceRequest(testutils.GanetiTestCase):
             "name_check": name_check,
             }
 
-          op = self.Parse(name, data)
-          self.assert_(isinstance(op, opcodes.OpInstanceRename))
+          handler = _CreateHandler(rlib2.R_2_instances_name_rename, [name],
+                                   {}, data, clfactory)
+          job_id = handler.PUT()
+
+          cl = clfactory.GetNextClient()
+          self.assertRaises(IndexError, clfactory.GetNextClient)
+
+          (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+          self.assertEqual(job_id, exp_job_id)
+          self.assertTrue(isinstance(op, opcodes.OpInstanceRename))
           self.assertEqual(op.instance_name, name)
           self.assertEqual(op.new_name, new_name)
           self.assertEqual(op.ip_check, ip_check)
           self.assertEqual(op.name_check, name_check)
+          self.assertFalse(hasattr(op, "dry_run"))
+          self.assertFalse(hasattr(op, "force"))
+
+          self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instahchie3t"
 
     for new_name in ["thag9mek", "quees7oh"]:
@@ -468,21 +1215,30 @@ class TestParseRenameInstanceRequest(testutils.GanetiTestCase):
         "new_name": new_name,
         }
 
-      op = self.Parse(name, data)
-      self.assert_(isinstance(op, opcodes.OpInstanceRename))
+      handler = _CreateHandler(rlib2.R_2_instances_name_rename, [name],
+                               {}, data, clfactory)
+      job_id = handler.PUT()
+
+      cl = clfactory.GetNextClient()
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+      (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+      self.assertEqual(job_id, exp_job_id)
+      self.assertTrue(isinstance(op, opcodes.OpInstanceRename))
       self.assertEqual(op.instance_name, name)
       self.assertEqual(op.new_name, new_name)
       self.assertFalse(hasattr(op, "ip_check"))
       self.assertFalse(hasattr(op, "name_check"))
+      self.assertFalse(hasattr(op, "dry_run"))
+      self.assertFalse(hasattr(op, "force"))
 
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
-class TestParseModifyInstanceRequest(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-
-    self.Parse = rlib2._ParseModifyInstanceRequest
 
+class TestParseModifyInstanceRequest(unittest.TestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instush8gah"
 
     test_disks = [
@@ -507,8 +1263,16 @@ class TestParseModifyInstanceRequest(testutils.GanetiTestCase):
                     "disk_template": disk_template,
                     }
 
-                  op = self.Parse(name, data)
-                  self.assert_(isinstance(op, opcodes.OpInstanceSetParams))
+                  handler = _CreateHandler(rlib2.R_2_instances_name_modify,
+                                           [name], {}, data, clfactory)
+                  job_id = handler.PUT()
+
+                  cl = clfactory.GetNextClient()
+                  self.assertRaises(IndexError, clfactory.GetNextClient)
+
+                  (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+                  self.assertEqual(job_id, exp_job_id)
+                  self.assertTrue(isinstance(op, opcodes.OpInstanceSetParams))
                   self.assertEqual(op.instance_name, name)
                   self.assertEqual(op.hvparams, hvparams)
                   self.assertEqual(op.beparams, beparams)
@@ -520,13 +1284,27 @@ class TestParseModifyInstanceRequest(testutils.GanetiTestCase):
                   self.assertFalse(hasattr(op, "remote_node"))
                   self.assertFalse(hasattr(op, "os_name"))
                   self.assertFalse(hasattr(op, "force_variant"))
+                  self.assertFalse(hasattr(op, "dry_run"))
+
+                  self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "instir8aish31"
 
-    op = self.Parse(name, {})
-    self.assert_(isinstance(op, opcodes.OpInstanceSetParams))
+    handler = _CreateHandler(rlib2.R_2_instances_name_modify,
+                             [name], {}, {}, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+    self.assertTrue(isinstance(op, opcodes.OpInstanceSetParams))
     self.assertEqual(op.instance_name, name)
+
     for i in ["hvparams", "beparams", "osparams", "force", "nics", "disks",
               "disk_template", "remote_node", "os_name", "force_variant"]:
       self.assertFalse(hasattr(op, i))
@@ -582,45 +1360,66 @@ class TestParseInstanceReinstallRequest(testutils.GanetiTestCase):
     self.assertEqual(ops[1].os_type, "linux1")
     self.assertFalse(ops[1].osparams)
 
+  def testErrors(self):
+    self.assertRaises(http.HttpBadRequest, self.Parse,
+                      "foo", "not a dictionary")
 
-class TestParseRenameGroupRequest(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-
-    self.Parse = rlib2._ParseRenameGroupRequest
 
+class TestGroupRename(unittest.TestCase):
   def test(self):
-    name = "instij0eeph7"
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    name = "group608242564"
     data = {
-      "new_name": "ua0aiyoo",
+      "new_name": "ua0aiyoo15112",
       }
 
-    op = self.Parse(name, data, False)
+    handler = _CreateHandler(rlib2.R_2_groups_name_rename, [name], {}, data,
+                             clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
 
-    self.assert_(isinstance(op, opcodes.OpGroupRename))
+    self.assertTrue(isinstance(op, opcodes.OpGroupRename))
     self.assertEqual(op.group_name, name)
-    self.assertEqual(op.new_name, "ua0aiyoo")
+    self.assertEqual(op.new_name, "ua0aiyoo15112")
     self.assertFalse(op.dry_run)
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testDryRun(self):
-    name = "instij0eeph7"
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    name = "group28548"
     data = {
       "new_name": "ua0aiyoo",
       }
 
-    op = self.Parse(name, data, True)
+    handler = _CreateHandler(rlib2.R_2_groups_name_rename, [name], {
+      "dry-run": ["1"],
+      }, data, clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
 
-    self.assert_(isinstance(op, opcodes.OpGroupRename))
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpGroupRename))
     self.assertEqual(op.group_name, name)
     self.assertEqual(op.new_name, "ua0aiyoo")
-    self.assert_(op.dry_run)
-
+    self.assertTrue(op.dry_run)
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
-class TestParseInstanceReplaceDisksRequest(unittest.TestCase):
-  def setUp(self):
-    self.Parse = rlib2._ParseInstanceReplaceDisksRequest
 
+class TestInstanceReplaceDisks(unittest.TestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "inst22568"
 
     for disks in [range(1, 4), "1,2,3", "1, 2, 3"]:
@@ -630,36 +1429,65 @@ class TestParseInstanceReplaceDisksRequest(unittest.TestCase):
         "iallocator": "myalloc",
         }
 
-      op = self.Parse(name, data)
-      self.assert_(isinstance(op, opcodes.OpInstanceReplaceDisks))
+      handler = _CreateHandler(rlib2.R_2_instances_name_replace_disks,
+                               [name], {}, data, clfactory)
+      job_id = handler.POST()
+
+      cl = clfactory.GetNextClient()
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+      (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+      self.assertEqual(job_id, exp_job_id)
+
+      self.assertTrue(isinstance(op, opcodes.OpInstanceReplaceDisks))
+      self.assertEqual(op.instance_name, name)
       self.assertEqual(op.mode, constants.REPLACE_DISK_SEC)
       self.assertEqual(op.disks, [1, 2, 3])
       self.assertEqual(op.iallocator, "myalloc")
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "inst11413"
     data = {
       "mode": constants.REPLACE_DISK_AUTO,
       }
 
-    op = self.Parse(name, data)
-    self.assert_(isinstance(op, opcodes.OpInstanceReplaceDisks))
+    handler = _CreateHandler(rlib2.R_2_instances_name_replace_disks,
+                             [name], {}, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpInstanceReplaceDisks))
+    self.assertEqual(op.instance_name, name)
     self.assertEqual(op.mode, constants.REPLACE_DISK_AUTO)
     self.assertFalse(hasattr(op, "iallocator"))
     self.assertFalse(hasattr(op, "disks"))
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testWrong(self):
-    self.assertRaises(http.HttpBadRequest, self.Parse, "inst",
-                      { "mode": constants.REPLACE_DISK_AUTO,
-                        "disks": "hello world",
-                      })
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    data = {
+      "mode": constants.REPLACE_DISK_AUTO,
+      "disks": "hello world",
+      }
 
+    handler = _CreateHandler(rlib2.R_2_instances_name_replace_disks,
+                             ["foo"], {}, data, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
 
-class TestParseModifyGroupRequest(unittest.TestCase):
-  def setUp(self):
-    self.Parse = rlib2._ParseModifyGroupRequest
 
+class TestGroupModify(unittest.TestCase):
   def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "group6002"
 
     for policy in constants.VALID_ALLOC_POLICIES:
@@ -667,34 +1495,60 @@ class TestParseModifyGroupRequest(unittest.TestCase):
         "alloc_policy": policy,
         }
 
-      op = self.Parse(name, data)
-      self.assert_(isinstance(op, opcodes.OpGroupSetParams))
+      handler = _CreateHandler(rlib2.R_2_groups_name_modify, [name], {}, data,
+                               clfactory)
+      job_id = handler.PUT()
+
+      cl = clfactory.GetNextClient()
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+      (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+      self.assertEqual(job_id, exp_job_id)
+
+      self.assertTrue(isinstance(op, opcodes.OpGroupSetParams))
       self.assertEqual(op.group_name, name)
       self.assertEqual(op.alloc_policy, policy)
+      self.assertFalse(hasattr(op, "dry_run"))
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testUnknownPolicy(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     data = {
       "alloc_policy": "_unknown_policy_",
       }
 
-    self.assertRaises(http.HttpBadRequest, self.Parse, "name", data)
+    handler = _CreateHandler(rlib2.R_2_groups_name_modify, ["xyz"], {}, data,
+                             clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+    self.assertRaises(IndexError, clfactory.GetNextClient)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "group6679"
-    data = {}
 
-    op = self.Parse(name, data)
-    self.assert_(isinstance(op, opcodes.OpGroupSetParams))
+    handler = _CreateHandler(rlib2.R_2_groups_name_modify, [name], {}, {},
+                             clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpGroupSetParams))
     self.assertEqual(op.group_name, name)
     self.assertFalse(hasattr(op, "alloc_policy"))
+    self.assertFalse(hasattr(op, "dry_run"))
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
 
-class TestParseCreateGroupRequest(unittest.TestCase):
-  def setUp(self):
-    self.Parse = rlib2._ParseCreateGroupRequest
-
+class TestGroupAdd(unittest.TestCase):
   def test(self):
     name = "group3618"
+    clfactory = _FakeClientFactory(_FakeClient)
 
     for policy in constants.VALID_ALLOC_POLICIES:
       data = {
@@ -702,40 +1556,162 @@ class TestParseCreateGroupRequest(unittest.TestCase):
         "alloc_policy": policy,
         }
 
-      op = self.Parse(data, False)
-      self.assert_(isinstance(op, opcodes.OpGroupAdd))
+      handler = _CreateHandler(rlib2.R_2_groups, [], {}, data,
+                               clfactory)
+      job_id = handler.POST()
+
+      cl = clfactory.GetNextClient()
+      self.assertRaises(IndexError, clfactory.GetNextClient)
+
+      (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+      self.assertEqual(job_id, exp_job_id)
+
+      self.assertTrue(isinstance(op, opcodes.OpGroupAdd))
       self.assertEqual(op.group_name, name)
       self.assertEqual(op.alloc_policy, policy)
       self.assertFalse(op.dry_run)
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
   def testUnknownPolicy(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     data = {
       "alloc_policy": "_unknown_policy_",
       }
 
-    self.assertRaises(http.HttpBadRequest, self.Parse, "name", data)
+    handler = _CreateHandler(rlib2.R_2_groups, [], {}, data, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
+    self.assertRaises(IndexError, clfactory.GetNextClient)
 
   def testDefaults(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "group15395"
     data = {
       "group_name": name,
       }
 
-    op = self.Parse(data, True)
-    self.assert_(isinstance(op, opcodes.OpGroupAdd))
+    handler = _CreateHandler(rlib2.R_2_groups, [], {}, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpGroupAdd))
     self.assertEqual(op.group_name, name)
     self.assertFalse(hasattr(op, "alloc_policy"))
-    self.assertTrue(op.dry_run)
+    self.assertFalse(op.dry_run)
 
   def testLegacyName(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
     name = "group29852"
     data = {
       "name": name,
       }
 
-    op = self.Parse(data, True)
-    self.assert_(isinstance(op, opcodes.OpGroupAdd))
+    handler = _CreateHandler(rlib2.R_2_groups, [], {
+      "dry-run": ["1"],
+      }, data, clfactory)
+    job_id = handler.POST()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpGroupAdd))
     self.assertEqual(op.group_name, name)
+    self.assertFalse(hasattr(op, "alloc_policy"))
+    self.assertTrue(op.dry_run)
+
+
+class TestNodeRole(unittest.TestCase):
+  def test(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    for role in rlib2._NR_MAP.values():
+      handler = _CreateHandler(rlib2.R_2_nodes_name_role,
+                               ["node-z"], {}, role, clfactory)
+      if role == rlib2._NR_MASTER:
+        self.assertRaises(http.HttpBadRequest, handler.PUT)
+      else:
+        job_id = handler.PUT()
+
+        cl = clfactory.GetNextClient()
+        self.assertRaises(IndexError, clfactory.GetNextClient)
+
+        (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+        self.assertEqual(job_id, exp_job_id)
+        self.assertTrue(isinstance(op, opcodes.OpNodeSetParams))
+        self.assertEqual(op.node_name, "node-z")
+        self.assertFalse(op.force)
+        self.assertFalse(hasattr(op, "dry_run"))
+
+        if role == rlib2._NR_REGULAR:
+          self.assertFalse(op.drained)
+          self.assertFalse(op.offline)
+          self.assertFalse(op.master_candidate)
+        elif role == rlib2._NR_MASTER_CANDIDATE:
+          self.assertFalse(op.drained)
+          self.assertFalse(op.offline)
+          self.assertTrue(op.master_candidate)
+        elif role == rlib2._NR_DRAINED:
+          self.assertTrue(op.drained)
+          self.assertFalse(op.offline)
+          self.assertFalse(op.master_candidate)
+        elif role == rlib2._NR_OFFLINE:
+          self.assertFalse(op.drained)
+          self.assertTrue(op.offline)
+          self.assertFalse(op.master_candidate)
+        else:
+          self.fail("Unknown role '%s'" % role)
+
+      self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+
+class TestSimpleResources(unittest.TestCase):
+  def setUp(self):
+    self.clfactory = _FakeClientFactory(_FakeClient)
+
+  def tearDown(self):
+    self.assertRaises(IndexError, self.clfactory.GetNextClient)
+
+  def testFeatures(self):
+    handler = _CreateHandler(rlib2.R_2_features, [], {}, None, self.clfactory)
+    self.assertEqual(set(handler.GET()), rlib2.ALL_FEATURES)
+
+  def testEmpty(self):
+    for cls in [rlib2.R_root, rlib2.R_2]:
+      handler = _CreateHandler(cls, [], {}, None, self.clfactory)
+      self.assertTrue(handler.GET() is None)
+
+  def testVersion(self):
+    handler = _CreateHandler(rlib2.R_version, [], {}, None, self.clfactory)
+    self.assertEqual(handler.GET(), constants.RAPI_VERSION)
+
+
+class TestClusterInfo(unittest.TestCase):
+  class _ClusterInfoClient:
+    def __init__(self):
+      self.cluster_info = None
+
+    def QueryClusterInfo(self):
+      assert self.cluster_info is None
+      self.cluster_info = object()
+      return self.cluster_info
+
+  def test(self):
+    clfactory = _FakeClientFactory(self._ClusterInfoClient)
+    handler = _CreateHandler(rlib2.R_2_info, [], {}, None, clfactory)
+    result = handler.GET()
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+    self.assertEqual(result, cl.cluster_info)
 
 
 if __name__ == '__main__':