diff --git a/lib/backend.py b/lib/backend.py
index cf28d4f2763ab83ead8a410e5027fa2006445819..86dc2ddcbe63db2d3b6a1cf9c0c301bd2d2b6799 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -110,6 +110,61 @@ class RPCFail(Exception):
   """
 
 
+def GetInstReasonFilename(instance_name):
+  """Path of the file containing the reason of the instance status change.
+
+  @type instance_name: string
+  @param instance_name: The name of the instance
+  @rtype: string
+  @return: The path of the file
+
+  """
+  return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name)
+
+
+class InstReason(object):
+  """Class representing the reason for a change of state of a VM.
+
+  It is used to allow an easy serialization of the reason, so that it can be
+  written on a file.
+
+  """
+  def __init__(self, source, text):
+    """Initialize the class with all the required values.
+
+    @type text: string
+    @param text: The textual description of the reason for changing state
+    @type source: string
+    @param source: The source of the state change (RAPI, CLI, ...)
+
+    """
+    self.source = source
+    self.text = text
+
+  def GetJson(self):
+    """Get the JSON representation of the InstReason.
+
+    @rtype: string
+    @return : The JSON representation of the object
+
+    """
+    return serializer.DumpJson(dict(source=self.source, text=self.text))
+
+  def Store(self, instance_name):
+    """Serialize on a file the reason for the last state change of an instance.
+
+    The exact location of the file depends on the name of the instance and on
+    the configuration of the Ganeti cluster defined at deploy time.
+
+    @type instance_name: string
+    @param instance_name: The name of the instance
+    @rtype: None
+
+    """
+    filename = GetInstReasonFilename(instance_name)
+    utils.WriteFile(filename, data=self.GetJson())
+
+
 def _Fail(msg, *args, **kwargs):
   """Log an error and the raise an RPCFail exception.
 
diff --git a/lib/cli.py b/lib/cli.py
index c82de3355660f2fafcfb6fb16db638f5672694c7..76234c205f71a46fa7a0785a11982f45020813e3 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -165,6 +165,7 @@ __all__ = [
   "PRIORITY_OPT",
   "RAPI_CERT_OPT",
   "READD_OPT",
+  "REASON_OPT",
   "REBOOT_TYPE_OPT",
   "REMOVE_INSTANCE_OPT",
   "REMOVE_RESERVED_IPS_OPT",
@@ -1389,6 +1390,10 @@ FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
                               help=("Hide successful results and show failures"
                                     " only (determined by the exit code)"))
 
+REASON_OPT = cli_option("--reason", default=None,
+                        help="The reason for executing a VM-state-changing"
+                             " operation")
+
 
 def _PriorityOptionCb(option, _, value, parser):
   """Callback for processing C{--priority} option.
diff --git a/lib/client/gnt_instance.py b/lib/client/gnt_instance.py
index ee9bf44f0d67e092a511efb7f58805cc53035e38..2553516e570a7f8a70d3fc9dcb3dfd495578b84b 100644
--- a/lib/client/gnt_instance.py
+++ b/lib/client/gnt_instance.py
@@ -631,7 +631,9 @@ def _RebootInstance(name, opts):
   return opcodes.OpInstanceReboot(instance_name=name,
                                   reboot_type=opts.reboot_type,
                                   ignore_secondaries=opts.ignore_secondaries,
-                                  shutdown_timeout=opts.shutdown_timeout)
+                                  shutdown_timeout=opts.shutdown_timeout,
+                                  reason=(constants.INSTANCE_REASON_SOURCE_CLI,
+                                          opts.reason))
 
 
 def _ShutdownInstance(name, opts):
diff --git a/lib/constants.py b/lib/constants.py
index db84892ce6f7a73d09aafbcbbb990c6abb12e62b..62813dc26d8e51a8ac5de15e4f74031c7f678631 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -2332,5 +2332,16 @@ AUTO_REPAIR_ALL_RESULTS = frozenset([
 # The version identifier for builtin data collectors
 BUILTIN_DATA_COLLECTOR_VERSION = "B"
 
+# The source reasons for the change of state of an instance
+INSTANCE_REASON_SOURCE_CLI = "cli"
+INSTANCE_REASON_SOURCE_RAPI = "rapi"
+INSTANCE_REASON_SOURCE_UNKNOWN = "unknown"
+
+INSTANCE_REASON_SOURCES = compat.UniqueFrozenset([
+  INSTANCE_REASON_SOURCE_CLI,
+  INSTANCE_REASON_SOURCE_RAPI,
+  INSTANCE_REASON_SOURCE_UNKNOWN,
+  ])
+
 # Do not re-export imported modules
 del re, _vcsversion, _autoconf, socket, pathutils, compat
diff --git a/lib/pathutils.py b/lib/pathutils.py
index fe531808ee3ef87fe4858d301d0e66c38ddda135..8affef7439ec48abed685316cd9fa5d6931cef69 100644
--- a/lib/pathutils.py
+++ b/lib/pathutils.py
@@ -71,6 +71,7 @@ SOCKET_DIR = RUN_DIR + "/socket"
 CRYPTO_KEYS_DIR = RUN_DIR + "/crypto"
 IMPORT_EXPORT_DIR = RUN_DIR + "/import-export"
 INSTANCE_STATUS_FILE = RUN_DIR + "/instance-status"
+INSTANCE_REASON_DIR = RUN_DIR + "/instance-reason"
 #: User-id pool lock directory (used user IDs have a corresponding lock file in
 #: this directory)
 UIDPOOL_LOCKDIR = RUN_DIR + "/uid-pool"
diff --git a/lib/tools/ensure_dirs.py b/lib/tools/ensure_dirs.py
index 95d2fcec6fd8acc111cbe511ec98445689ce620c..85c32ddcc90cd8b79e9d96f99d4207ce60249fbb 100644
--- a/lib/tools/ensure_dirs.py
+++ b/lib/tools/ensure_dirs.py
@@ -197,6 +197,8 @@ def GetPaths():
     (pathutils.LOG_OS_DIR, DIR, 0750, getent.masterd_uid, getent.daemons_gid),
     (cleaner_log_dir, DIR, 0750, getent.noded_uid, getent.noded_gid),
     (master_cleaner_log_dir, DIR, 0750, getent.masterd_uid, getent.masterd_gid),
+    (pathutils.INSTANCE_REASON_DIR, DIR, 0755, getent.noded_uid,
+     getent.noded_gid),
     ])
 
   return paths
diff --git a/src/Ganeti/OpParams.hs b/src/Ganeti/OpParams.hs
index 8e91b8f7dae53994d091b906a6a1530922c01f65..cccdb7c5fa3b0e7b2efd65dc6f9a9c1e5334dbbb 100644
--- a/src/Ganeti/OpParams.hs
+++ b/src/Ganeti/OpParams.hs
@@ -236,6 +236,7 @@ module Ganeti.OpParams
   , pOpPriority
   , pDependencies
   , pComment
+  , pReason
   , dOldQuery
   , dOldQueryNoLocking
   ) where
@@ -1435,6 +1436,10 @@ pDependencies =
 pComment :: Field
 pComment = optionalNullSerField $ stringField "comment"
 
+-- | The description of the state change reason.
+pReason :: Field
+pReason = simpleField "reason" [t| (InstReasonSrc, NonEmptyString) |]
+
 -- * Entire opcode parameter list
 
 -- | Old-style query opcode, with locking.
diff --git a/src/Ganeti/Types.hs b/src/Ganeti/Types.hs
index 0f3a8e17703b36999e452653fe78a057f71766df..1753ce6824aabeb5cb7532797cbe734ae4f9f8f1 100644
--- a/src/Ganeti/Types.hs
+++ b/src/Ganeti/Types.hs
@@ -92,6 +92,7 @@ module Ganeti.Types
   , opStatusToRaw
   , opStatusFromRaw
   , ELogType(..)
+  , InstReasonSrc(..)
   ) where
 
 import Control.Monad (liftM)
@@ -481,3 +482,12 @@ $(THH.declareSADT "ELogType"
   , ("ELogJqueueTest",   'C.elogJqueueTest)
   ])
 $(THH.makeJSONInstance ''ELogType)
+
+-- | Type for the source of the state change of instances.
+$(THH.declareSADT "InstReasonSrc"
+  [ ("IRSCli", 'C.instanceReasonSourceCli)
+  , ("IRSRapi",  'C.instanceReasonSourceRapi)
+  ])
+$(THH.makeJSONInstance ''InstReasonSrc)
+
+
diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs
index 5015fd2e95e4c20f3b0f61e3888d66930680578e..4dc39ae7a069620dba8c6aaca4ccf9b933a38456 100644
--- a/test/hs/Test/Ganeti/OpCodes.hs
+++ b/test/hs/Test/Ganeti/OpCodes.hs
@@ -69,6 +69,8 @@ $(genArbitrary ''OpCodes.ReplaceDisksMode)
 
 $(genArbitrary ''DiskAccess)
 
+$(genArbitrary ''InstReasonSrc)
+
 instance Arbitrary OpCodes.DiskIndex where
   arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
 
diff --git a/test/py/ganeti.backend_unittest.py b/test/py/ganeti.backend_unittest.py
index a84fbff6118ebfc026edde1afdd6faf3511d6688..86f88bc409bc8246755104c7f8c7dfaff567380f 100755
--- a/test/py/ganeti.backend_unittest.py
+++ b/test/py/ganeti.backend_unittest.py
@@ -32,6 +32,7 @@ from ganeti import constants
 from ganeti import backend
 from ganeti import netutils
 from ganeti import errors
+from ganeti import serializer
 
 import testutils
 import mocks
@@ -537,5 +538,18 @@ class TestGetBlockDevSymlinkPath(unittest.TestCase):
       self._Test("inst1.example.com", idx)
 
 
+class TestInstReason(unittest.TestCase):
+  def testGetJson(self):
+    reason_text = "OS Update"
+    reason_source = constants.INSTANCE_REASON_SOURCE_CLI
+    origDict = dict(text=reason_text, source=reason_source)
+
+    reason = backend.InstReason(reason_source, reason_text)
+    json = reason.GetJson()
+    resultDict = serializer.LoadJson(json)
+
+    self.assertEqual(origDict, resultDict)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()