cmdlib: Remove all diskparams calculations not required anymore
[ganeti-local] / test / ganeti.rapi.rlib2_unittest.py
index 3bb154f..cde68ce 100755 (executable)
@@ -272,6 +272,26 @@ class TestNodeEvacuate(unittest.TestCase):
     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)
@@ -446,6 +466,26 @@ class TestInstanceDeactivateDisks(unittest.TestCase):
     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)
+
+
 class TestInstanceFailover(unittest.TestCase):
   def test(self):
     clfactory = _FakeClientFactory(_FakeClient)
@@ -515,13 +555,238 @@ class TestBackupPrepare(unittest.TestCase):
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
 
-class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
+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)),
+          }
 
-    self.Parse = rlib2._ParseInstanceCreateRequestVersion1
+        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
       [],
@@ -553,10 +818,13 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
       None,
       {},
       { constants.BE_VCPUS: 2, },
-      { constants.BE_MEMORY: 123, },
+      { constants.BE_MAXMEM: 200, },
+      { constants.BE_MEMORY: 256, },
       { constants.BE_VCPUS: 2,
-        constants.BE_MEMORY: 1024,
-        constants.BE_AUTO_BALANCE: True, }
+        constants.BE_MAXMEM: 1024,
+        constants.BE_MINMEM: 1024,
+        constants.BE_AUTO_BALANCE: True,
+        constants.BE_ALWAYS_FAILOVER: True, }
       ]
 
     hvparam_variants = [
@@ -572,25 +840,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)
@@ -619,34 +903,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": [],
@@ -654,27 +951,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": [],
@@ -683,9 +988,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"]:
@@ -695,7 +1002,46 @@ 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)
+
+    # 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)
 
 
 class TestBackupExport(unittest.TestCase):
@@ -905,7 +1251,7 @@ class TestParseModifyInstanceRequest(unittest.TestCase):
 
     for osparams in [{}, { "some": "value", "other": "Hello World", }]:
       for hvparams in [{}, { constants.HV_KERNEL_PATH: "/some/kernel", }]:
-        for beparams in [{}, { constants.BE_MEMORY: 128, }]:
+        for beparams in [{}, { constants.BE_MAXMEM: 128, }]:
           for force in [False, True]:
             for nics in [[], [(0, { constants.INIC_IP: "192.0.2.1", })]]:
               for disks in test_disks:
@@ -1017,6 +1363,10 @@ 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 TestGroupRename(unittest.TestCase):
   def test(self):
@@ -1124,6 +1474,20 @@ class TestInstanceReplaceDisks(unittest.TestCase):
     self.assertFalse(hasattr(op, "disks"))
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
+  def testNoDisks(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    handler = _CreateHandler(rlib2.R_2_instances_name_replace_disks,
+                             ["inst20661"], {}, {}, clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.POST)
+
+    for disks in [None, "", {}]:
+      handler = _CreateHandler(rlib2.R_2_instances_name_replace_disks,
+                               ["inst20661"], {}, {
+        "disks": disks,
+        }, clfactory)
+      self.assertRaises(http.HttpBadRequest, handler.POST)
+
   def testWrong(self):
     clfactory = _FakeClientFactory(_FakeClient)
 
@@ -1283,5 +1647,89 @@ class TestGroupAdd(unittest.TestCase):
     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__':
   testutils.GanetiTestProgram()