diff --git a/NEWS b/NEWS
index aec004633819a323f0627dfd737cc5c6fe48f16f..6ffbcb520508abbd418237ddc1d5211aef8d4426 100644
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,10 @@
 News
 ====
 
-Version 2.5.0 rc1
+Version 2.5.0 rc2
 -----------------
 
-*(Released Tue, 4 Oct 2011)*
+*(Released Tue, 18 Oct 2011)*
 
 Incompatible/important changes and bugfixes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -129,6 +129,14 @@ Misc
 - DRBD metadata volumes are overwritten with zeros during disk creation.
 
 
+Version 2.5.0 rc1
+-----------------
+
+*(Released Tue, 4 Oct 2011)*
+
+This was the first release candidate of the 2.5 series.
+
+
 Version 2.5.0 beta3
 -------------------
 
@@ -153,6 +161,18 @@ Version 2.5.0 beta1
 This was the first beta release of the 2.5 series.
 
 
+Version 2.4.5
+-------------
+
+*(unreleased)*
+
+- Fixed bug when parsing command line parameter values ending in
+  backslash
+- Fixed assertion error after unclean master shutdown
+- Disable HTTP client pool for RPC, significantly reducing memory usage
+  of master daemon
+
+
 Version 2.4.4
 -------------
 
diff --git a/configure.ac b/configure.ac
index 82e4a35a681ce6e1adaba9c10a39aae367a6c938..4234b467b282e257a72adccc7775681019499321 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], [~rc1])
+m4_define([gnt_version_suffix], [~rc2])
 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 977f24a68828bc7b2f8419491c8f0c8fedce9cf8..6dea27c617d191dd111ba466e4e2604053bf6a54 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -789,16 +789,15 @@ It supports the following commands: ``POST``.
 ``POST``
 ~~~~~~~~
 
-Takes the parameters ``mode`` (one of ``replace_on_primary``,
-``replace_on_secondary``, ``replace_new_secondary`` or
-``replace_auto``), ``disks`` (comma separated list of disk indexes),
-``remote_node`` and ``iallocator``.
+Returns a job ID.
+
+Body parameters:
 
-Either ``remote_node`` or ``iallocator`` needs to be defined when using
-``mode=replace_new_secondary``.
+.. opcode_params:: OP_INSTANCE_REPLACE_DISKS
+   :exclude: instance_name
 
-``mode`` is a mandatory parameter. ``replace_auto`` tries to determine
-the broken disk(s) on its own and replacing it.
+Ganeti 2.4 and below used query parameters. Those are deprecated and
+should no longer be used.
 
 
 ``/2/instances/[instance_name]/activate-disks``
diff --git a/lib/backend.py b/lib/backend.py
index 57b31a9caf5c06bc367cdf197e387436eb8f1e2c..2434c4495394cfcb7b43475bfb2133e54df39848 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -2660,7 +2660,10 @@ def JobQueueRename(old, new):
   _EnsureJobQueueFile(old)
   _EnsureJobQueueFile(new)
 
-  utils.RenameFile(old, new, mkdir=True)
+  getents = runtime.GetEnts()
+
+  utils.RenameFile(old, new, mkdir=True, mkdir_mode=0700,
+                   dir_uid=getents.masterd_uid, dir_gid=getents.masterd_gid)
 
 
 def BlockdevClose(instance_name, disks):
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index daec49dd5374f1225d7e0e7ea5272b73b97f7d83..62cebc29aabb81f57404409149a1447da3c841aa 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -3162,7 +3162,7 @@ class LUGroupVerifyDisks(NoHooksLU):
       # any leftover items in nv_dict are missing LVs, let's arrange the data
       # better
       for key, inst in nv_dict.iteritems():
-        res_missing.setdefault(inst, []).append(key)
+        res_missing.setdefault(inst, []).append(list(key))
 
     return (res_nodes, list(res_instances), res_missing)
 
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 0f5de5a8862ed3a332497ce4a0c41596f62e8e68..f00044a3b5bfc4fea279818460152a172d01f2ba 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -673,7 +673,8 @@ class OpGroupVerifyDisks(OpCode):
     ht.TAnd(ht.TIsLength(3),
             ht.TItems([ht.TDictOf(ht.TString, ht.TString),
                        ht.TListOf(ht.TString),
-                       ht.TDictOf(ht.TString, ht.TListOf(ht.TString))]))
+                       ht.TDictOf(ht.TString,
+                                  ht.TListOf(ht.TListOf(ht.TString)))]))
 
 
 class OpClusterRepairDiskSizes(OpCode):
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 213712dddc7640591e00c3ac1c5abba66211c19a..f41b6fca97432d5036e78255c27f0021c0c19c9e 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -63,6 +63,10 @@ REPLACE_DISK_SECONDARY = "replace_on_secondary"
 REPLACE_DISK_CHG = "replace_new_secondary"
 REPLACE_DISK_AUTO = "replace_auto"
 
+NODE_EVAC_PRI = "primary-only"
+NODE_EVAC_SEC = "secondary-only"
+NODE_EVAC_ALL = "all"
+
 NODE_ROLE_DRAINED = "drained"
 NODE_ROLE_MASTER_CANDIATE = "master-candidate"
 NODE_ROLE_MASTER = "master"
@@ -956,7 +960,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
-                           remote_node=None, iallocator=None, dry_run=False):
+                           remote_node=None, iallocator=None):
     """Replaces disks on an instance.
 
     @type instance: str
@@ -971,8 +975,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @type iallocator: str or None
     @param iallocator: instance allocator plugin to use (for use with
                        replace_auto mode)
-    @type dry_run: bool
-    @param dry_run: whether to perform a dry run
 
     @rtype: string
     @return: job id
@@ -982,18 +984,17 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       ("mode", mode),
       ]
 
-    if disks:
+    # TODO: Convert to body parameters
+
+    if disks is not None:
       query.append(("disks", ",".join(str(idx) for idx in disks)))
 
-    if remote_node:
+    if remote_node is not None:
       query.append(("remote_node", remote_node))
 
-    if iallocator:
+    if iallocator is not None:
       query.append(("iallocator", iallocator))
 
-    if dry_run:
-      query.append(("dry-run", 1))
-
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/replace-disks" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
@@ -1287,7 +1288,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
   def EvacuateNode(self, node, iallocator=None, remote_node=None,
                    dry_run=False, early_release=None,
-                   primary=None, secondary=None, accept_old=False):
+                   mode=None, accept_old=False):
     """Evacuates instances from a Ganeti node.
 
     @type node: str
@@ -1300,10 +1301,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param dry_run: whether to perform a dry run
     @type early_release: bool
     @param early_release: whether to enable parallelization
-    @type primary: bool
-    @param primary: Whether to evacuate primary instances
-    @type secondary: bool
-    @param secondary: Whether to evacuate secondary instances
+    @type mode: string
+    @param mode: Node evacuation mode
     @type accept_old: bool
     @param accept_old: Whether caller is ready to accept old-style (pre-2.5)
         results
@@ -1326,6 +1325,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       query.append(("dry-run", 1))
 
     if _NODE_EVAC_RES1 in self.GetFeatures():
+      # Server supports body parameters
       body = {}
 
       if iallocator is not None:
@@ -1334,10 +1334,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
         body["remote_node"] = remote_node
       if early_release is not None:
         body["early_release"] = early_release
-      if primary is not None:
-        body["primary"] = primary
-      if secondary is not None:
-        body["secondary"] = secondary
+      if mode is not None:
+        body["mode"] = mode
     else:
       # Pre-2.5 request format
       body = None
@@ -1347,7 +1345,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                              " not accept old-style results (parameter"
                              " accept_old)")
 
-      if primary or primary is None or not (secondary is None or secondary):
+      # Pre-2.5 servers can only evacuate secondaries
+      if mode is not None and mode != NODE_EVAC_SEC:
         raise GanetiApiError("Server can only evacuate secondary instances")
 
       if iallocator:
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index 74fdf228c5ffbfb7e014505b5b96219b13e679ef..ab8927045c1f4abd8141191b338efc8782a2d493 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -1021,16 +1021,19 @@ def _ParseInstanceReplaceDisksRequest(name, data):
 
   # Parse disks
   try:
-    raw_disks = data["disks"]
+    raw_disks = data.pop("disks")
   except KeyError:
     pass
   else:
-    if not ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
-      # Backwards compatibility for strings of the format "1, 2, 3"
-      try:
-        data["disks"] = [int(part) for part in raw_disks.split(",")]
-      except (TypeError, ValueError), err:
-        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
+    if raw_disks:
+      if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
+        data["disks"] = raw_disks
+      else:
+        # Backwards compatibility for strings of the format "1, 2, 3"
+        try:
+          data["disks"] = [int(part) for part in raw_disks.split(",")]
+        except (TypeError, ValueError), err:
+          raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
 
   return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override)
 
@@ -1043,7 +1046,20 @@ class R_2_instances_name_replace_disks(baserlib.R_Generic):
     """Replaces disks on an instance.
 
     """
-    op = _ParseInstanceReplaceDisksRequest(self.items[0], self.request_body)
+    if self.request_body:
+      body = self.request_body
+    elif self.queryargs:
+      # Legacy interface, do not modify/extend
+      body = {
+        "remote_node": self._checkStringVariable("remote_node", default=None),
+        "mode": self._checkStringVariable("mode", default=None),
+        "disks": self._checkStringVariable("disks", default=None),
+        "iallocator": self._checkStringVariable("iallocator", default=None),
+        }
+    else:
+      body = {}
+
+    op = _ParseInstanceReplaceDisksRequest(self.items[0], body)
 
     return baserlib.SubmitJob([op])
 
diff --git a/lib/tools/ensure_dirs.py b/lib/tools/ensure_dirs.py
index 7abcce2a2bcf556326fd9b8df9663bac52a015b1..8cf01ae7a13632743d78ce9fe39821c33a72c106 100644
--- a/lib/tools/ensure_dirs.py
+++ b/lib/tools/ensure_dirs.py
@@ -227,6 +227,8 @@ def GetPaths():
      getent.masterd_uid, getent.masterd_gid, False),
     (constants.JOB_QUEUE_SERIAL_FILE, FILE, 0600,
      getent.masterd_uid, getent.masterd_gid, False),
+    (constants.JOB_QUEUE_VERSION_FILE, FILE, 0600,
+     getent.masterd_uid, getent.masterd_gid, False),
     (constants.JOB_QUEUE_ARCHIVE_DIR, DIR, 0700,
      getent.masterd_uid, getent.masterd_gid),
     (rapi_dir, DIR, 0750, getent.rapi_uid, getent.masterd_gid),
diff --git a/lib/utils/io.py b/lib/utils/io.py
index 91899a210f4d3a70607f51d1549066af8acbb319..5d3a5e643db840a9ade4ca0a2f32e5263a9355bb 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -295,7 +295,8 @@ def RemoveDir(dirname):
       raise
 
 
-def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
+def RenameFile(old, new, mkdir=False, mkdir_mode=0750, dir_uid=None,
+               dir_gid=None):
   """Renames a file.
 
   @type old: string
@@ -306,6 +307,10 @@ def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
   @param mkdir: Whether to create target directory if it doesn't exist
   @type mkdir_mode: int
   @param mkdir_mode: Mode for newly created directories
+  @type dir_uid: int
+  @param dir_uid: The uid for the (if fresh created) dir
+  @type dir_gid: int
+  @param dir_gid: The gid for the (if fresh created) dir
 
   """
   try:
@@ -316,7 +321,10 @@ def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
     # as efficient.
     if mkdir and err.errno == errno.ENOENT:
       # Create directory and try again
-      Makedirs(os.path.dirname(new), mode=mkdir_mode)
+      dir_path = os.path.dirname(new)
+      Makedirs(dir_path, mode=mkdir_mode)
+      if not (dir_uid is None or dir_gid is None):
+        os.chown(dir_path, dir_uid, dir_gid)
 
       return os.rename(old, new)
 
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 8b0c33889f36922cbf7db315afad918bf523811e..a2a4eb05819883d13cf622b3c6a06294bd676fdc 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -382,6 +382,7 @@ def RunHardwareFailureTests(instance, pnode, snode):
   if qa_config.TestEnabled("instance-replace-disks"):
     othernode = qa_config.AcquireNode(exclude=[pnode, snode])
     try:
+      RunTestIf("rapi", qa_rapi.TestRapiInstanceReplaceDisks, instance)
       RunTest(qa_instance.TestReplaceDisks,
               instance, pnode, snode, othernode)
     finally:
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index 02218463c349dacf27c39e0052ef1d4c9a18e83e..a4c5921ce1386960ccaaa578e9f87378ba690b6b 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -618,6 +618,14 @@ def TestRapiInstanceReinstall(instance):
   _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"]))
 
 
+def TestRapiInstanceReplaceDisks(instance):
+  """Test replacing instance disks via RAPI"""
+  _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"],
+    mode=constants.REPLACE_DISK_AUTO, disks=[]))
+  _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"],
+    mode=constants.REPLACE_DISK_SEC, disks="0"))
+
+
 def TestRapiInstanceModify(instance):
   """Test modifying instance via RAPI"""
   def _ModifyInstance(**kwargs):
diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py
index dc972d2875cb5c7440de133a5ca633574ad3fd05..1067656d3c1fd3ea2a65df3ea976ba2fac3d49c2 100755
--- a/test/ganeti.rapi.client_unittest.py
+++ b/test/ganeti.rapi.client_unittest.py
@@ -165,6 +165,11 @@ class TestConstants(unittest.TestCase):
     self.assertEqual(client.JOB_STATUS_FINALIZED, constants.JOBS_FINALIZED)
     self.assertEqual(client.JOB_STATUS_ALL, constants.JOB_STATUS_ALL)
 
+    # Node evacuation
+    self.assertEqual(client.NODE_EVAC_PRI, constants.IALLOCATOR_NEVAC_PRI)
+    self.assertEqual(client.NODE_EVAC_SEC, constants.IALLOCATOR_NEVAC_SEC)
+    self.assertEqual(client.NODE_EVAC_ALL, constants.IALLOCATOR_NEVAC_ALL)
+
     # Legacy name
     self.assertEqual(client.JOB_STATUS_WAITLOCK, constants.JOB_STATUS_WAITING)
 
@@ -669,24 +674,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
   def testReplaceInstanceDisks(self):
     self.rapi.AddResponse("999")
     job_id = self.client.ReplaceInstanceDisks("instance-name",
-        disks=[0, 1], dry_run=True, iallocator="hail")
+        disks=[0, 1], iallocator="hail")
     self.assertEqual(999, job_id)
     self.assertHandler(rlib2.R_2_instances_name_replace_disks)
     self.assertItems(["instance-name"])
     self.assertQuery("disks", ["0,1"])
     self.assertQuery("mode", ["replace_auto"])
     self.assertQuery("iallocator", ["hail"])
-    self.assertDryRun()
 
     self.rapi.AddResponse("1000")
     job_id = self.client.ReplaceInstanceDisks("instance-bar",
-        disks=[1], mode="replace_on_secondary", remote_node="foo-node",
-        dry_run=True)
+        disks=[1], mode="replace_on_secondary", remote_node="foo-node")
     self.assertEqual(1000, job_id)
     self.assertItems(["instance-bar"])
     self.assertQuery("disks", ["1"])
     self.assertQuery("remote_node", ["foo-node"])
-    self.assertDryRun()
 
     self.rapi.AddResponse("5175")
     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
@@ -863,11 +865,16 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
 
     self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_EVAC_RES1]))
     self.rapi.AddResponse("8888")
-    job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
+    job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True,
+                                      mode=constants.IALLOCATOR_NEVAC_ALL,
+                                      early_release=True)
     self.assertEqual(8888, job_id)
     self.assertItems(["node-3"])
-    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
-                     { "iallocator": "hail", })
+    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()), {
+      "iallocator": "hail",
+      "mode": "all",
+      "early_release": True,
+      })
     self.assertDryRun()
 
     self.assertRaises(client.GanetiApiError,
@@ -881,34 +888,26 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
                       "node-4", accept_old=False)
     self.assertEqual(self.rapi.CountPending(), 0)
 
-    self.rapi.AddResponse(serializer.DumpJson([]))
-    self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
-                      "node-4", accept_old=True)
-    self.assertEqual(self.rapi.CountPending(), 0)
-
-    self.rapi.AddResponse(serializer.DumpJson([]))
-    self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
-                      "node-4", accept_old=True, primary=True)
-    self.assertEqual(self.rapi.CountPending(), 0)
+    for mode in [client.NODE_EVAC_PRI, client.NODE_EVAC_ALL]:
+      self.rapi.AddResponse(serializer.DumpJson([]))
+      self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
+                        "node-4", accept_old=True, mode=mode)
+      self.assertEqual(self.rapi.CountPending(), 0)
 
     self.rapi.AddResponse(serializer.DumpJson([]))
-    self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
-                      "node-4", accept_old=True, secondary=False)
+    self.rapi.AddResponse(serializer.DumpJson("21533"))
+    result = self.client.EvacuateNode("node-3", iallocator="hail",
+                                      dry_run=True, accept_old=True,
+                                      mode=client.NODE_EVAC_SEC,
+                                      early_release=True)
+    self.assertEqual(result, "21533")
+    self.assertItems(["node-3"])
+    self.assertQuery("iallocator", ["hail"])
+    self.assertQuery("early_release", ["1"])
+    self.assertFalse(self.rapi.GetLastRequestData())
+    self.assertDryRun()
     self.assertEqual(self.rapi.CountPending(), 0)
 
-    for sec in [True, None]:
-      self.rapi.AddResponse(serializer.DumpJson([]))
-      self.rapi.AddResponse(serializer.DumpJson([["res", "foo"]]))
-      result = self.client.EvacuateNode("node-3", iallocator="hail",
-                                        dry_run=True, accept_old=True,
-                                        primary=False, secondary=sec)
-      self.assertEqual(result, [["res", "foo"]])
-      self.assertItems(["node-3"])
-      self.assertQuery("iallocator", ["hail"])
-      self.assertFalse(self.rapi.GetLastRequestData())
-      self.assertDryRun()
-      self.assertEqual(self.rapi.CountPending(), 0)
-
   def testMigrateNode(self):
     self.rapi.AddResponse(serializer.DumpJson([]))
     self.rapi.AddResponse("1111")
diff --git a/test/ganeti.rapi.rlib2_unittest.py b/test/ganeti.rapi.rlib2_unittest.py
index 6584c38e8cd4328569d30c84bed826cacbbd2d28..de82745567ae868a79cee2ec0f746a712bb5bc13 100755
--- a/test/ganeti.rapi.rlib2_unittest.py
+++ b/test/ganeti.rapi.rlib2_unittest.py
@@ -529,6 +529,14 @@ class TestParseInstanceReplaceDisksRequest(unittest.TestCase):
     self.assertFalse(hasattr(op, "iallocator"))
     self.assertFalse(hasattr(op, "disks"))
 
+  def testNoDisks(self):
+    self.assertRaises(http.HttpBadRequest, self.Parse, "inst20661", {})
+
+    for disks in [None, "", {}]:
+      self.assertRaises(http.HttpBadRequest, self.Parse, "inst20661", {
+        "disks": disks,
+        })
+
   def testWrong(self):
     self.assertRaises(http.HttpBadRequest, self.Parse, "inst",
                       { "mode": constants.REPLACE_DISK_AUTO,