
cherokee at cherokee-project
Jan 29, 2010, 2:20 PM
Post #1 of 1
(111 views)
Permalink
|
|
[4204] cherokee/branches/adminctk/admin: WIP: Plugin/Module support
|
|
Revision: 4204 http://svn.cherokee-project.com/changeset/4204 Author: alo Date: 2010-01-29 23:22:41 +0100 (Fri, 29 Jan 2010) Log Message: ----------- WIP: Plugin/Module support Modified Paths: -------------- cherokee/branches/adminctk/admin/PageGeneral.py Added Paths: ----------- cherokee/branches/adminctk/admin/Cherokee.py cherokee/branches/adminctk/admin/config_version.py cherokee/branches/adminctk/admin/consts.py Added: cherokee/branches/adminctk/admin/Cherokee.py =================================================================== --- cherokee/branches/adminctk/admin/Cherokee.py (rev 0) +++ cherokee/branches/adminctk/admin/Cherokee.py 2010-01-29 22:22:41 UTC (rev 4204) @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +# +# Cherokee-admin +# +# Authors: +# Alvaro Lopez Ortega <alvaro [at] alobbs> +# +# Copyright (C) 2001-2010 Alvaro Lopez Ortega +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +import os +import sys +import time +import stat +import signal + +from select import select +from subprocess import * + +from consts import * +from configured import * +from config_version import * + +DEFAULT_DELAY = 2 +WAIT_SERVER_STOP = 10 +PID_TIMEOUT = 2 +DEFAULT_PATH = ['/usr/local/sbin', '/usr/local/bin', + '/usr/sbin', '/usr/bin', '/sbin', '/bin'] + +DEFAULT_PID_LOCATIONS = [ + '/var/run/cherokee.pid', + os.path.join (PREFIX, 'var/run/cherokee.pid') +] + +CHEROKEE_MIN_DEFAULT_CONFIG = """# Default configuration +server!pid_file = %s +vserver!1!nick = default +vserver!1!document_root = /tmp +vserver!1!rule!1!match = default +vserver!1!rule!1!handler = common +""" % (DEFAULT_PID_LOCATIONS[0]) + +# Cherokee Management 'factory': +# + +cherokee_management = None + +def cherokee_management_get (cfg): + global cherokee_management + + # Fast path + if cherokee_management: + return cherokee_management + + # Needs to create a new object + cherokee_management = CherokeeManagement(cfg) + return cherokee_management + +def cherokee_management_reset (): + global cherokee_management + cherokee_management = None + + +# Cherokee Management class +# + +class CherokeeManagement: + def __init__ (self, cfg): + self._cfg = cfg + self._pid_prev = None + self._pid_prev_time = 0 + self._pid_mtime = None + self._is_child = False + + # Public + # + + def save (self, restart=None): + self._cfg.save() + + if not restart or restart.lower() == 'no': + return + if restart.lower() == 'graceful': + self._restart (graceful=True) + else: + self._restart() + + def is_alive (self): + pid = self._get_pid() + if not pid: + return False + + return is_PID_alive (pid) + + def launch (self): + def daemonize(): + os.setsid() + + # Ensure the a minimum path is set + environ = os.environ.copy() + if not "PATH" in environ: + environ["PATH"] = ':'.join(DEFAULT_PATH) + + p = Popen ([CHEROKEE_SERVER, '--admin_child', '-C', self._cfg.file], + stdout=PIPE, stderr=PIPE, env=environ, + preexec_fn=daemonize, close_fds=True) + + stdout_f, stderr_f = (p.stdout, p.stderr) + stdout_fd, stderr_fd = stdout_f.fileno(), stderr_f.fileno() + stdout, stderr = '', '' + + while True: + r,w,e = select([stdout_fd, stderr_fd], [], [stdout_fd, stderr_fd], 1) + + if e: + return _("Could not access file descriptors: ") + str(e) + + if stdout_fd in r: + stdout += stdout_f.read(1) + if stderr_fd in r: + stderr += stderr_f.read(1) + + nl = stderr.find('\n') + if nl != -1: + for e in ["{'type': ", 'ERROR', '(error) ', '(critical) ']: + if e in stderr: + self.__stop_process (p.pid) + return stderr + stderr = stderr[nl+1:] + + if stdout.count('\n') > 1: + break + + self._pid_prev = p.pid + self._is_child = True + time.sleep (DEFAULT_DELAY) + return None + + def stop (self): + # Stop Cherokee Guardian + pid = self._get_pid() + + self.__stop_process (pid) + self._is_child = False + + def create_config (self, file, template_file): + if os.path.exists (file): + return True + + dirname = os.path.dirname(file) + if not os.path.exists (dirname): + try: + os.mkdir (dirname) + except: + return False + + content = "config!version = %s\n" %(config_version_get_current()) + + conf_sample = os.path.join(CHEROKEE_ADMINDIR, template_file) + if os.path.exists (conf_sample): + content += open(conf_sample, 'r').read() + else: + content += CHEROKEE_MIN_DEFAULT_CONFIG + + try: + f = open(file, 'w+') + f.write (content) + f.close() + except: + return False + + return True + + # Protected + # + def _get_pid (self): + # Read the PID file + pid_file = self._cfg.get_val("server!pid_file") + if pid_file: + try: + s = os.stat(pid_file) + mtime = s[stat.ST_MTIME] + except: + mtime = None + + if (mtime and + mtime != self._pid_mtime): + self._pid_prev = self.__read_pid_file (pid_file) + self._pid_mtime = mtime + + return self._pid_prev + + # Previous PID may work + now = time.time() + if ((self._pid_prev_time and self._pid_prev) and + (self._pid_prev_time + PID_TIMEOUT > now)): + return self._pid_prev + + # Try to figure the PID + pid = self.__try_to_figure_pid() + if pid: + self._pid_prev = pid + self._pid_prev_time = now + return self._pid_prev + + return self._pid_prev + + def _restart (self, graceful=False): + pid = self._get_pid() + if not pid: + return + + try: + if graceful: + os.kill (pid, signal.SIGHUP) + else: + os.kill (pid, signal.SIGUSR1) + except: + pass + + # Private + # + def __try_to_figure_pid (self): + try: + f = os.popen ("ps aux") + ps = f.read() + except: + return None + + try: + f.close() + except: pass + + for l in ps.split("\n"): + if "cherokee " in l and "-C %s"%(self._cfg.file) in l: + pid = filter (lambda x: x.isdigit(), l.split())[0] + return int(pid) + return None + + def __read_pid_file (self, file): + if not os.access (file, os.R_OK): + return + f = open (file, "r") + try: + pid = int(f.readline()) + except: + return None + try: f.close() + except: pass + return pid + + def __stop_process (self, pid): + if not pid: + return + + try: + os.kill (pid, signal.SIGTERM) + self.__wait_process (pid) + except: + pass + + def __wait_process (self, pid): + if self._is_child: + try: os.waitpid (pid, 0) + except: pass + else: + retries = 0 + while is_PID_alive (pid) and (retries < WAIT_SERVER_STOP): + time.sleep (1) + retries += 1 + +def is_PID_alive (pid): + if not pid: + return False + + if sys.platform.startswith('linux') or \ + sys.platform.startswith('sunos') or \ + sys.platform.startswith('irix'): + return os.path.exists('/proc/%s'%(pid)) + + elif sys.platform == 'darwin' or \ + "bsd" in sys.platform.lower(): + f = os.popen('/bin/ps -p %s'%(pid)) + alive = len(f.readlines()) >= 2 + try: + f.close() + except: pass + return alive + + elif sys.platform == 'win32': + None + + raise 'TODO' + + +# +# Plug-in checking +# + +_server_info = None + +def cherokee_get_server_info (): + global _server_info + + if _server_info == None: + try: + f = os.popen ("%s -i" % (CHEROKEE_WORKER)) + except: + msg = _("ERROR: Couldn't execute '%s -i'") + print msg % (CHEROKEE_WORKER) + + _server_info = f.read() + + try: + f.close() + except: pass + + return _server_info + + +_built_in_lists = {} + +def cherokee_build_info_has (filter, module): + # Let's see whether it's built-in + global _built_in_lists + + if not _built_in_lists.has_key(filter): + _built_in_lists[filter] = {} + + cont = cherokee_get_server_info() + + try: + filter_string = " %s: " % (filter) + for l in cont.split("\n"): + if l.startswith(filter_string): + line = l.replace (filter_string, "") + _built_in_lists[filter] = line.split(" ") + break + except: + pass + + return module in _built_in_lists[filter] + +def cherokee_has_plugin (module): + # Check for the dynamic plug-in + try: + mods = filter(lambda x: module in x, os.listdir(CHEROKEE_PLUGINDIR)) + if len(mods) >= 1: + return True + except: + pass + + return cherokee_build_info_has ("Built-in", module) + +def cherokee_has_polling_method (module): + return cherokee_build_info_has ("Polling methods", module) + +def modules_available (module_list): + new_module_list = [] + + for entry in module_list: + assert (type(entry) == tuple) + assert (len(entry) == 2) + plugin, name = entry + + if not len(plugin) or \ + cherokee_has_plugin (plugin): + new_module_list.append(entry) + + return new_module_list Modified: cherokee/branches/adminctk/admin/PageGeneral.py =================================================================== --- cherokee/branches/adminctk/admin/PageGeneral.py 2010-01-29 19:24:38 UTC (rev 4203) +++ cherokee/branches/adminctk/admin/PageGeneral.py 2010-01-29 22:22:41 UTC (rev 4204) @@ -24,22 +24,16 @@ import CTK import Page +import Cherokee +from consts import * URL_BASE = '/general' URL_APPLY = '/general/apply' -PRODUCT_TOKENS = [. - ('', N_('Default')), - ('product', N_('Product only')), - ('minor', N_('Product + Minor version')), - ('minimal', N_('Product + Minimal version')), - ('os', N_('Product + Platform')), - ('full', N_('Full Server string')) -] - NOTE_IPV6 = N_('Set to enable the IPv6 support. The OS must support IPv6 for this to work.') NOTE_TOKENS = N_('This option allows to choose how the server identifies itself.') NOTE_TIMEOUT = N_('Time interval until the server closes inactive connections.') +NOTE_TLS = N_('Which, if any, should be the TLS/SSL backend.') HELPS = [('config_general', N_("General Configuration")), ('config_quickstart', N_("Configuration Quickstart"))] @@ -59,7 +53,8 @@ self += CTK.RawHTML ("<h2>%s</h2>" %(_('Support'))) table = CTK.PropsTableAuto (URL_APPLY) - table.Add (_('IPv6'), CTK.CheckCfg('server!ipv6', True), _(NOTE_IPV6)) + table.Add (_('IPv6'), CTK.CheckCfg('server!ipv6', True), _(NOTE_IPV6)) + table.Add (_('SSL/TLS back-end'), CTK.ComboCfg('server!tls', Cherokee.modules_available(CRYPTORS)), _(NOTE_TLS)) self += table self += CTK.RawHTML ("<h2>%s</h2>" %(_('Network behavior'))) Added: cherokee/branches/adminctk/admin/config_version.py =================================================================== --- cherokee/branches/adminctk/admin/config_version.py (rev 0) +++ cherokee/branches/adminctk/admin/config_version.py 2010-01-29 22:22:41 UTC (rev 4204) @@ -0,0 +1,101 @@ +import configured + +# Converts from 0.99.30 to 0.99.31 +def upgrade_to_0_99_31 (cfg): + # verver!_!logger!error is vserver!_!error_writer now. + # Must be relocated on each virtual server. + # + for v in cfg.keys('vserver'): + pre = 'vserver!%s' % (v) + if cfg['vserver!%s!logger!error' %(v)]: + cfg.clone ('vserver!%s!logger!error' %(v), + 'vserver!%s!error_writer' %(v)) + del(cfg['vserver!%s!logger!error' %(v)]) + +# Converts from 0.99.31-39 to 0.99.40 +def upgrade_to_0_99_40 (cfg): + # The encoder related configuration changed. What used to be + # vserver!10!rule!600!encoder!gzip = 1 is now + # vserver!10!rule!600!encoder!gzip = allow + # + # There are three possible options: "allow", "deny" and empty. + # The previous "1" turns "allow", "0" is default so those entries + # are removed and the brand new "deny" key is not assigned. + # + to_del = [] + for v in cfg.keys('vserver'): + for r in cfg.keys('vserver!%s!rule'%(v)): + if cfg['vserver!%s!rule!%s!encoder' %(v,r)]: + for e in cfg['vserver!%s!rule!%s!encoder' %(v,r)]: + pre = 'vserver!%s!rule!%s!encoder!%s' %(v,r,e) + if cfg.get_val(pre) == "1": + cfg[pre] = "allow" + else: + to_del.append(pre) + for pre in to_del: + del(cfg[pre]) + + +def config_version_get_current(): + ver = configured.VERSION.split ('b')[0] + v1,v2,v3 = ver.split (".") + + major = int(v1) + minor = int(v2) + micro = int(v3) + + return "%03d%03d%03d" %(major, minor, micro) + + +def config_version_cfg_is_up_to_date (cfg): + # Cherokee's version + ver_cherokee = config_version_get_current() + + # Configuration file version + ver_config = cfg.get_val("config!version") + if not ver_config: + cfg["config!version"] = "000099028" + return False + + # Cherokee 0.99.26 bug: 990250 is actually 99025 + if int(ver_config) == 990250: + ver_config = "000099025" + cfg['config!version'] = ver_config + return False + + # Compare both of them + if int(ver_config) > int(ver_cherokee): + print "WARNING!! Running a new configuration file (version %d)" % int(ver_config) + print " with an older version of Cherokee (version %d)" % int(ver_cherokee) + return True + + elif int(ver_config) == int(ver_cherokee): + return True + + else: + return False + + +def config_version_update_cfg (cfg): + # Do not proceed if it's empty + if not cfg.has_tree(): + return False + + # Update only when it's outdated + if config_version_cfg_is_up_to_date (cfg): + return False + + ver_release_s = config_version_get_current() + ver_config_s = cfg.get_val("config!version") + ver_config_i = int(ver_config_s) + + # Update to.. 0.99.31 + if ver_config_i < 99031: + upgrade_to_0_99_31 (cfg) + + # Update to.. 0.99.40 + if ver_config_i < 99040: + upgrade_to_0_99_40 (cfg) + + cfg["config!version"] = ver_release_s + return True Added: cherokee/branches/adminctk/admin/consts.py =================================================================== --- cherokee/branches/adminctk/admin/consts.py (rev 0) +++ cherokee/branches/adminctk/admin/consts.py 2010-01-29 22:22:41 UTC (rev 4204) @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +# +# Cherokee-admin +# +# Authors: +# Alvaro Lopez Ortega <alvaro [at] alobbs> +# +# Copyright (C) 2001-2010 Alvaro Lopez Ortega +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +AVAILABLE_LANGUAGES = [. + ('en', N_('English')), + ('es', N_('Spanish')), + ('de', N_('German')), + ('fr', N_('French')), + ('nl', N_('Dutch')), + ('sv_SE', N_('Swedish')), + ('po_BR', N_('Brazilian Portuguese')), + ('zh_CN', N_('Chinese Simplified')) +] + +PRODUCT_TOKENS = [. + ('', N_('Default')), + ('product', N_('Product only')), + ('minor', N_('Product + Minor version')), + ('minimal', N_('Product + Minimal version')), + ('os', N_('Product + Platform')), + ('full', N_('Full Server string')) +] + +HANDLERS = [. + ('', N_('None')), + ('common', N_('List & Send')), + ('file', N_('Static content')), + ('dirlist', N_('Only listing')), + ('redir', N_('Redirection')), + ('fcgi', N_('FastCGI')), + ('scgi', N_('SCGI')), + ('uwsgi', N_('uWSGI')), + ('proxy', N_('HTTP reverse proxy')), + ('post_report', N_('Upload reporting')), + ('streaming', N_('Audio/Video streaming')), + ('cgi', N_('CGI')), + ('ssi', N_('Server Side Includes')), + ('secdownload', N_('Hidden Downloads')), + ('server_info', N_('Server Info')), + ('dbslayer', N_('MySQL bridge')), + ('custom_error', N_('HTTP error')), + ('admin', N_('Remote Administration')), + ('empty_gif', N_('1x1 Transparent GIF')) +] + +ERROR_HANDLERS = [. + ('', N_('Default errors')), + ('error_redir', N_('Custom redirections')), + ('error_nn', N_('Closest match')) +] + +VALIDATORS = [. + ('', N_('None')), + ('plain', N_('Plain text file')), + ('htpasswd', N_('Htpasswd file')), + ('htdigest', N_('Htdigest file')), + ('ldap', N_('LDAP server')), + ('mysql', N_('MySQL server')), + ('pam', N_('PAM')), + ('authlist', N_('Fixed list')) +] + +VALIDATOR_METHODS = [. + ('basic', N_('Basic')), + ('digest', N_('Digest')), + ('basic,digest', N_('Basic or Digest')) +] + +LOGGERS = [. + ('', N_('None')), + ('combined', N_('Apache compatible')), + ('ncsa', N_('NCSA')), + ('custom', N_('Custom')) +] + +LOGGER_WRITERS = [. + ('file', N_('File')), + ('syslog', N_('System logger')), + ('stderr', N_('Standard Error')), + ('exec', N_('Execute program')) +] + +BALANCERS = [ + ('round_robin', N_("Round Robin")), + ('ip_hash', N_("IP Hash")) +] + +SOURCE_TYPES = [ + ('interpreter', N_('Local interpreter')), + ('host', N_('Remote host')) +] + +ENCODERS = [ + ('gzip', N_('GZip')), + ('deflate', N_('Deflate')) +] + +THREAD_POLICY = [. + ('', N_('Default')), + ('fifo', N_('FIFO')), + ('rr', N_('Round-robin')), + ('other', N_('Dynamic')) +] + +POLL_METHODS = [. + ('', N_('Automatic')), + ('epoll', 'epoll() - Linux >= 2.6'), + ('kqueue', 'kqueue() - BSD, OS X'), + ('ports', 'Solaris ports - >= 10'), + ('poll', 'poll()'), + ('select', 'select()'), + ('win32', 'Win32') +] + +REDIR_SHOW = [ + ('1', N_('External')), + ('0', N_('Internal')) +] + +ERROR_CODES = [. + ('400', '400 Bad Request'), + ('403', '403 Forbidden'), + ('404', '404 Not Found'), + ('405', '405 Method Not Allowed'), + ('410', '410 Gone'), + ('413', '413 Request Entity too large'), + ('414', '414 Request-URI too long'), + ('416', '416 Requested range not satisfiable'), + ('500', '500 Internal Server Error'), + ('501', '501 Not Implemented'), + ('502', '502 Bad gateway'), + ('503', '503 Service Unavailable'), + ('504', '504 Gateway Timeout'), + ('505', '505 HTTP Version Not Supported') +] + +RULES = [. + ('directory', N_('Directory')), + ('extensions', N_('Extensions')), + ('request', N_('Regular Expression')), + ('header', N_('Header')), + ('exists', N_('File Exists')), + ('method', N_('HTTP Method')), + ('bind', N_('Incoming IP/Port')), + ('fullpath', N_('Full Path')), + ('from', N_('Connected from')), + ('url_arg', N_('URL Argument')), + ('geoip', N_('GeoIP')) +] + +VRULES = [. + ('', N_('Choose..')), + ('wildcard', N_('Wildcards')), + ('rehost', N_('Regular Expressions')), + ('target_ip', N_('Server IP')) +] + +EXPIRATION_TYPE = [. + ('', N_('Not set')), + ('epoch', N_('Already expired on 1970')), + ('max', N_('Do not expire until 2038')), + ('time', N_('Custom value')) +] + +CRYPTORS = [. + ('', N_('No TLS/SSL')), + ('libssl', N_('OpenSSL / libssl')) +] + +EVHOSTS = [ + ('', N_('Off')), + ('evhost', N_('Enhanced Virtual Hosting')) +] + +CLIENT_CERTS = [. + ('', N_('Skip')), + ('accept', N_('Accept')), + ('required', N_('Require')) +] + +COLLECTORS = [ + ('', N_('Disabled')), + ('rrd', N_('RRDtool graphs')) +] + +UTC_TIME = [. + ('', N_('Local time')), + ('1', N_('UTC: Coordinated Universal Time')) +] + +DWRITER_LANGS = [. + ('json', N_('JSON')), + ('python', N_('Python')), + ('php', N_('PHP')), + ('ruby', N_('Ruby')) +] + +POST_TRACKERS = [ + ('', N_('Disabled')), + ('post_track', N_('POST tracker')) +]
|