From a59d5fa1351df43917514a1e30cd52f6a3789340 Mon Sep 17 00:00:00 2001 From: Michele Tartara <mtartara@google.com> Date: Tue, 12 Feb 2013 09:15:46 +0000 Subject: [PATCH] Infrastructure for specifying instance status change reason This patch introduces some infrastructural modifications that will be used by the following commits to implement the support for specifying the reason for the last status change of an instance. Signed-off-by: Michele Tartara <mtartara@google.com> Reviewed-by: Michael Hanselmann <hansmi@google.com> --- lib/backend.py | 55 ++++++++++++++++++++++++++++++ lib/cli.py | 5 +++ lib/client/gnt_instance.py | 4 ++- lib/constants.py | 11 ++++++ lib/pathutils.py | 1 + lib/tools/ensure_dirs.py | 2 ++ src/Ganeti/OpParams.hs | 5 +++ src/Ganeti/Types.hs | 10 ++++++ test/hs/Test/Ganeti/OpCodes.hs | 2 ++ test/py/ganeti.backend_unittest.py | 14 ++++++++ 10 files changed, 108 insertions(+), 1 deletion(-) diff --git a/lib/backend.py b/lib/backend.py index cf28d4f27..86dc2ddcb 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 c82de3355..76234c205 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 ee9bf44f0..2553516e5 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 db84892ce..62813dc26 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 fe531808e..8affef743 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 95d2fcec6..85c32ddcc 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 8e91b8f7d..cccdb7c5f 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 0f3a8e177..1753ce682 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 5015fd2e9..4dc39ae7a 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 a84fbff61..86f88bc40 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() -- GitLab