X-Git-Url: https://git.llucax.com/software/pymin.git/blobdiff_plain/296d853cc95fd5bef262248cfe21b507abd26a4f..e69fa50ae7f488fb49641d4a5d98d14428c526b1:/pymind diff --git a/pymind b/pymind index 27a9722..78939f0 100755 --- a/pymind +++ b/pymind @@ -1,8 +1,178 @@ #!/usr/bin/env python # vim: set encoding=utf-8 et sw=4 sts=4 : +import os +import sys +from formencode import Invalid, validators as V + +import logging, logging.config ; log = logging.getLogger('pymind') +# default logging configuration +# (this is used before the user configuration file gets parsed) +try: + # first we try to load a log config file + default = '/etc/pymin/log.init.ini' + logging.config.fileConfig(os.getenv('PYMIND_LOGINITCONF', default)) +except: + # then we try to load a reasonable default + logging.basicConfig(format='%(levelname)s: %(message)s') + +from pymin.config import OptionGroup, Option, ConfigOption, ListOption +from pymin.config import load_config, options +from pymin.config import ConfigError, MissingSectionHeaderError, ParsingError +from pymin.dispatcher import Handler from pymin.pymindaemon import PyminDaemon -import config +from pymin.service import load_service, LoadError + +# exit status (1 is reserved for command-line errors) +EXIT_CONFIG_ERROR = 2 +EXIT_NO_SERVICE = 3 + +# default locations where to look for configuration files +# all found files will be processed, overriding the previous configuration +# files values. +config_file_paths = [ + '/etc/pymin.ini', + '/etc/pymin/pymin.ini', + os.path.expanduser('~/.pymin.ini'), + os.path.expanduser('~/.pymin/pymin.ini'), +] + +# default locations where to look for the log configuration file +# all found files will be processed, overriding the previous configuration +# files values. +log_file_paths = [ + '/etc/pymin/log.ini', + os.path.expanduser('~/.pymin/log.ini'), +] + +# default locations where to look for service plug-ins +# search stops when a service plug-in is found +services_paths = [ + os.path.expanduser('~/.pymin/services'), + '/usr/share/pymin/services', +] + +# default configuration variables +# these are useful variables to help the user writing the configuration file +config_defaults = { + 'pymind-data-dir': '/var/lib/pymin', + 'pymind-pickle-dir': '/var/lib/pymin/pickle', + 'pymind-config-dir': '/var/lib/pymin/config', +} + +# Validator to check if is a valid Python identifier +PythonIdentifier = V.Regex(r'^[a-zA-Z_]\w*$') + +options.init('pymind', 'Pymin daemon global options', [ + Option('bind_addr', V.CIDR, 'a', default='127.0.0.1', metavar='ADDR', + help='Bind to IP ADDR'), + Option('bind_port', V.Int(min=1, max=65535), 'p', default=9999, + metavar='PORT', help="Bind to port PORT"), + ListOption('services', PythonIdentifier, 's', default=[], + metavar='SERVICE', help="manage service SERVICE"), + ListOption('services_dirs', V.String, 'd', default=[], + metavar='DIR', help="search for services in DIR"), + ListOption('log_config_files', V.String, 'l', default=log_file_paths, + metavar='FILE', help="load log configuration FILE"), + ConfigOption('config_file', 'c', metavar='FILE', + help="load the configuration file FILE after the default " + "configuration files"), + ConfigOption('replace_config_file', 'C', override=True, metavar='FILE', + help="don't load the default configuration files, just FILE"), +]) + + +# FIXME: move to IpHandler or someplace else +def activate_ip_forward(): + try: + f = file("/proc/sys/net/ipv4/ip_forward","w") + f.write("1") + f.close() + except (IOError, OSError), e: + log.warning("Can't set ip_forward: %s", e) + + +def die(status, msg, *args): + log.critical(msg, *args) + logging.shutdown() + sys.exit(status) + +def get_config(paths, version, desc, add_options, defaults): + global config_file_paths + try: + (config, args) = load_config(paths, version, desc, add_options, defaults) + except ConfigError, e: + die(EXIT_CONFIG_ERROR, str(e)) + except MissingSectionHeaderError, e: + dir(EXIT_CONFIG_ERROR, "%s:%s: missing section header near: %s", + e.filename, e.lineno, e.line) + except ParsingError, e: + for (lineno, line) in e.errors: + log.critical("%s:%s: invalid syntax near: %s", e.filename, lineno, line) + die(EXIT_CONFIG_ERROR, str(e.errors)) + except Invalid, e: + die(EXIT_CONFIG_ERROR, str(e.unpack_errors())) + except LoadError, e: + die(EXIT_NO_SERVICE, "service '%s' not found (see option " \ + "--services-dir)", e.service_name) + return (config, args) + + +class Services: + def __init__(self): + self.services = dict() + def add_config_options(self, config, args): + for service in config.services: + s = load_service(service, config.services_dirs) + s.setup_service(options, config) + self.services[service] = s + +def build_root(config, args, services): + from pymin.dispatcher import Handler + class Root(Handler): + pass + # TODO check services dependencies + root = Root() + for name, service in services.items(): + setattr(root, name, service.get_service(config)) + return root + + +def setup_logging(config_files): + # XXX: this is a hack for re-enabling loggers not configured via + # fileConfig(). This function disable all existing loggers that + # has no configuration specified in the config file, so we have + # to re-enable the by hand. + # See Python bug 3136: http://bugs.python.org/issue3136 + existing = logging.root.manager.loggerDict.keys() + loaded_files = 0 + for f in config_files: + try: + f = open(f) + except Exception, e: + log.info("config file '%s' can't be readed (%s)", f, e) + continue + logging.config.fileConfig(f) + f.close() + loaded_files += 1 + if not loaded_files: + log.warning('no log config file loaded') + # XXX: finish the hack commented above + for log in existing: + logging.root.manager.loggerDict[log].disabled = 0 + + +def main(): + services = Services() + (config, args) = get_config(config_file_paths, '%prog 0.1', + 'Router services administration daemon', + services.add_config_options, config_defaults) + setup_logging(config.log_config_files) + root_handler = build_root(config, args, services.services) + activate_ip_forward() + PyminDaemon(root_handler, (config.bind_addr, config.bind_port)).run() + logging.shutdown() -PyminDaemon(config.routes, config.bind_addr).run() +if __name__ == '__main__': + main()