diff --git a/NEWS b/NEWS
index 3d8229288333e55d90dc33b1a31dd2861d884ea7..1e1cd6c8f1ec03327846a063179c5a9b71ad8390 100644
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,10 @@
 News
 ====
 
-Version 2.5.0 rc2
+Version 2.5.0 rc4
 -----------------
 
-*(Released Tue, 18 Oct 2011)*
+*(Released Thu, 27 Oct 2011)*
 
 Incompatible/important changes and bugfixes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -119,6 +119,8 @@ Misc
 
   - :doc:`RAPI <rapi>` documentation now has detailed parameter
     descriptions.
+  - Some opcode/job results are now also documented, see :doc:`RAPI
+    <rapi>`.
 
 - A lockset's internal lock is now also visible in lock monitor.
 - Log messages from job queue workers now contain information about the
@@ -129,6 +131,22 @@ Misc
 - DRBD metadata volumes are overwritten with zeros during disk creation.
 
 
+Version 2.5.0 rc3
+-----------------
+
+*(Released Wed, 26 Oct 2011)*
+
+This was the third release candidate of the 2.5 series.
+
+
+Version 2.5.0 rc2
+-----------------
+
+*(Released Tue, 18 Oct 2011)*
+
+This was the second release candidate of the 2.5 series.
+
+
 Version 2.5.0 rc1
 -----------------
 
diff --git a/configure.ac b/configure.ac
index 4234b467b282e257a72adccc7775681019499321..dbad2efbd679c85e232c1c78eb6508dc2956721b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 m4_define([gnt_version_major], [2])
 m4_define([gnt_version_minor], [5])
 m4_define([gnt_version_revision], [0])
-m4_define([gnt_version_suffix], [~rc2])
+m4_define([gnt_version_suffix], [~rc4])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
                     gnt_version_major, gnt_version_minor,
diff --git a/doc/rapi.rst b/doc/rapi.rst
index 6dea27c617d191dd111ba466e4e2604053bf6a54..79373211ccf91e4122bf43124f25938adcb78571 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -1287,6 +1287,10 @@ The query arguments used up to and including Ganeti 2.4 are deprecated
 and should no longer be used. The new request format can be detected by
 the presence of the :pyeval:`rlib2._NODE_MIGRATE_REQV1` feature string.
 
+Job result:
+
+.. opcode_result:: OP_NODE_MIGRATE
+
 
 ``/2/nodes/[node_name]/role``
 +++++++++++++++++++++++++++++
diff --git a/htools/Ganeti/HTools/Loader.hs b/htools/Ganeti/HTools/Loader.hs
index 58ad010c963ae946880844b66e0af4a16af4dbfb..db08d73a5464a35d7ead2defd16f4a4ea9812783 100644
--- a/htools/Ganeti/HTools/Loader.hs
+++ b/htools/Ganeti/HTools/Loader.hs
@@ -318,11 +318,14 @@ checkData nl il =
                              - nodeIdsk node il
                  newn = Node.setFmem (Node.setXmem node delta_mem)
                         (Node.fMem node - adj_mem)
-                 umsg1 = [printf "node %s is missing %d MB ram \
-                                 \and %d GB disk"
-                                 nname delta_mem (delta_dsk `div` 1024) |
-                                 delta_mem > 512 || delta_dsk > 1024]::[String]
-             in (msgs ++ umsg1, newn)
+                 umsg1 =
+                   if delta_mem > 512 || delta_dsk > 1024
+                      then (printf "node %s is missing %d MB ram \
+                                   \and %d GB disk"
+                                   nname delta_mem (delta_dsk `div` 1024)):
+                           msgs
+                      else msgs
+             in (umsg1, newn)
         ) [] nl
 
 -- | Compute the amount of memory used by primary instances on a node.
diff --git a/htools/Ganeti/HTools/Program/Hbal.hs b/htools/Ganeti/HTools/Program/Hbal.hs
index 424ff88edc0decaeb876e8c2073d73d7d1281342..77d38340fc0f2a82521e85d33ba75c71f217ad46 100644
--- a/htools/Ganeti/HTools/Program/Hbal.hs
+++ b/htools/Ganeti/HTools/Program/Hbal.hs
@@ -308,10 +308,9 @@ main = do
       Just grp ->
           case lookup (Group.idx grp) ngroups of
             Nothing -> do
-              -- TODO: while this is unlikely to happen, log here the
-              -- actual group data to help debugging
-              hPutStrLn stderr "Internal failure, missing group idx"
-              exitWith $ ExitFailure 1
+              -- This will only happen if there are no nodes assigned
+              -- to this group
+              return (Group.name grp, (Container.empty, Container.empty))
             Just cdata -> return (Group.name grp, cdata)
 
   unless oneline $ printf "Group size %d nodes, %d instances\n"
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index a3d9fc9d82681c82ec0a8a99aa02a7717876e900..4297b8a0dc48c4ad127994805196833846c1c690 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -3187,7 +3187,10 @@ class LUClusterRepairDiskSizes(NoHooksLU):
         locking.LEVEL_NODE: locking.ALL_SET,
         locking.LEVEL_INSTANCE: locking.ALL_SET,
         }
-    self.share_locks = _ShareAll()
+    self.share_locks = {
+      locking.LEVEL_NODE: 1,
+      locking.LEVEL_INSTANCE: 0,
+      }
 
   def DeclareLocks(self, level):
     if level == locking.LEVEL_NODE and self.wanted_names is not None:
@@ -6466,7 +6469,7 @@ class LUInstanceRename(LogicalUnit):
     new_name = self.op.new_name
     if self.op.name_check:
       hostname = netutils.GetHostname(name=new_name)
-      if hostname != new_name:
+      if hostname.name != new_name:
         self.LogInfo("Resolved given name '%s' to '%s'", new_name,
                      hostname.name)
       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
@@ -10340,9 +10343,10 @@ def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
   (moved, failed, jobs) = alloc_result
 
   if failed:
-    lu.LogWarning("Unable to evacuate instances %s",
-                  utils.CommaJoin("%s (%s)" % (name, reason)
-                                  for (name, reason) in failed))
+    failreason = utils.CommaJoin("%s (%s)" % (name, reason)
+                                 for (name, reason) in failed)
+    lu.LogWarning("Unable to evacuate instances %s", failreason)
+    raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
 
   if moved:
     lu.LogInfo("Instances to be moved: %s",
diff --git a/lib/jqueue.py b/lib/jqueue.py
index 3cf3b428762be1b26b4e509fc63fb34c2e819d33..d5ea3cb79b81d5bc7e8d36a5079227d6605d9724 100644
--- a/lib/jqueue.py
+++ b/lib/jqueue.py
@@ -1798,7 +1798,8 @@ class JobQueue(object):
     @return: a string representing the job identifier.
 
     """
-    assert count > 0
+    assert ht.TPositiveInt(count)
+
     # New number
     serial = self._last_serial + count
 
diff --git a/lib/opcodes.py b/lib/opcodes.py
index f00044a3b5bfc4fea279818460152a172d01f2ba..ed017cc1674b9d8f3b97d7911cfe28b919ae1582 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -1003,6 +1003,7 @@ class OpNodeMigrate(OpCode):
     ("iallocator", None, ht.TMaybeString,
      "Iallocator for deciding the target node for shared-storage instances"),
     ]
+  OP_RESULT = TJobIdListOnly
 
 
 class OpNodeEvacuate(OpCode):
diff --git a/test/ganeti.cmdlib_unittest.py b/test/ganeti.cmdlib_unittest.py
index 6d06200e7942c08516dcd556636abb3f97a2b151..522fe8b9f9660b327da673b741cd7cbe06be7590 100755
--- a/test/ganeti.cmdlib_unittest.py
+++ b/test/ganeti.cmdlib_unittest.py
@@ -28,6 +28,7 @@ import time
 import tempfile
 import shutil
 import operator
+import itertools
 
 from ganeti import constants
 from ganeti import mcpu
@@ -380,5 +381,71 @@ class TestClusterVerifyFiles(unittest.TestCase):
       ]))
 
 
+class _FakeLU:
+  def __init__(self):
+    self.warning_log = []
+    self.info_log = []
+
+  def LogWarning(self, text, *args):
+    self.warning_log.append((text, args))
+
+  def LogInfo(self, text, *args):
+    self.info_log.append((text, args))
+
+
+class TestLoadNodeEvacResult(unittest.TestCase):
+  def testSuccess(self):
+    for moved in [[], [
+      ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
+      ]]:
+      for early_release in [False, True]:
+        for use_nodes in [False, True]:
+          jobs = [
+            [opcodes.OpInstanceReplaceDisks().__getstate__()],
+            [opcodes.OpInstanceMigrate().__getstate__()],
+            ]
+
+          alloc_result = (moved, [], jobs)
+          assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
+
+          lu = _FakeLU()
+          result = cmdlib._LoadNodeEvacResult(lu, alloc_result,
+                                              early_release, use_nodes)
+
+          if moved:
+            (_, (info_args, )) = lu.info_log.pop(0)
+            for (instname, instgroup, instnodes) in moved:
+              self.assertTrue(instname in info_args)
+              if use_nodes:
+                for i in instnodes:
+                  self.assertTrue(i in info_args)
+              else:
+                self.assertTrue(instgroup in info_args)
+
+          self.assertFalse(lu.info_log)
+          self.assertFalse(lu.warning_log)
+
+          for op in itertools.chain(*result):
+            if hasattr(op.__class__, "early_release"):
+              self.assertEqual(op.early_release, early_release)
+            else:
+              self.assertFalse(hasattr(op, "early_release"))
+
+  def testFailed(self):
+    alloc_result = ([], [
+      ("inst5191.example.com", "errormsg21178"),
+      ], [])
+    assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
+
+    lu = _FakeLU()
+    self.assertRaises(errors.OpExecError, cmdlib._LoadNodeEvacResult,
+                      lu, alloc_result, False, False)
+    self.assertFalse(lu.info_log)
+    (_, (args, )) = lu.warning_log.pop(0)
+    self.assertTrue("inst5191.example.com" in args)
+    self.assertTrue("errormsg21178" in args)
+    self.assertFalse(lu.warning_log)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()