From 8d528b7cce3db73301cf3d82962cacbce492ad48 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Mon, 28 Apr 2008 09:47:23 +0000
Subject: [PATCH] Move iallocator script execution to ganeti-noded

Currently the iallocator execution takes place in the master, which is a
violation of the current architecture, and will create problems with a
threaded master daemon.

This patch moves the execution to the backend, similar to the hooks
runner, by:
  - introducing a new class that handles the execution in the backend
    (and could be used also for listing the allocators, etc.)
  - introducing a new rpc call
  - replacing the actual execution in IAllocator.Run() with a rpc call

This passes burnin with the dumb allocator

Reviewed-by: imsnah
---
 daemons/ganeti-noded | 11 +++++++++++
 lib/backend.py       | 36 ++++++++++++++++++++++++++++++++++++
 lib/cmdlib.py        | 27 ++++++++++++---------------
 lib/constants.py     |  3 +++
 lib/rpc.py           | 18 ++++++++++++++++++
 5 files changed, 80 insertions(+), 15 deletions(-)

diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded
index 66e98dd85..f7d76fabf 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 c56053488..fcddf0e7d 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 72e11b5f5..05484cf8a 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 91fcf81aa..896fc4803 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 57f0a8ef5..0efb6a7bc 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.
 
-- 
GitLab