[Kimchi-devel] [PATCH v2 1/4] Github #329: kimchid script changes

Daniel Barboza danielhb at linux.vnet.ibm.com
Tue Apr 8 20:50:27 UTC 2014


From: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>

The kimchid script was revamped to launch 3 processes:

- a cherrypy process that will act as frontend, running as
non-root, providing the html templates;
- another cherrypy process running as backend, with root privileges;
- a reverse proxy (nginx) that will forward all requests made to
the kimchi port to the frontend/backend.

The same options that the previous kimchid scripts provided are
available in this new version.

Signed-off-by: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>
---
 src/kimchid.in | 200 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 174 insertions(+), 26 deletions(-)

diff --git a/src/kimchid.in b/src/kimchid.in
index 8b63b57..51d4026 100644
--- a/src/kimchid.in
+++ b/src/kimchid.in
@@ -2,7 +2,7 @@
 #
 # Project Kimchi
 #
-# Copyright IBM, Corp. 2013
+# Copyright IBM, Corp. 2014
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -16,18 +16,27 @@
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301  USA
 
 import logging
 import os
+import pwd
+import re
+import signal
+import subprocess
 import sys
+import time
 sys.path.insert(1, '@pythondir@')
 
+from string import Template
+
 import kimchi.server
 import kimchi.config
 
 from kimchi.config import config, paths
-from optparse import OptionParser
+from kimchi.utils import parse_command_line_options
+
 
 if not paths.installed:
     sys.path.append(paths.prefix)
@@ -35,30 +44,169 @@ if not paths.installed:
 ACCESS_LOG = "kimchi-access.log"
 ERROR_LOG = "kimchi-error.log"
 
+
+def create_proxy_config(p_port, f_port, b_port,
+                        p_ssl_port, f_ssl_port, b_ssl_port):
+    """Create nginx configuration file based on current ports config
+
+    To allow flexibility in which port kimchi runs, we need the same
+    flexibility with the nginx proxy. This method creates the config
+    file dynamically by using 'nginx.conf.in' as a template, creating
+    the file 'nginx_kimchi.config' which will be used to launch the
+    proxy.
+
+    Arguments:
+    p_port - proxy port
+    f_port - frontend port
+    b_port - backend port
+    p_ssl_port - proxy SSL port
+    f_ssl_port - frontend SSL port
+    b_ssl_port - backend SSL port
+    """
+
+    # get SSL paths to be used by the proxy
+    config_dir = paths.conf_dir
+    cert = '%s/kimchi-cert.pem' % config_dir
+    key = '%s/kimchi-key.pem' % config_dir
+
+    with open(os.path.join(paths.conf_dir, "nginx.conf.in")) as myfile:
+        data = myfile.read()
+
+    data = Template(data)
+    data = data.safe_substitute(proxy_port=p_port,
+                                frontend_port=f_port,
+                                backend_port=b_port,
+                                proxy_ssl_port=p_ssl_port,
+                                frontend_ssl_port=f_ssl_port,
+                                backend_ssl_port=b_ssl_port,
+                                cert_pem=cert, cert_key=key)
+
+    config_file = open(os.getcwd() + "/nginx_kimchi.conf", "w")
+    config_file.write(data)
+    config_file.close()
+
+
+def start_proxy():
+    """Start nginx reverse proxy."""
+    config_file = os.getcwd() + "/nginx_kimchi.conf"
+    cmd = ['nginx', '-c', config_file]
+    subprocess.call(cmd)
+
+
+def terminate_proxy():
+    """Stop nginx process."""
+    term_proxy_cmd = ['nginx', '-s', 'stop']
+    subprocess.call(term_proxy_cmd)
+
+
 def main(options):
-    host = config.get("server", "host")
-    port = config.get("server", "port")
-    ssl_port = config.get("server", "ssl_port")
-    runningEnv = config.get('server', 'environment')
-    logDir = config.get("logging", "log_dir")
-    logLevel = config.get("logging", "log_level")
-
-    parser = OptionParser()
-    parser.add_option('--host', type="string", default=host, help="Hostname to listen on")
-    parser.add_option('--port', type="int", default=port, help="Port to listen on")
-    parser.add_option('--ssl-port', type="int", default=ssl_port, help="Enable a SSL server on the given port")
-    parser.add_option('--log-level', default=logLevel, help="Logging level")
-    parser.add_option('--access-log', default=os.path.join(logDir,ACCESS_LOG), help="Access log file")
-    parser.add_option('--error-log', default=os.path.join(logDir,ERROR_LOG), help="Error log file")
-    parser.add_option('--environment', default=runningEnv, help="Running environment of kimchi server")
-    parser.add_option('--test', action='store_true', help="Run server in mock model")
-    (options, args) = parser.parse_args()
-
-    # Add non-option arguments
-    setattr(options, 'ssl_cert', config.get('server', 'ssl_cert'))
-    setattr(options, 'ssl_key', config.get('server', 'ssl_key'))
-
-    kimchi.server.main(options)
+    # Script must run as root or with sudo.
+    if not os.geteuid() == 0:
+        sys.exit("\nMust be root to run this script. Exiting ...\n")
+
+    # The following method is used when the process receives a
+    # SIGINT signal (CTRL+C).
+    def terminate_kimchi(signal, frame):
+        terminate_proxy()
+        sys.exit(0)
+    signal.signal(signal.SIGINT, terminate_kimchi)
+
+    # User that will execute the frontend will own this
+    # process temporarily. First we need to detect if the script
+    # was run with SUDO or from the root directly. If
+    # run with SUDO, the user that fired the execution will
+    # take over (this user is registered at the 'SUDO_USER' env
+    # variable). Otherwise the user kimchi will take over.
+    #
+    # The frontend will **NOT** be run as root under any
+    # circunstance using this script.
+    if os.getenv("SUDO_USER") is not None:
+        temp_user = os.getenv("SUDO_USER")
+    else:
+        temp_user = 'kimchi'
+
+    # Drop execution privileges temporarily
+    os.seteuid(pwd.getpwnam(temp_user).pw_uid)
+
+    # Validate command line options before forwarding them
+    # to the cherrypy processes.
+    parse_command_line_options()
+
+    # Copy the options (sys.argv[:1]) into 2 other lists. That
+    # way we can edit the copies with modified parameters to
+    # use in the cherrypy processes.
+    argv_frontend = list(options)
+    argv_backend = list(options)
+
+    ssl_port_match = re.compile("^--ssl-port.*")
+    port_match = re.compile("^--port.*")
+
+    port_index = None
+    ssl_port_index = None
+
+    # Default ports
+    proxy_port = 8000
+    proxy_ssl_port = 8001
+
+    # Search for the '--port=' and '--ssl-port=' arguments to verify
+    # if the user wants to run in a port other than the default.
+    for i, value in enumerate(options):
+        if re.search(port_match, value):
+            proxy_port = int(value.partition("=")[2])
+            port_index = i
+        elif re.search(ssl_port_match, value):
+            proxy_ssl_port = int(value.partition("=")[2])
+            ssl_port_index = i
+
+    # The max value between port and proxy_port
+    # will be used to calculate the frontend and
+    # backend ports.
+    next_port = max(proxy_port, proxy_ssl_port) + 1
+    frontend_port = next_port
+    next_port += 1
+    frontend_ssl_port = next_port
+    next_port += 1
+    backend_port = next_port
+    next_port += 1
+    backend_ssl_port = next_port
+
+    if port_index is not None:
+        # edit args to be used by the front/backend
+        argv_frontend[port_index] = "--port="+str(frontend_port)
+        argv_backend[port_index] = "--port="+str(backend_port)
+    else:
+        # add port info to the front/backend options
+        argv_frontend += ["--port="+str(frontend_port)]
+        argv_backend += ["--port="+str(backend_port)]
+
+    if ssl_port_index is not None:
+        # edit args to be used by the front/backend
+        argv_frontend[ssl_port_index] = "--ssl-port="+str(frontend_ssl_port)
+        argv_backend[ssl_port_index] = "--ssl-port="+str(backend_ssl_port)
+    else:
+        # add ssl-port info to the front/backend options
+        argv_frontend += ["--ssl-port="+str(frontend_ssl_port)]
+        argv_backend += ["--ssl-port="+str(backend_ssl_port)]
+
+    # launch frontend with edited options
+    command = os.getcwd() + '/src/kimchid_server'
+    cmd = [command] + argv_frontend
+    frontend = subprocess.Popen(cmd)
+
+    # Retrieve root access before launching the backend.
+    os.seteuid(0)
+
+    # Launch the cherrypy backend process.
+    cmd = [command] + argv_backend + ['--backend'] + ['--log_screen']
+    backend = subprocess.Popen(cmd, stderr=subprocess.STDOUT)
+
+    # Launch reverse proxy: create config file and start.
+    create_proxy_config(proxy_port, frontend_port, backend_port,
+                        proxy_ssl_port, frontend_ssl_port, backend_ssl_port)
+    start_proxy()
+
+    # suspend execution until interruption (SIGINT, SIGKILL)
+    signal.pause()
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
-- 
1.8.3.1




More information about the Kimchi-devel mailing list