From ed54b47eec1194dd6f669144f33bf5e734d1f255 Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Fri, 15 Aug 2008 08:43:15 +0000
Subject: [PATCH] Remove QA hook functionality

To my knowledge they're used nowhere and it's at least slightly
confusing to people adding new QA checks.

Reviewed-by: ultrotter
---
 Makefile.am          |  4 --
 qa/ganeti-qa.py      |  1 -
 qa/hooks/datehook.py | 42 ---------------------
 qa/hooks/loghook.py  | 61 ------------------------------
 qa/qa-sample.yaml    |  6 ---
 qa/qa_cluster.py     | 11 ------
 qa/qa_daemon.py      |  2 -
 qa/qa_env.py         |  3 --
 qa/qa_instance.py    | 16 --------
 qa/qa_node.py        |  7 ----
 qa/qa_os.py          |  5 ---
 qa/qa_rapi.py        |  4 --
 qa/qa_tags.py        |  3 --
 qa/qa_utils.py       | 88 --------------------------------------------
 14 files changed, 253 deletions(-)
 delete mode 100644 qa/hooks/datehook.py
 delete mode 100644 qa/hooks/loghook.py

diff --git a/Makefile.am b/Makefile.am
index 6c4b2c62c..38efd06aa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,7 +30,6 @@ DIRS = \
 	lib/rapi \
 	man \
 	qa \
-	qa/hooks \
 	scripts \
 	test \
 	test/data \
@@ -51,7 +50,6 @@ CLEANFILES = \
 	man/*.[78] \
 	man/*.in \
 	qa/*.py[co] \
-	qa/hooks/*.py[co] \
 	test/*.py[co] \
 	stamp-directories \
 	$(nodist_pkgpython_PYTHON)
@@ -141,8 +139,6 @@ EXTRA_DIST = \
 	doc/examples/ganeti.initd.in \
 	doc/examples/ganeti.cron.in \
 	doc/examples/dumb-allocator \
-	qa/hooks/datehook.py \
-	qa/hooks/loghook.py \
 	test/testutils.py \
 	test/mocks.py \
 	$(dist_TESTS) \
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index f38ca337d..c4573ce3a 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -263,7 +263,6 @@ def main():
     sys.exit(1)
 
   qa_config.Load(config_file)
-  qa_utils.LoadHooks()
 
   RunTest(qa_other.UploadKnownHostsFile, known_hosts_file)
 
diff --git a/qa/hooks/datehook.py b/qa/hooks/datehook.py
deleted file mode 100644
index 590bc8556..000000000
--- a/qa/hooks/datehook.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2007 Google Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Example QA hook.
-
-"""
-
-from ganeti import utils
-
-import qa_utils
-import qa_config
-
-from qa_utils import AssertEqual, StartSSH
-
-
-class DateHook:
-  def run(self, ctx):
-    if ctx.name == 'cluster-init' and ctx.phase == 'pre':
-      self._CallDate(ctx)
-
-  def _CallDate(self, ctx):
-    for node in qa_config.get('nodes'):
-      cmd = ['date']
-      AssertEqual(StartSSH(node['primary'],
-                           utils.ShellQuoteArgs(cmd)).wait(), 0)
-
-hook = DateHook
diff --git a/qa/hooks/loghook.py b/qa/hooks/loghook.py
deleted file mode 100644
index 4b86542ff..000000000
--- a/qa/hooks/loghook.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright (C) 2007 Google Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""QA hook to log all function calls.
-
-"""
-
-from ganeti import utils
-
-import qa_utils
-import qa_config
-
-from qa_utils import AssertEqual, StartSSH
-
-
-class LogHook:
-  def __init__(self):
-    file_name = qa_config.get('options', {}).get('hook-logfile', None)
-    if file_name:
-      self.log = open(file_name, "a+")
-    else:
-      self.log = None
-
-  def __del__(self):
-    if self.log:
-      self.log.close()
-
-  def run(self, ctx):
-    if not self.log:
-      return
-
-    msg = "%s-%s" % (ctx.phase, ctx.name)
-    if ctx.phase == 'post':
-      msg += " success=%s" % ctx.success
-    if ctx.args:
-      msg += " %s" % repr(ctx.args)
-    if ctx.kwargs:
-      msg += " %s" % repr(ctx.kwargs)
-    if ctx.phase == 'pre':
-      self.log.write("---\n")
-    self.log.write(msg)
-    self.log.write("\n")
-    self.log.flush()
-
-
-hook = LogHook
diff --git a/qa/qa-sample.yaml b/qa/qa-sample.yaml
index 38b2fa692..e1457c78f 100644
--- a/qa/qa-sample.yaml
+++ b/qa/qa-sample.yaml
@@ -83,9 +83,3 @@ tests:
 options:
   burnin-instances: 2
   burnin-disk-template: drbd
-
-  # Directory containing QA hooks
-  #hooks-dir: hooks/
-
-  # Logfile for loghook.py
-  hook-logfile: /tmp/qa.log
diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py
index 24c9a8872..bf4095fdb 100644
--- a/qa/qa_cluster.py
+++ b/qa/qa_cluster.py
@@ -54,7 +54,6 @@ def _CheckFileOnAllNodes(filename, content):
                 content)
 
 
-@qa_utils.DefineHook('cluster-init')
 def TestClusterInit():
   """gnt-cluster init"""
   master = qa_config.GetMasterNode()
@@ -79,7 +78,6 @@ def TestClusterInit():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('cluster-rename')
 def TestClusterRename():
   """gnt-cluster rename"""
   master = qa_config.GetMasterNode()
@@ -110,7 +108,6 @@ def TestClusterRename():
                        utils.ShellQuoteArgs(cmd_verify)).wait(), 0)
 
 
-@qa_utils.DefineHook('cluster-verify')
 def TestClusterVerify():
   """gnt-cluster verify"""
   master = qa_config.GetMasterNode()
@@ -120,7 +117,6 @@ def TestClusterVerify():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('cluster-info')
 def TestClusterInfo():
   """gnt-cluster info"""
   master = qa_config.GetMasterNode()
@@ -130,7 +126,6 @@ def TestClusterInfo():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('cluster-getmaster')
 def TestClusterGetmaster():
   """gnt-cluster getmaster"""
   master = qa_config.GetMasterNode()
@@ -140,7 +135,6 @@ def TestClusterGetmaster():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('cluster-version')
 def TestClusterVersion():
   """gnt-cluster version"""
   master = qa_config.GetMasterNode()
@@ -150,7 +144,6 @@ def TestClusterVersion():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('cluster-burnin')
 def TestClusterBurnin():
   """Burnin"""
   master = qa_config.GetMasterNode()
@@ -191,7 +184,6 @@ def TestClusterBurnin():
       qa_config.ReleaseInstance(inst)
 
 
-@qa_utils.DefineHook('cluster-master-failover')
 def TestClusterMasterFailover():
   """gnt-cluster masterfailover"""
   master = qa_config.GetMasterNode()
@@ -209,7 +201,6 @@ def TestClusterMasterFailover():
     qa_config.ReleaseNode(failovermaster)
 
 
-@qa_utils.DefineHook('cluster-copyfile')
 def TestClusterCopyfile():
   """gnt-cluster copyfile"""
   master = qa_config.GetMasterNode()
@@ -234,7 +225,6 @@ def TestClusterCopyfile():
     _RemoveFileFromAllNodes(testname)
 
 
-@qa_utils.DefineHook('cluster-command')
 def TestClusterCommand():
   """gnt-cluster command"""
   master = qa_config.GetMasterNode()
@@ -252,7 +242,6 @@ def TestClusterCommand():
     _RemoveFileFromAllNodes(rfile)
 
 
-@qa_utils.DefineHook('cluster-destroy')
 def TestClusterDestroy():
   """gnt-cluster destroy"""
   master = qa_config.GetMasterNode()
diff --git a/qa/qa_daemon.py b/qa/qa_daemon.py
index 30bb657e6..4817bfef0 100644
--- a/qa/qa_daemon.py
+++ b/qa/qa_daemon.py
@@ -104,7 +104,6 @@ def PrintCronWarning():
   print qa_utils.FormatWarning(msg)
 
 
-@qa_utils.DefineHook('daemon-automatic-restart')
 def TestInstanceAutomaticRestart(node, instance):
   """Test automatic restart of instance by ganeti-watcher.
 
@@ -126,7 +125,6 @@ def TestInstanceAutomaticRestart(node, instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('daemon-consecutive-failures')
 def TestInstanceConsecutiveFailures(node, instance):
   """Test five consecutive instance failures.
 
diff --git a/qa/qa_env.py b/qa/qa_env.py
index 540363d3a..b2dff2e09 100644
--- a/qa/qa_env.py
+++ b/qa/qa_env.py
@@ -31,7 +31,6 @@ import qa_utils
 from qa_utils import AssertEqual, StartSSH
 
 
-@qa_utils.DefineHook('env-ssh-connection')
 def TestSshConnection():
   """Test SSH connection.
 
@@ -40,7 +39,6 @@ def TestSshConnection():
     AssertEqual(StartSSH(node['primary'], 'exit').wait(), 0)
 
 
-@qa_utils.DefineHook('env-ganeti-commands')
 def TestGanetiCommands():
   """Test availibility of Ganeti commands.
 
@@ -59,7 +57,6 @@ def TestGanetiCommands():
     AssertEqual(StartSSH(node['primary'], cmd).wait(), 0)
 
 
-@qa_utils.DefineHook('env-icmp-ping')
 def TestIcmpPing():
   """ICMP ping each node.
 
diff --git a/qa/qa_instance.py b/qa/qa_instance.py
index b2e8f4604..cf749d77d 100644
--- a/qa/qa_instance.py
+++ b/qa/qa_instance.py
@@ -66,20 +66,17 @@ def _DiskTest(node, disk_template):
     raise
 
 
-@qa_utils.DefineHook('instance-add-plain-disk')
 def TestInstanceAddWithPlainDisk(node):
   """gnt-instance add -t plain"""
   return _DiskTest(node['primary'], 'plain')
 
 
-@qa_utils.DefineHook('instance-add-drbd-disk')
 def TestInstanceAddWithDrbdDisk(node, node2):
   """gnt-instance add -t drbd"""
   return _DiskTest("%s:%s" % (node['primary'], node2['primary']),
                    'drbd')
 
 
-@qa_utils.DefineHook('instance-remove')
 def TestInstanceRemove(instance):
   """gnt-instance remove"""
   master = qa_config.GetMasterNode()
@@ -91,7 +88,6 @@ def TestInstanceRemove(instance):
   qa_config.ReleaseInstance(instance)
 
 
-@qa_utils.DefineHook('instance-startup')
 def TestInstanceStartup(instance):
   """gnt-instance startup"""
   master = qa_config.GetMasterNode()
@@ -101,7 +97,6 @@ def TestInstanceStartup(instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-shutdown')
 def TestInstanceShutdown(instance):
   """gnt-instance shutdown"""
   master = qa_config.GetMasterNode()
@@ -111,7 +106,6 @@ def TestInstanceShutdown(instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-reboot')
 def TestInstanceReboot(instance):
   """gnt-instance reboot"""
   master = qa_config.GetMasterNode()
@@ -123,7 +117,6 @@ def TestInstanceReboot(instance):
                          utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-reinstall')
 def TestInstanceReinstall(instance):
   """gnt-instance reinstall"""
   master = qa_config.GetMasterNode()
@@ -133,7 +126,6 @@ def TestInstanceReinstall(instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-failover')
 def TestInstanceFailover(instance):
   """gnt-instance failover"""
   master = qa_config.GetMasterNode()
@@ -148,7 +140,6 @@ def TestInstanceFailover(instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-info')
 def TestInstanceInfo(instance):
   """gnt-instance info"""
   master = qa_config.GetMasterNode()
@@ -158,7 +149,6 @@ def TestInstanceInfo(instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-modify')
 def TestInstanceModify(instance):
   """gnt-instance modify"""
   master = qa_config.GetMasterNode()
@@ -191,7 +181,6 @@ def TestInstanceModify(instance):
                           utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-list')
 def TestInstanceList():
   """gnt-instance list"""
   master = qa_config.GetMasterNode()
@@ -201,7 +190,6 @@ def TestInstanceList():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-console')
 def TestInstanceConsole(instance):
   """gnt-instance console"""
   master = qa_config.GetMasterNode()
@@ -211,7 +199,6 @@ def TestInstanceConsole(instance):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('instance-replace-disks')
 def TestReplaceDisks(instance, pnode, snode, othernode):
   """gnt-instance replace-disks"""
   master = qa_config.GetMasterNode()
@@ -240,7 +227,6 @@ def TestReplaceDisks(instance, pnode, snode, othernode):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('backup-export')
 def TestInstanceExport(instance, node):
   """gnt-backup export"""
   master = qa_config.GetMasterNode()
@@ -252,7 +238,6 @@ def TestInstanceExport(instance, node):
   return qa_utils.ResolveInstanceName(instance)
 
 
-@qa_utils.DefineHook('backup-import')
 def TestInstanceImport(node, newinst, expnode, name):
   """gnt-backup import"""
   master = qa_config.GetMasterNode()
@@ -269,7 +254,6 @@ def TestInstanceImport(node, newinst, expnode, name):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('backup-list')
 def TestBackupList(expnode):
   """gnt-backup list"""
   master = qa_config.GetMasterNode()
diff --git a/qa/qa_node.py b/qa/qa_node.py
index f337734e5..9899762e4 100644
--- a/qa/qa_node.py
+++ b/qa/qa_node.py
@@ -28,7 +28,6 @@ import qa_utils
 from qa_utils import AssertEqual, StartSSH
 
 
-@qa_utils.DefineHook('node-add')
 def _NodeAdd(node, readd=False):
   master = qa_config.GetMasterNode()
 
@@ -49,7 +48,6 @@ def _NodeAdd(node, readd=False):
   node['_added'] = True
 
 
-@qa_utils.DefineHook('node-remove')
 def _NodeRemove(node):
   master = qa_config.GetMasterNode()
 
@@ -75,13 +73,11 @@ def TestNodeRemoveAll():
       _NodeRemove(node)
 
 
-@qa_utils.DefineHook('node-readd')
 def TestNodeReadd(node):
   """gnt-node add --readd"""
   _NodeAdd(node, readd=True)
 
 
-@qa_utils.DefineHook('node-info')
 def TestNodeInfo():
   """gnt-node info"""
   master = qa_config.GetMasterNode()
@@ -91,7 +87,6 @@ def TestNodeInfo():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('node-volumes')
 def TestNodeVolumes():
   """gnt-node volumes"""
   master = qa_config.GetMasterNode()
@@ -101,7 +96,6 @@ def TestNodeVolumes():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('node-failover')
 def TestNodeFailover(node, node2):
   """gnt-node failover"""
   master = qa_config.GetMasterNode()
@@ -122,7 +116,6 @@ def TestNodeFailover(node, node2):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('node-evacuate')
 def TestNodeEvacuate(node, node2):
   """gnt-node evacuate"""
   master = qa_config.GetMasterNode()
diff --git a/qa/qa_os.py b/qa/qa_os.py
index fbdaa27e6..b39e3ca39 100644
--- a/qa/qa_os.py
+++ b/qa/qa_os.py
@@ -39,7 +39,6 @@ _TEMP_OS_NAME = "TEMP-Ganeti-QA-OS"
 _TEMP_OS_PATH = os.path.join(constants.OS_SEARCH_PATH[0], _TEMP_OS_NAME)
 
 
-@qa_utils.DefineHook('os-list')
 def TestOsList():
   """gnt-os list"""
   master = qa_config.GetMasterNode()
@@ -49,7 +48,6 @@ def TestOsList():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('os-diagnose')
 def TestOsDiagnose():
   """gnt-os diagnose"""
   master = qa_config.GetMasterNode()
@@ -128,19 +126,16 @@ def _TestOs(mode):
       _RemoveTempOs(node, dir)
 
 
-@qa_utils.DefineHook('os-valid')
 def TestOsValid():
   """Testing valid OS definition"""
   return _TestOs(1)
 
 
-@qa_utils.DefineHook('os-invalid')
 def TestOsInvalid():
   """Testing invalid OS definition"""
   return _TestOs(0)
 
 
-@qa_utils.DefineHook('os-partially-valid')
 def TestOsPartiallyValid():
   """Testing partially valid OS definition"""
   return _TestOs(2)
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index 0f9054fc6..f45f6c4d7 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -97,7 +97,6 @@ def _DoTests(uris):
         AssertEqual(data, verify)
 
 
-@qa_utils.DefineHook('rapi-version')
 def TestVersion():
   """Testing remote API version.
 
@@ -107,7 +106,6 @@ def TestVersion():
     ])
 
 
-@qa_utils.DefineHook('rapi-empty-cluster')
 def TestEmptyCluster():
   """Testing remote API on an empty cluster.
 
@@ -143,7 +141,6 @@ def TestEmptyCluster():
     ])
 
 
-@qa_utils.DefineHook('rapi-instance')
 def TestInstance(instance):
   """Testing getting instance(s) info via remote API.
 
@@ -168,7 +165,6 @@ def TestInstance(instance):
     ])
 
 
-@qa_utils.DefineHook('rapi-node')
 def TestNode(node):
   """Testing getting node(s) info via remote API.
 
diff --git a/qa/qa_tags.py b/qa/qa_tags.py
index 7cccced6f..ae19f2e31 100644
--- a/qa/qa_tags.py
+++ b/qa/qa_tags.py
@@ -74,19 +74,16 @@ def _TestTags(kind, name):
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
-@qa_utils.DefineHook('tags-cluster')
 def TestClusterTags():
   """gnt-cluster tags"""
   _TestTags(constants.TAG_CLUSTER, "")
 
 
-@qa_utils.DefineHook('tags-node')
 def TestNodeTags(node):
   """gnt-node tags"""
   _TestTags(constants.TAG_NODE, node["primary"])
 
 
-@qa_utils.DefineHook('tags-instance')
 def TestInstanceTags(instance):
   """gnt-instance tags"""
   _TestTags(constants.TAG_INSTANCE, instance["name"])
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 200563435..bbf667cc4 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -39,10 +39,6 @@ _ERROR_SEQ = None
 _RESET_SEQ = None
 
 
-# List of all hooks
-_hooks = []
-
-
 def _SetupColours():
   """Initializes the colour constants.
 
@@ -225,87 +221,3 @@ def _FormatWithColor(text, seq):
 FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
 FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
 FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)
-
-
-def LoadHooks():
-  """Load all QA hooks.
-
-  """
-  hooks_dir = qa_config.get('options', {}).get('hooks-dir', None)
-  if not hooks_dir:
-    return
-  if hooks_dir not in sys.path:
-    sys.path.insert(0, hooks_dir)
-  for name in utils.ListVisibleFiles(hooks_dir):
-    if name.endswith('.py'):
-      # Load and instanciate hook
-      print "Loading hook %s" % name
-      _hooks.append(__import__(name[:-3], None, None, ['']).hook())
-
-
-class QaHookContext:
-  """Definition of context passed to hooks.
-
-  """
-  name = None
-  phase = None
-  success = None
-  args = None
-  kwargs = None
-
-
-def _CallHooks(ctx):
-  """Calls all hooks with the given context.
-
-  """
-  if not _hooks:
-    return
-
-  name = "%s-%s" % (ctx.phase, ctx.name)
-  if ctx.success is not None:
-    msg = "%s (success=%s)" % (name, ctx.success)
-  else:
-    msg = name
-  print FormatInfo("Begin %s" % msg)
-  for hook in _hooks:
-    hook.run(ctx)
-  print FormatInfo("End %s" % name)
-
-
-def DefineHook(name):
-  """Wraps a function with calls to hooks.
-
-  Usage: prefix function with @qa_utils.DefineHook(...)
-
-  This is based on PEP 318, "Decorators for Functions and Methods".
-
-  """
-  def wrapper(fn):
-    def new_f(*args, **kwargs):
-      # Create context
-      ctx = QaHookContext()
-      ctx.name = name
-      ctx.phase = 'pre'
-      ctx.args = args
-      ctx.kwargs = kwargs
-
-      _CallHooks(ctx)
-      try:
-        ctx.phase = 'post'
-        ctx.success = True
-        try:
-          # Call real function
-          return fn(*args, **kwargs)
-        except:
-          ctx.success = False
-          raise
-      finally:
-        _CallHooks(ctx)
-
-    # Override function metadata
-    new_f.func_name = fn.func_name
-    new_f.func_doc = fn.func_doc
-
-    return new_f
-
-  return wrapper
-- 
GitLab