#!/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 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() if __name__ == '__main__': main()