]> git.llucax.com Git - software/pymin.git/blobdiff - pymind
Hack to overcome Python's bug 3136 in logging system (refs #7).
[software/pymin.git] / pymind
diff --git a/pymind b/pymind
index 488b34eafc366559da36cbefdc7198d300044de3..fa4fa36f5c9d5b537704bec3c1b996d8d35bab3e 100755 (executable)
--- a/pymind
+++ b/pymind
 #!/usr/bin/env python
 # vim: set encoding=utf-8 et sw=4 sts=4 :
 
 #!/usr/bin/env python
 # vim: set encoding=utf-8 et sw=4 sts=4 :
 
+import logging, logging.config ; log = logging.getLogger('pymind')
+# default logging configuration
+# (this is used before the user configuration file gets parsed)
+logging.basicConfig(format='%(levelname)s: %(message)s')
+
+import os
 import sys
 import sys
-import logging ; log = logging.getLogger('pymind')
+from formencode import Invalid, validators as V
 
 
-from pymin.pymindaemon import PyminDaemon
+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.dispatcher import Handler
+from pymin.pymindaemon import PyminDaemon
 from pymin.service import load_service, LoadError
 from pymin.service import load_service, LoadError
-import config
 
 
-# exit status
-EXIT_NO_SERVICE = 1
+# exit status (1 is reserved for command-line errors)
+EXIT_CONFIG_ERROR = 2
+EXIT_NO_SERVICE   = 3
 
 
-class Root(Handler):
-    pass
+# 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'),
+]
 
 
-def build_root(config):
-    # TODO check services dependencies
-    services = dict()
-    for service in config.services:
-        try:
+# 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 = load_service(service, config.services_dirs)
-        except LoadError, e:
-            log.error("Can't find service called '%s'\n", service)
-            sys.exit(EXIT_NO_SERVICE)
-        services[service] = s
+            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
 
     root = Root()
     for name, service in services.items():
         setattr(root, name, service.get_service(config))
     return root
 
-PyminDaemon(build_root(config), config.bind_addr).run()
+
+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()