commit 1f350e0fb197d85be018ec575afc97911cde5bd2
Author: Michele Tartara <mtartara@google.com>
Date:   Mon Apr 22 15:26:29 2013 +0000

    Reason trail implementation for "shutdown"
    
    Opcode-specific implementation of the reason trail for the instance shutdown
    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 7d8315d..b2924c0 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -1355,7 +1355,7 @@ def StartInstance(instance, startup_paused):
     _Fail("Hypervisor error: %s", err, exc=True)
 
 
-def InstanceShutdown(instance, timeout):
+def InstanceShutdown(instance, timeout, reason, store_reason=True):
   """Shut an instance down.
 
   @note: this functions uses polling with a hardcoded timeout.
@@ -1364,6 +1364,10 @@ def InstanceShutdown(instance, timeout):
   @param instance: the instance object
   @type timeout: integer
   @param timeout: maximum timeout for soft shutdown
+  @type reason: list of reasons
+  @param reason: the reason trail for this shutdown
+  @type store_reason: boolean
+  @param store_reason: whether to store the shutdown reason trail on file
   @rtype: None
 
   """
@@ -1385,6 +1389,8 @@ def InstanceShutdown(instance, timeout):
 
       try:
         hyper.StopInstance(instance, retry=self.tried_once)
+        if store_reason:
+          _StoreInstReasonTrail(instance.name, reason)
       except errors.HypervisorError, err:
         if iname not in hyper.ListInstances():
           # if the instance is no longer existing, consider this a
@@ -1460,7 +1466,7 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
       _Fail("Failed to soft reboot instance %s: %s", instance.name, err)
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
-      InstanceShutdown(instance, shutdown_timeout)
+      InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
       result = StartInstance(instance, False)
       _StoreInstReasonTrail(instance.name, reason)
       return result
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 19898e7..a67acc6 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -7449,7 +7449,8 @@ class LUInstanceReboot(LogicalUnit):
     else:
       if instance_running:
         result = self.rpc.call_instance_shutdown(node_current, instance,
-                                                 self.op.shutdown_timeout)
+                                                 self.op.shutdown_timeout,
+                                                 reason)
         result.Raise("Could not shutdown instance for full reboot")
         _ShutdownInstanceDisks(self, instance)
       else:
@@ -7525,6 +7526,7 @@ class LUInstanceShutdown(LogicalUnit):
     instance = self.instance
     node_current = instance.primary_node
     timeout = self.op.timeout
+    reason = self.op.reason
 
     # If the instance is offline we shouldn't mark it as down, as that
     # resets the offline flag.
@@ -7535,7 +7537,8 @@ class LUInstanceShutdown(LogicalUnit):
       assert self.op.ignore_offline_nodes
       self.LogInfo("Primary node offline, marked instance as stopped")
     else:
-      result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
+      result = self.rpc.call_instance_shutdown(node_current, instance, timeout,
+                                               reason)
       msg = result.fail_msg
       if msg:
         self.LogWarning("Could not shutdown instance: %s", msg)
@@ -8116,7 +8119,8 @@ class LUInstanceRemove(LogicalUnit):
                  instance.name, instance.primary_node)
 
     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
-                                             self.op.shutdown_timeout)
+                                             self.op.shutdown_timeout,
+                                             self.op.reason)
     msg = result.fail_msg
     if msg:
       if self.op.ignore_failures:
@@ -8481,7 +8485,8 @@ class LUInstanceMove(LogicalUnit):
             self.owned_locks(locking.LEVEL_NODE_RES))
 
     result = self.rpc.call_instance_shutdown(source_node, instance,
-                                             self.op.shutdown_timeout)
+                                             self.op.shutdown_timeout,
+                                             self.op.reason)
     msg = result.fail_msg
     if msg:
       if self.op.ignore_consistency:
@@ -9254,7 +9259,8 @@ class TLMigrateInstance(Tasklet):
                  instance.name, source_node)
 
     result = self.rpc.call_instance_shutdown(source_node, instance,
-                                             self.shutdown_timeout)
+                                             self.shutdown_timeout,
+                                             self.lu.op.reason)
     msg = result.fail_msg
     if msg:
       if self.ignore_consistency or primary_node.offline:
@@ -14927,7 +14933,8 @@ class LUBackupExport(LogicalUnit):
       # shutdown the instance, but not the disks
       feedback_fn("Shutting down instance %s" % instance.name)
       result = self.rpc.call_instance_shutdown(src_node, instance,
-                                               self.op.shutdown_timeout)
+                                               self.op.shutdown_timeout,
+                                               self.op.reason)
       # TODO: Maybe ignore failures if ignore_remove_failures is set
       result.Raise("Could not shutdown instance %s on"
                    " node %s" % (instance.name, src_node))
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index ace4d1e..e6e5751 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1031,7 +1031,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def ShutdownInstance(self, instance, dry_run=False, no_remember=False,
-                       **kwargs):
+                       reason=None, **kwargs):
     """Shuts down an instance.
 
     @type instance: str
@@ -1040,6 +1040,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 shutdown
     @rtype: string
     @return: job id
 
@@ -1049,6 +1051,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     _AppendDryRunIf(query, dry_run)
     _AppendIf(query, no_remember, ("no_remember", 1))
+    _AppendIf(query, reason, ("reason", reason))
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/shutdown" %
diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
index bf3cbe8..af625a9 100644
--- a/lib/rpc_defs.py
+++ b/lib/rpc_defs.py
@@ -235,6 +235,7 @@ _INSTANCE_CALLS = [
   ("instance_shutdown", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("instance", ED_INST_DICT, "Instance object"),
     ("timeout", None, None),
+    ("reason", None, "The reason for the shutdown"),
     ], None, None, "Stops an instance"),
   ("instance_balloon_memory", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("instance", ED_INST_DICT, "Instance object"),
diff --git a/lib/server/noded.py b/lib/server/noded.py
index c81352b..f4d9df2 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -589,7 +589,9 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """
     instance = objects.Instance.FromDict(params[0])
     timeout = params[1]
-    return backend.InstanceShutdown(instance, timeout)
+    trail = params[2]
+    _extendReasonTrail(trail, "shutdown")
+    return backend.InstanceShutdown(instance, timeout, trail)
 
   @staticmethod
   def perspective_instance_start(params):
diff --git a/test/py/ganeti.rapi.client_unittest.py b/test/py/ganeti.rapi.client_unittest.py
index 3174c69..a122d4e 100755
--- a/test/py/ganeti.rapi.client_unittest.py
+++ b/test/py/ganeti.rapi.client_unittest.py
@@ -619,10 +619,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
   def testShutdownInstance(self):
     self.rapi.AddResponse("1487")
     self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
+                                                        dry_run=True,
+                                                        reason="NoMore"))
+    self.assertHandler(rlib2.R_2_instances_name_shutdown)
+    self.assertItems(["foo-instance"])
+    self.assertDryRun()
+    self.assertQuery("reason", ["NoMore"])
+
+  def testShutdownInstanceDefaultReason(self):
+    self.rapi.AddResponse("1487")
+    self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
                                                         dry_run=True))
     self.assertHandler(rlib2.R_2_instances_name_shutdown)
     self.assertItems(["foo-instance"])
     self.assertDryRun()
+    self.assertQuery("reason", None)
 
   def testStartupInstance(self):
     self.rapi.AddResponse("27149")
diff --git a/test/py/ganeti.rapi.rlib2_unittest.py b/test/py/ganeti.rapi.rlib2_unittest.py
index e19c925..14b4d75 100755
--- a/test/py/ganeti.rapi.rlib2_unittest.py
+++ b/test/py/ganeti.rapi.rlib2_unittest.py
@@ -422,6 +422,7 @@ class TestInstanceShutdown(unittest.TestCase):
     clfactory = _FakeClientFactory(_FakeClient)
     handler = _CreateHandler(rlib2.R_2_instances_name_shutdown, ["inst26791"], {
       "no_remember": ["0"],
+      "reason": ["Not used anymore"],
       }, {}, clfactory)
     job_id = handler.PUT()
 
@@ -434,6 +435,12 @@ class TestInstanceShutdown(unittest.TestCase):
     self.assertEqual(op.instance_name, "inst26791")
     self.assertFalse(op.no_remember)
     self.assertFalse(op.dry_run)
+    self.assertEqual(op.reason[0][0], constants.OPCODE_REASON_SRC_USER)
+    self.assertEqual(op.reason[0][1], "Not used anymore")
+    self.assertEqual(op.reason[1][0],
+                     "%s:%s" % (constants.OPCODE_REASON_SRC_RLIB2,
+                                "instances_name_shutdown"))
+    self.assertEqual(op.reason[1][1], "")
 
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
