
On 04/07/2014 04:55 PM, Daniel Barboza wrote:
From: Daniel Henrique Barboza <danielhb@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@linux.vnet.ibm.com> --- src/kimchid.in | 202 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 175 insertions(+), 27 deletions(-)
diff --git a/src/kimchid.in b/src/kimchid.in index 8b63b57..9e24797 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 -sys.path.insert(1, '@pythondir@') +import time +sys.path.insert(1, '/usr/lib/python2.7/site-packages')
not sure if all supported distros came with python2.7. But, would be nice to check if the path exists: if os.path.exists('/usr/lib/python2.7/site-packages'): ...
+ +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:]))
-- Ramon Nunes Medeiros RHEV-H Blue for Troy & Sparta Focal Point Software Engineer - Linux Technology Center Brazil IBM Systems & Technology Group Phone : +55 19 2132 7878 ramonn@br.ibm.com