commit 3882937a7015bd4e66b912d4d9af1f46e3dbb27b
Author: Michael Hanselmann <hansmi@google.com>
Date:   Tue Aug 17 18:50:11 2010 +0200

    RAPI: Allow modifying instance
    
    Signed-off-by: Michael Hanselmann <hansmi@google.com>
    Reviewed-by: Iustin Pop <iustin@google.com>

diff --git a/doc/rapi.rst b/doc/rapi.rst
index 38e7e59..fcf955a 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -671,6 +671,45 @@ Body parameters:
   Whether to ensure instance's name is resolvable.
 
 
+``/2/instances/[instance_name]/modify``
+++++++++++++++++++++++++++++++++++++++++
+
+Modifies an instance.
+
+Supports the following commands: ``PUT``.
+
+``PUT``
+~~~~~~~
+
+Returns a job ID.
+
+Body parameters:
+
+``osparams`` (dict)
+  Dictionary with OS parameters.
+``hvparams`` (dict)
+  Hypervisor parameters, hypervisor-dependent.
+``beparams`` (dict)
+  Backend parameters.
+``force`` (bool)
+  Whether to force the operation.
+``nics`` (list)
+  List of NIC changes. Each item is of the form ``(op, settings)``.
+  ``op`` can be ``add`` to add a new NIC with the specified settings,
+  ``remove`` to remove the last NIC or a number to modify the settings
+  of the NIC with that index.
+``disks`` (list)
+  List of disk changes. See ``nics``.
+``disk_template`` (string)
+  Disk template for instance.
+``remote_node`` (string)
+  Secondary node (used when changing disk template).
+``os_name`` (string)
+  Change instance's OS name. Does not reinstall the instance.
+``force_variant`` (bool)
+  Whether to force an unknown variant.
+
+
 ``/2/instances/[instance_name]/tags``
 +++++++++++++++++++++++++++++++++++++
 
diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py
index b865e74..648aa0b 100644
--- a/lib/rapi/connector.py
+++ b/lib/rapi/connector.py
@@ -217,6 +217,8 @@ def GetHandlers(node_name_pattern, instance_name_pattern, job_id_pattern):
       rlib2.R_2_instances_name_migrate,
     re.compile(r'^/2/instances/(%s)/rename$' % instance_name_pattern):
       rlib2.R_2_instances_name_rename,
+    re.compile(r'^/2/instances/(%s)/modify$' % instance_name_pattern):
+      rlib2.R_2_instances_name_modify,
 
     "/2/jobs": rlib2.R_2_jobs,
     re.compile(r"^/2/jobs/(%s)$" % job_id_pattern):
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index cacf7d8..b7e9b9b 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -1055,6 +1055,56 @@ class R_2_instances_name_rename(baserlib.R_Generic):
     return baserlib.SubmitJob([op])
 
 
+def _ParseModifyInstanceRequest(name, data):
+  """Parses a request for modifying an instance.
+
+  @rtype: L{opcodes.OpSetInstanceParams}
+  @return: Instance modify opcode
+
+  """
+  osparams = baserlib.CheckParameter(data, "osparams", default={})
+  force = baserlib.CheckParameter(data, "force", default=False)
+  nics = baserlib.CheckParameter(data, "nics", default=[])
+  disks = baserlib.CheckParameter(data, "disks", default=[])
+  disk_template = baserlib.CheckParameter(data, "disk_template", default=None)
+  remote_node = baserlib.CheckParameter(data, "remote_node", default=None)
+  os_name = baserlib.CheckParameter(data, "os_name", default=None)
+  force_variant = baserlib.CheckParameter(data, "force_variant", default=False)
+
+  # HV/BE parameters
+  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
+  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES,
+                      allowed_values=[constants.VALUE_DEFAULT])
+
+  beparams = baserlib.CheckParameter(data, "beparams", default={})
+  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES,
+                      allowed_values=[constants.VALUE_DEFAULT])
+
+  return opcodes.OpSetInstanceParams(instance_name=name, hvparams=hvparams,
+                                     beparams=beparams, osparams=osparams,
+                                     force=force, nics=nics, disks=disks,
+                                     disk_template=disk_template,
+                                     remote_node=remote_node, os_name=os_name,
+                                     force_variant=force_variant)
+
+
+class R_2_instances_name_modify(baserlib.R_Generic):
+  """/2/instances/[instance_name]/modify resource.
+
+  """
+  def PUT(self):
+    """Changes some parameters of an instance.
+
+    @return: a job id
+
+    """
+    baserlib.CheckType(self.request_body, dict, "Body contents")
+
+    op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
+
+    return baserlib.SubmitJob([op])
+
+
 class _R_Tags(baserlib.R_Generic):
   """ Quasiclass for tagging resources
 
diff --git a/test/ganeti.rapi.rlib2_unittest.py b/test/ganeti.rapi.rlib2_unittest.py
index 632001d..e9effb8 100755
--- a/test/ganeti.rapi.rlib2_unittest.py
+++ b/test/ganeti.rapi.rlib2_unittest.py
@@ -295,5 +295,68 @@ class TestParseRenameInstanceRequest(testutils.GanetiTestCase):
       self.assert_(op.name_check)
 
 
+class TestParseModifyInstanceRequest(testutils.GanetiTestCase):
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+
+    self.Parse = rlib2._ParseModifyInstanceRequest
+
+  def test(self):
+    name = "instush8gah"
+
+    test_disks = [
+      [],
+      [(1, { constants.IDISK_MODE: constants.DISK_RDWR, })],
+      ]
+
+    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 force in [False, True]:
+            for nics in [[], [(0, { constants.INIC_IP: "192.0.2.1", })]]:
+              for disks in test_disks:
+                for disk_template in constants.DISK_TEMPLATES:
+                  data = {
+                    "osparams": osparams,
+                    "hvparams": hvparams,
+                    "beparams": beparams,
+                    "nics": nics,
+                    "disks": disks,
+                    "force": force,
+                    "disk_template": disk_template,
+                    }
+
+                  op = self.Parse(name, data)
+                  self.assert_(isinstance(op, opcodes.OpSetInstanceParams))
+                  self.assertEqual(op.instance_name, name)
+                  self.assertEqual(op.hvparams, hvparams)
+                  self.assertEqual(op.beparams, beparams)
+                  self.assertEqual(op.osparams, osparams)
+                  self.assertEqual(op.force, force)
+                  self.assertEqual(op.nics, nics)
+                  self.assertEqual(op.disks, disks)
+                  self.assertEqual(op.disk_template, disk_template)
+                  self.assert_(op.remote_node is None)
+                  self.assert_(op.os_name is None)
+                  self.assertFalse(op.force_variant)
+
+  def testDefaults(self):
+    name = "instir8aish31"
+
+    op = self.Parse(name, {})
+    self.assert_(isinstance(op, opcodes.OpSetInstanceParams))
+    self.assertEqual(op.instance_name, name)
+    self.assertEqual(op.hvparams, {})
+    self.assertEqual(op.beparams, {})
+    self.assertEqual(op.osparams, {})
+    self.assertFalse(op.force)
+    self.assertEqual(op.nics, [])
+    self.assertEqual(op.disks, [])
+    self.assert_(op.disk_template is None)
+    self.assert_(op.remote_node is None)
+    self.assert_(op.os_name is None)
+    self.assertFalse(op.force_variant)
+
+
 if __name__ == '__main__':
   testutils.GanetiTestProgram()
