diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded
index 66e98dd856ee4ad05c425757e24dfd348f60e394..f7d76fabf64a5c417c197e4848ff39741319510a 100755
--- a/daemons/ganeti-noded
+++ b/daemons/ganeti-noded
@@ -484,6 +484,17 @@ class ServerObject(BaseHTTPServer.BaseHTTPRequestHandler):
     hr = backend.HooksRunner()
     return hr.RunHooks(hpath, phase, env)
 
+  # iallocator -----------------
+
+  @staticmethod
+  def perspective_iallocator_runner(params):
+    """Run an iallocator script.
+
+    """
+    name, idata = params
+    iar = backend.IAllocatorRunner()
+    return iar.Run(name, idata)
+
   # test -----------------------
 
   @staticmethod
diff --git a/lib/backend.py b/lib/backend.py
index c56053488c5141b5b0532aad97a699d13b5c718d..fcddf0e7dd78c79bd7fd15d6eab2790bb6bdc2a5 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -1633,6 +1633,42 @@ class HooksRunner(object):
     return rr
 
 
+class IAllocatorRunner(object):
+  """IAllocator runner.
+
+  This class is instantiated on the node side (ganeti-noded) and not on
+  the master side.
+
+  """
+  def Run(self, name, idata):
+    """Run an iallocator script.
+
+    Return value: tuple of:
+       - run status (one of the IARUN_ constants)
+       - stdout
+       - stderr
+       - fail reason (as from utils.RunResult)
+
+    """
+    alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
+                                  os.path.isfile)
+    if alloc_script is None:
+      return (constants.IARUN_NOTFOUND, None, None, None)
+
+    fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
+    try:
+      os.write(fd, idata)
+      os.close(fd)
+      result = utils.RunCmd([alloc_script, fin_name])
+      if result.failed:
+        return (constants.IARUN_FAILURE, result.stdout, result.stderr,
+                result.fail_reason)
+    finally:
+      os.unlink(fin_name)
+
+    return (constants.IARUN_SUCCESS, result.stdout, result.stderr, None)
+
+
 class DevCacheManager(object):
   """Simple class for managing a cache of block device information.
 
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 72e11b5f558cf50ad62bf75a6180284c3824305f..05484cf8ac2eb43ec2a41773b2f9f6ad22fcae0f 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -5005,29 +5005,26 @@ class IAllocator(object):
 
     self.in_text = serializer.Dump(self.in_data)
 
-  def Run(self, name, validate=True):
+  def Run(self, name, validate=True, call_fn=rpc.call_iallocator_runner):
     """Run an instance allocator and return the results.
 
     """
     data = self.in_text
 
-    alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
-                                  os.path.isfile)
-    if alloc_script is None:
-      raise errors.OpExecError("Can't find allocator '%s'" % name)
+    result = call_fn(self.sstore.GetMasterNode(), name, self.in_text)
 
-    fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
-    try:
-      os.write(fd, data)
-      os.close(fd)
-      result = utils.RunCmd([alloc_script, fin_name])
-      if result.failed:
+    if not isinstance(result, tuple) or len(result) != 4:
+      raise errors.OpExecError("Invalid result from master iallocator runner")
+
+    rcode, stdout, stderr, fail = result
+
+    if rcode == constants.IARUN_NOTFOUND:
+      raise errors.OpExecError("Can't find allocator '%s'" % name)
+    elif rcode == constants.IARUN_FAILURE:
         raise errors.OpExecError("Instance allocator call failed: %s,"
                                  " output: %s" %
-                                 (result.fail_reason, result.output))
-    finally:
-      os.unlink(fin_name)
-    self.out_text = result.stdout
+                                 (fail, stdout+stderr))
+    self.out_text = stdout
     if validate:
       self._ValidateResult()
 
diff --git a/lib/constants.py b/lib/constants.py
index 91fcf81aaf155c56ea12eed958c947b555bdb130..896fc4803e817f7965c6976657fba21dd72a86f6 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -190,3 +190,6 @@ IALLOCATOR_DIR_OUT = "out"
 IALLOCATOR_MODE_ALLOC = "allocate"
 IALLOCATOR_MODE_RELOC = "relocate"
 IALLOCATOR_SEARCH_PATH = _autoconf.IALLOCATOR_SEARCH_PATH
+IARUN_NOTFOUND = 1
+IARUN_FAILURE = 2
+IARUN_SUCCESS = 3
diff --git a/lib/rpc.py b/lib/rpc.py
index 57f0a8ef5263c986fd434432b04bd28974831550..0efb6a7bcc83824663938f9f74b70a6bcc2d40ee 100644
--- a/lib/rpc.py
+++ b/lib/rpc.py
@@ -577,6 +577,24 @@ def call_hooks_runner(node_list, hpath, phase, env):
   return result
 
 
+def call_iallocator_runner(node, name, idata):
+  """Call an iallocator on a remote node
+
+  Args:
+    - name: the iallocator name
+    - input: the json-encoded input string
+
+  This is a single-node call.
+
+  """
+  params = [name, idata]
+  c = Client("iallocator_runner", params)
+  c.connect(node)
+  c.run()
+  result = c.getresult().get(node, False)
+  return result
+
+
 def call_blockdev_snapshot(node, cf_bdev):
   """Request a snapshot of the given block device.