commit 1fa6fcba45167b32bad0e4958ce9589a9bfc8649
Author: Michele Tartara <mtartara@google.com>
Date:   Tue Apr 23 13:38:44 2013 +0200

    Reason trail implementation for "start"
    
    Opcode-specific implementation of the reason trail for the instance
    startup operation.
    
    Signed-off-by: Michele Tartara <mtartara@google.com>
    Reviewed-by: Guido Trotter <ultrotter@google.com>

diff --git a/lib/backend.py b/lib/backend.py
index b2924c0..68fd393 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -1328,13 +1328,17 @@ def _GatherAndLinkBlockDevs(instance):
   return block_devices
 
 
-def StartInstance(instance, startup_paused):
+def StartInstance(instance, startup_paused, reason, store_reason=True):
   """Start an instance.
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
   @type startup_paused: bool
   @param instance: pause instance at startup?
+  @type reason: list of reasons
+  @param reason: the reason trail for this startup
+  @type store_reason: boolean
+  @param store_reason: whether to store the shutdown reason trail on file
   @rtype: None
 
   """
@@ -1348,6 +1352,8 @@ def StartInstance(instance, startup_paused):
     block_devices = _GatherAndLinkBlockDevs(instance)
     hyper = hypervisor.GetHypervisor(instance.hypervisor)
     hyper.StartInstance(instance, block_devices, startup_paused)
+    if store_reason:
+      _StoreInstReasonTrail(instance.name, reason)
   except errors.BlockDeviceError, err:
     _Fail("Block device error: %s", err, exc=True)
   except errors.HypervisorError, err:
@@ -1467,7 +1473,7 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
       InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
-      result = StartInstance(instance, False)
+      result = StartInstance(instance, False, reason, store_reason=False)
       _StoreInstReasonTrail(instance.name, reason)
       return result
     except errors.HypervisorError, err:
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index a67acc6..38c2189 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -7349,6 +7349,7 @@ class LUInstanceStartup(LogicalUnit):
     """
     instance = self.instance
     force = self.op.force
+    reason = self.op.reason
 
     if not self.op.no_remember:
       self.cfg.MarkInstanceUp(instance.name)
@@ -7365,7 +7366,7 @@ class LUInstanceStartup(LogicalUnit):
         self.rpc.call_instance_start(node_current,
                                      (instance, self.op.hvparams,
                                       self.op.beparams),
-                                     self.op.startup_paused)
+                                     self.op.startup_paused, reason)
       msg = result.fail_msg
       if msg:
         _ShutdownInstanceDisks(self, instance)
@@ -7458,7 +7459,8 @@ class LUInstanceReboot(LogicalUnit):
                      instance.name)
       _StartInstanceDisks(self, instance, ignore_secondaries)
       result = self.rpc.call_instance_start(node_current,
-                                            (instance, None, None), False)
+                                            (instance, None, None), False,
+                                             reason)
       msg = result.fail_msg
       if msg:
         _ShutdownInstanceDisks(self, instance)
@@ -8557,7 +8559,8 @@ class LUInstanceMove(LogicalUnit):
         raise errors.OpExecError("Can't activate the instance's disks")
 
       result = self.rpc.call_instance_start(target_node,
-                                            (instance, None, None), False)
+                                            (instance, None, None), False,
+                                             self.op.reason)
       msg = result.fail_msg
       if msg:
         _ShutdownInstanceDisks(self, instance)
@@ -9297,7 +9300,7 @@ class TLMigrateInstance(Tasklet):
       self.feedback_fn("* starting the instance on the target node %s" %
                        target_node)
       result = self.rpc.call_instance_start(target_node, (instance, None, None),
-                                            False)
+                                            False, self.lu.op.reason)
       msg = result.fail_msg
       if msg:
         _ShutdownInstanceDisks(self.lu, instance)
@@ -11229,7 +11232,7 @@ class LUInstanceCreate(LogicalUnit):
       logging.info("Starting instance %s on node %s", instance, pnode_name)
       feedback_fn("* starting instance...")
       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
-                                            False)
+                                            False, self.op.reason)
       result.Raise("Could not start instance")
 
     return list(iobj.all_nodes)
@@ -14963,7 +14966,8 @@ class LUBackupExport(LogicalUnit):
           assert not activate_disks
           feedback_fn("Starting instance %s" % instance.name)
           result = self.rpc.call_instance_start(src_node,
-                                                (instance, None, None), False)
+                                                (instance, None, None), False,
+                                                 self.op.reason)
           msg = result.fail_msg
           if msg:
             feedback_fn("Failed to start instance: %s" % msg)
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index e6e5751..eb3c21e 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1057,7 +1057,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                              ("/%s/instances/%s/shutdown" %
                               (GANETI_RAPI_VERSION, instance)), query, body)
 
-  def StartupInstance(self, instance, dry_run=False, no_remember=False):
+  def StartupInstance(self, instance, dry_run=False, no_remember=False,
+                      reason=None):
     """Starts up an instance.
 
     @type instance: str
@@ -1066,6 +1067,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param dry_run: whether to perform a dry run
     @type no_remember: bool
     @param no_remember: if true, will not record the state change
+    @type reason: string
+    @param reason: the reason for the startup
     @rtype: string
     @return: job id
 
@@ -1073,6 +1076,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     query = []
     _AppendDryRunIf(query, dry_run)
     _AppendIf(query, no_remember, ("no_remember", 1))
+    _AppendIf(query, reason, ("reason", reason))
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/startup" %
diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
index af625a9..c6f9a09 100644
--- a/lib/rpc_defs.py
+++ b/lib/rpc_defs.py
@@ -279,6 +279,7 @@ _INSTANCE_CALLS = [
   ("instance_start", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("instance_hvp_bep", ED_INST_DICT_HVP_BEP_DP, None),
     ("startup_paused", None, None),
+    ("reason", None, "The reason for the startup"),
     ], None, None, "Starts an instance"),
   ("instance_os_add", SINGLE, None, constants.RPC_TMO_1DAY, [
     ("instance_osp", ED_INST_DICT_OSP_DP, None),
diff --git a/lib/server/noded.py b/lib/server/noded.py
index f4d9df2..a7d7a88 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -598,9 +598,10 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Start an instance.
 
     """
-    (instance_name, startup_paused) = params
+    (instance_name, startup_paused, trail) = params
     instance = objects.Instance.FromDict(instance_name)
-    return backend.StartInstance(instance, startup_paused)
+    _extendReasonTrail(trail, "start")
+    return backend.StartInstance(instance, startup_paused, trail)
 
   @staticmethod
   def perspective_migration_info(params):
diff --git a/test/py/ganeti.rapi.client_unittest.py b/test/py/ganeti.rapi.client_unittest.py
index a122d4e..555ee21 100755
--- a/test/py/ganeti.rapi.client_unittest.py
+++ b/test/py/ganeti.rapi.client_unittest.py
@@ -638,10 +638,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
   def testStartupInstance(self):
     self.rapi.AddResponse("27149")
     self.assertEqual(27149, self.client.StartupInstance("bar-instance",
+                                                        dry_run=True,
+                                                        reason="New"))
+    self.assertHandler(rlib2.R_2_instances_name_startup)
+    self.assertItems(["bar-instance"])
+    self.assertDryRun()
+    self.assertQuery("reason", ["New"])
+
+  def testStartupInstanceDefaultReason(self):
+    self.rapi.AddResponse("27149")
+    self.assertEqual(27149, self.client.StartupInstance("bar-instance",
                                                         dry_run=True))
     self.assertHandler(rlib2.R_2_instances_name_startup)
     self.assertItems(["bar-instance"])
     self.assertDryRun()
+    self.assertQuery("reason", None)
 
   def testReinstallInstance(self):
     self.rapi.AddResponse(serializer.DumpJson([]))
diff --git a/test/py/ganeti.rapi.rlib2_unittest.py b/test/py/ganeti.rapi.rlib2_unittest.py
index 14b4d75..cb19811 100755
--- a/test/py/ganeti.rapi.rlib2_unittest.py
+++ b/test/py/ganeti.rapi.rlib2_unittest.py
@@ -400,6 +400,7 @@ class TestInstanceStartup(unittest.TestCase):
     handler = _CreateHandler(rlib2.R_2_instances_name_startup, ["inst31083"], {
       "force": ["1"],
       "no_remember": ["1"],
+      "reason": ["Newly created instance"],
       }, {}, clfactory)
     job_id = handler.PUT()
 
@@ -413,6 +414,12 @@ class TestInstanceStartup(unittest.TestCase):
     self.assertTrue(op.no_remember)
     self.assertTrue(op.force)
     self.assertFalse(op.dry_run)
+    self.assertEqual(op.reason[0][0], constants.OPCODE_REASON_SRC_USER)
+    self.assertEqual(op.reason[0][1], "Newly created instance")
+    self.assertEqual(op.reason[1][0],
+                     "%s:%s" % (constants.OPCODE_REASON_SRC_RLIB2,
+                                "instances_name_startup"))
+    self.assertEqual(op.reason[1][1], "")
 
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
