diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 021e1d9d27ccb8c2f24b978d40617adc333e2448..b7cd009747847db0febf5e03dff2ad92fede4fd6 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -211,6 +211,12 @@ def main():
         if qa_config.TestEnabled('instance-failover'):
           RunTest(qa_instance.TestInstanceFailover, instance)
 
+        if qa_config.TestEnabled('node-evacuate'):
+          RunTest(qa_node.TestNodeEvacuate, node, node2)
+
+        if qa_config.TestEnabled('node-failover'):
+          RunTest(qa_node.TestNodeFailover, node, node2)
+
         if qa_config.TestEnabled('node-volumes'):
           RunTest(qa_node.TestNodeVolumes)
 
diff --git a/qa/qa-sample.yaml b/qa/qa-sample.yaml
index 59fb8f6b32464b735c60676e82b3f293a18f24a1..2a26742b0e2b9cc4ba26418f5e01e19b36079e1c 100644
--- a/qa/qa-sample.yaml
+++ b/qa/qa-sample.yaml
@@ -38,6 +38,10 @@ tests:
   node-info: True
   node-volumes: True
 
+  # These tests need at least three nodes
+  node-evacuate: False
+  node-failover: False
+
   instance-add-plain-disk: True
   instance-add-local-mirror-disk: True
   instance-add-remote-raid-disk: True
diff --git a/qa/qa_config.py b/qa/qa_config.py
index bf176bafb895919934987ee3c781fdb70943e978..5f0a8ac9250c7d34cc7d8e6db009c0941bf65afc 100644
--- a/qa/qa_config.py
+++ b/qa/qa_config.py
@@ -96,6 +96,8 @@ def AcquireNode(exclude=None):
   # TODO: Maybe combine filters
   if exclude is None:
     nodes = cfg['nodes'][:]
+  elif isinstance(exclude, (list, tuple)):
+    nodes = filter(lambda node: node not in exclude, cfg['nodes'])
   else:
     nodes = filter(lambda node: node != exclude, cfg['nodes'])
 
diff --git a/qa/qa_error.py b/qa/qa_error.py
index 60c1a46aadb8fef0d84202e1bf332354fba87fa8..d2885477e6954abf4b298a497d34463278a2cd00 100644
--- a/qa/qa_error.py
+++ b/qa/qa_error.py
@@ -35,3 +35,10 @@ class OutOfInstancesError(Error):
 
   """
   pass
+
+
+class UnusableNodeError(Error):
+  """Unusable node.
+
+  """
+  pass
diff --git a/qa/qa_node.py b/qa/qa_node.py
index 44f89b1c385360f8fd74913365cc6763d16dfc4e..6b29d04ffb0e2e0702d7901d071b80771089444f 100644
--- a/qa/qa_node.py
+++ b/qa/qa_node.py
@@ -20,6 +20,7 @@ from ganeti import utils
 
 import qa_config
 import qa_error
+import qa_utils
 
 from qa_utils import AssertEqual, StartSSH
 
@@ -81,3 +82,47 @@ def TestNodeVolumes():
   cmd = ['gnt-node', 'volumes']
   AssertEqual(StartSSH(master['primary'],
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+
+def TestNodeFailover(node, node2):
+  """gnt-node failover"""
+  master = qa_config.GetMasterNode()
+
+  if qa_utils.GetNodeInstances(node2):
+    raise qa_errors.UnusableNodeError("Secondary node has at least one "
+                                      "primary instance. This test requires "
+                                      "it to have no primary instances.")
+
+  # Fail over to secondary node
+  cmd = ['gnt-node', 'failover', '-f', node['primary']]
+  AssertEqual(StartSSH(master['primary'],
+                       utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+  # ... and back again.
+  cmd = ['gnt-node', 'failover', '-f', node2['primary']]
+  AssertEqual(StartSSH(master['primary'],
+                       utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+
+def TestNodeEvacuate(node, node2):
+  """gnt-node evacuate"""
+  master = qa_config.GetMasterNode()
+
+  node3 = qa_config.AcquireNode(exclude=[node, node2])
+  try:
+    if qa_utils.GetNodeInstances(node3):
+      raise qa_errors.UnusableNodeError("Evacuation node has at least one "
+                                        "secondary instance. This test requires "
+                                        "it to have no secondary instances.")
+
+    # Evacuate all secondary instances
+    cmd = ['gnt-node', 'evacuate', '-f', node2['primary'], node3['primary']]
+    AssertEqual(StartSSH(master['primary'],
+                         utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+    # ... and back again.
+    cmd = ['gnt-node', 'evacuate', '-f', node3['primary'], node2['primary']]
+    AssertEqual(StartSSH(master['primary'],
+                         utils.ShellQuoteArgs(cmd)).wait(), 0)
+  finally:
+    qa_config.ReleaseNode(node3)
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 1ad596fb8deafc0dfbbdc1edaaab3571f3b89fb9..84330601b7992dec72462d9179d0da4eedbae8c5 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -100,8 +100,18 @@ def StartSSH(node, cmd, strict=True):
   """Starts SSH.
 
   """
-  args = GetSSHCommand(node, cmd, strict=strict)
-  return subprocess.Popen(args, shell=False)
+  return subprocess.Popen(GetSSHCommand(node, cmd, strict=strict),
+                          shell=False)
+
+
+def GetCommandOutput(node, cmd):
+  """Returns the output of a command executed on the given node.
+
+  """
+  p = subprocess.Popen(GetSSHCommand(node, cmd),
+                       shell=False, stdout=subprocess.PIPE)
+  AssertEqual(p.wait(), 0)
+  return p.stdout.read()
 
 
 def UploadFile(node, src):
@@ -130,21 +140,57 @@ def UploadFile(node, src):
     f.close()
 
 
+def _ResolveName(cmd, key):
+  """Helper function.
+
+  """
+  master = qa_config.GetMasterNode()
+
+  output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
+  for line in output.splitlines():
+    (lkey, lvalue) = line.split(':', 1)
+    if lkey == key:
+      return lvalue.lstrip()
+  raise KeyError("Key not found")
+
+
 def ResolveInstanceName(instance):
   """Gets the full name of an instance.
 
+  """
+  return _ResolveName(['gnt-instance', 'info', instance['info']],
+                      'Instance name')
+
+
+def ResolveNodeName(node):
+  """Gets the full name of a node.
+
+  """
+  return _ResolveName(['gnt-node', 'info', node['primary']],
+                      'Node name')
+
+
+def GetNodeInstances(node, secondaries=False):
+  """Gets a list of instances on a node.
+
   """
   master = qa_config.GetMasterNode()
 
-  info_cmd = utils.ShellQuoteArgs(['gnt-instance', 'info', instance['name']])
-  sed_cmd = utils.ShellQuoteArgs(['sed', '-n', '-e', 's/^Instance name: *//p'])
+  node_name = ResolveNodeName(node)
 
-  cmd = '%s | %s' % (info_cmd, sed_cmd)
-  ssh_cmd = GetSSHCommand(master['primary'], cmd)
-  p = subprocess.Popen(ssh_cmd, shell=False, stdout=subprocess.PIPE)
-  AssertEqual(p.wait(), 0)
+  # Get list of all instances
+  cmd = ['gnt-instance', 'list', '--separator=:', '--no-headers',
+         '--output=name,pnode,snodes']
+  output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
+
+  instances = []
+  for line in output.splitlines():
+    (name, pnode, snodes) = line.split(':', 2)
+    if ((not secondaries and pnode == node_name) or
+        (secondaries and node_name in snodes.split(','))):
+      instances.append(name)
 
-  return p.stdout.read().strip()
+  return instances
 
 
 def _PrintWithColor(text, seq):