diff --git a/NEWS b/NEWS
index b14e9923212324dd75503295047a9ec1483062b8..37527ebe1933875d9f13b495aec4607e44403bef 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,7 @@ Version 2.6.0 beta1
 - Deprecation warnings due to pycrypto/paramiko import in
   tools/setup-ssh have been silenced, as usually they are safe; please
   make sure to run an up-to-date paramiko version
+- The QA scripts now depend on Python 2.5 or above
 
 
 Version 2.5.0
diff --git a/qa/qa-sample.json b/qa/qa-sample.json
index 3deeda37d6dd1aff4a85e5f285065de66ccaf589..d2c5f1a9a33bec100404f210b0498d88db085316 100644
--- a/qa/qa-sample.json
+++ b/qa/qa-sample.json
@@ -24,6 +24,9 @@
   "disk": ["1G", "512M"],
   "disk-growth": ["2G", "768M"],
 
+  "# Script to check instance status": null,
+  "instance-check": null,
+
   "nodes": [
     {
       "# Master node": null,
diff --git a/qa/qa_config.py b/qa/qa_config.py
index 5e19c5eab60094ebf2b4998965d1fb67df793977..48a8a11fa4ccf6128ace5d88a5b141e28b50d501 100644
--- a/qa/qa_config.py
+++ b/qa/qa_config.py
@@ -23,6 +23,7 @@
 
 """
 
+import os
 
 from ganeti import utils
 from ganeti import serializer
@@ -31,6 +32,9 @@ from ganeti import compat
 import qa_error
 
 
+_INSTANCE_CHECK_KEY = "instance-check"
+
+
 cfg = None
 options = None
 
@@ -55,6 +59,14 @@ def Validate():
     raise qa_error.Error("Config options 'disk' and 'disk-growth' must have"
                          " the same number of items")
 
+  check = GetInstanceCheckScript()
+  if check:
+    try:
+      os.stat(check)
+    except EnvironmentError, err:
+      raise qa_error.Error("Can't find instance check script '%s': %s" %
+                           (check, err))
+
 
 def get(name, default=None):
   return cfg.get(name, default)
@@ -135,6 +147,13 @@ def TestEnabled(tests, _cfg=None):
                            tests, compat.all)
 
 
+def GetInstanceCheckScript():
+  """Returns path to instance check script or C{None}.
+
+  """
+  return cfg.get(_INSTANCE_CHECK_KEY, None)
+
+
 def GetMasterNode():
   return cfg["nodes"][0]
 
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index f0ef63cc2b7ed78defaf2f9f623f42645ed0a127..d772302c6825b8d23f23c7559386650d07c4ebf9 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -30,9 +30,15 @@ import subprocess
 import random
 import tempfile
 
+try:
+  import functools
+except ImportError, err:
+  raise ImportError("Python 2.5 or higher is required: %s" % err)
+
 from ganeti import utils
 from ganeti import compat
 from ganeti import constants
+from ganeti import ht
 
 import qa_config
 import qa_error
@@ -45,6 +51,16 @@ _RESET_SEQ = None
 
 _MULTIPLEXERS = {}
 
+#: Unique ID per QA run
+_RUN_UUID = utils.NewUUID()
+
+
+(INST_DOWN,
+ INST_UP) = range(500, 502)
+
+(FIRST_ARG,
+ RETURN_VALUE) = range(1000, 1002)
+
 
 def _SetupColours():
   """Initializes the colour constants.
@@ -522,3 +538,91 @@ def RemoveFromEtcHosts(hostnames):
                                               quoted_tmp_hosts))
   except qa_error.Error:
     AssertCommand(["rm", tmp_hosts])
+
+
+def RunInstanceCheck(instance, running):
+  """Check if instance is running or not.
+
+  """
+  script = qa_config.GetInstanceCheckScript()
+  if not script:
+    return
+
+  master_node = qa_config.GetMasterNode()
+  instance_name = instance["name"]
+
+  # Build command to connect to master node
+  master_ssh = GetSSHCommand(master_node["primary"], "--")
+
+  if running:
+    running_shellval = "1"
+    running_text = ""
+  else:
+    running_shellval = ""
+    running_text = "not "
+
+  print FormatInfo("Checking if instance '%s' is %srunning" %
+                   (instance_name, running_text))
+
+  args = [script, instance_name]
+  env = {
+    "PATH": constants.HOOKS_PATH,
+    "RUN_UUID": _RUN_UUID,
+    "MASTER_SSH": utils.ShellQuoteArgs(master_ssh),
+    "INSTANCE_NAME": instance_name,
+    "INSTANCE_RUNNING": running_shellval,
+    }
+
+  result = os.spawnve(os.P_WAIT, script, args, env)
+  if result != 0:
+    raise qa_error.Error("Instance check failed with result %s" % result)
+
+
+_TInstCheck = ht.TStrictDict(False, False, {
+  "name": ht.TNonEmptyString,
+  })
+
+
+def _InstanceCheckInner(expected, instarg, args, result):
+  """Helper function used by L{InstanceCheck}.
+
+  """
+  if instarg == FIRST_ARG:
+    instance = args[0]
+  elif instarg == RETURN_VALUE:
+    instance = result
+  else:
+    raise Exception("Invalid value '%s' for instance argument" % instarg)
+
+  if expected in (INST_DOWN, INST_UP):
+    if not _TInstCheck(instance):
+      raise Exception("Invalid instance: %s" % instance)
+
+    RunInstanceCheck(instance, (expected == INST_UP))
+  elif expected is not None:
+    raise Exception("Invalid value '%s'" % expected)
+
+
+def InstanceCheck(before, after, instarg):
+  """Decorator to check instance status before and after test.
+
+  @param before: L{INST_DOWN} if instance must be stopped before test,
+    L{INST_UP} if instance must be running before test, L{None} to not check.
+  @param after: L{INST_DOWN} if instance must be stopped after test,
+    L{INST_UP} if instance must be running after test, L{None} to not check.
+  @param instarg: L{FIRST_ARG} to use first argument to test as instance (a
+    dictionary), L{RETURN_VALUE} to use return value (disallows pre-checks)
+
+  """
+  def decorator(fn):
+    @functools.wraps(fn)
+    def wrapper(*args, **kwargs):
+      _InstanceCheckInner(before, instarg, args, NotImplemented)
+
+      result = fn(*args, **kwargs)
+
+      _InstanceCheckInner(after, instarg, args, result)
+
+      return result
+    return wrapper
+  return decorator