]> git.llucax.com Git - software/pymin.git/blob - pymind
Hack to overcome Python's bug 3136 in logging system (refs #7).
[software/pymin.git] / pymind
1 #!/usr/bin/env python
2 # vim: set encoding=utf-8 et sw=4 sts=4 :
3
4 import logging, logging.config ; log = logging.getLogger('pymind')
5 # default logging configuration
6 # (this is used before the user configuration file gets parsed)
7 logging.basicConfig(format='%(levelname)s: %(message)s')
8
9 import os
10 import sys
11 from formencode import Invalid, validators as V
12
13 from pymin.config import OptionGroup, Option, ConfigOption, ListOption
14 from pymin.config import load_config, options
15 from pymin.config import ConfigError, MissingSectionHeaderError, ParsingError
16 from pymin.dispatcher import Handler
17 from pymin.pymindaemon import PyminDaemon
18 from pymin.service import load_service, LoadError
19
20 # exit status (1 is reserved for command-line errors)
21 EXIT_CONFIG_ERROR = 2
22 EXIT_NO_SERVICE   = 3
23
24 # default locations where to look for configuration files
25 # all found files will be processed, overriding the previous configuration
26 # files values.
27 config_file_paths = [
28     '/etc/pymin.ini',
29     '/etc/pymin/pymin.ini',
30     os.path.expanduser('~/.pymin.ini'),
31     os.path.expanduser('~/.pymin/pymin.ini'),
32 ]
33
34 # default locations where to look for the log configuration file
35 # all found files will be processed, overriding the previous configuration
36 # files values.
37 log_file_paths = [
38     '/etc/pymin/log.ini',
39     os.path.expanduser('~/.pymin/log.ini'),
40 ]
41
42 # default locations where to look for service plug-ins
43 # search stops when a service plug-in is found
44 services_paths = [
45     os.path.expanduser('~/.pymin/services'),
46     '/usr/share/pymin/services',
47 ]
48
49 # default configuration variables
50 # these are useful variables to help the user writing the configuration file
51 config_defaults = {
52     'pymind-data-dir':   '/var/lib/pymin',
53     'pymind-pickle-dir': '/var/lib/pymin/pickle',
54     'pymind-config-dir': '/var/lib/pymin/config',
55 }
56
57 # Validator to check if is a valid Python identifier
58 PythonIdentifier = V.Regex(r'^[a-zA-Z_]\w*$')
59
60 options.init('pymind', 'Pymin daemon global options', [
61     Option('bind_addr', V.CIDR, 'a', default='127.0.0.1', metavar='ADDR',
62            help='Bind to IP ADDR'),
63     Option('bind_port', V.Int(min=1, max=65535), 'p', default=9999,
64            metavar='PORT', help="Bind to port PORT"),
65     ListOption('services', PythonIdentifier, 's', default=[],
66                metavar='SERVICE', help="manage service SERVICE"),
67     ListOption('services_dirs', V.String, 'd', default=[],
68                metavar='DIR', help="search for services in DIR"),
69     ListOption('log_config_files', V.String, 'l', default=log_file_paths,
70                metavar='FILE', help="load log configuration FILE"),
71     ConfigOption('config_file', 'c', metavar='FILE',
72                  help="load the configuration file FILE after the default "
73                       "configuration files"),
74     ConfigOption('replace_config_file', 'C', override=True, metavar='FILE',
75                  help="don't load the default configuration files, just FILE"),
76 ])
77
78
79 # FIXME: move to IpHandler or someplace else
80 def activate_ip_forward():
81     try:
82         f = file("/proc/sys/net/ipv4/ip_forward","w")
83         f.write("1")
84         f.close()
85     except (IOError, OSError), e:
86         log.warning("Can't set ip_forward: %s", e)
87
88
89 def die(status, msg, *args):
90     log.critical(msg, *args)
91     logging.shutdown()
92     sys.exit(status)
93
94 def get_config(paths, version, desc, add_options, defaults):
95     global config_file_paths
96     try:
97         (config, args) = load_config(paths, version, desc, add_options, defaults)
98     except ConfigError, e:
99         die(EXIT_CONFIG_ERROR, str(e))
100     except MissingSectionHeaderError, e:
101         dir(EXIT_CONFIG_ERROR, "%s:%s: missing section header near: %s",
102             e.filename, e.lineno, e.line)
103     except ParsingError, e:
104         for (lineno, line) in e.errors:
105             log.critical("%s:%s: invalid syntax near: %s", e.filename, lineno, line)
106         die(EXIT_CONFIG_ERROR, str(e.errors))
107     except Invalid, e:
108         die(EXIT_CONFIG_ERROR, str(e.unpack_errors()))
109     except LoadError, e:
110         die(EXIT_NO_SERVICE, "service '%s' not found (see option " \
111             "--services-dir)", e.service_name)
112     return (config, args)
113
114
115 class Services:
116     def __init__(self):
117         self.services = dict()
118     def add_config_options(self, config, args):
119         for service in config.services:
120             s = load_service(service, config.services_dirs)
121             s.setup_service(options, config)
122             self.services[service] = s
123
124 def build_root(config, args, services):
125     from pymin.dispatcher import Handler
126     class Root(Handler):
127         pass
128     # TODO check services dependencies
129     root = Root()
130     for name, service in services.items():
131         setattr(root, name, service.get_service(config))
132     return root
133
134
135 def setup_logging(config_files):
136     # XXX: this is a hack for re-enabling loggers not configured via
137     #      fileConfig(). This function disable all existing loggers that
138     #      has no configuration specified in the config file, so we have
139     #      to re-enable the by hand.
140     #      See Python bug 3136: http://bugs.python.org/issue3136
141     existing = logging.root.manager.loggerDict.keys()
142     loaded_files = 0
143     for f in config_files:
144         try:
145             f = open(f)
146         except Exception, e:
147             log.info("config file '%s' can't be readed (%s)", f, e)
148             continue
149         logging.config.fileConfig(f)
150         f.close()
151         loaded_files += 1
152     if not loaded_files:
153         log.warning('no log config file loaded')
154     # XXX: finish the hack commented above
155     for log in existing:
156         logging.root.manager.loggerDict[log].disabled = 0
157
158
159 def main():
160     services = Services()
161     (config, args) = get_config(config_file_paths, '%prog 0.1',
162                                 'Router services administration daemon',
163                                 services.add_config_options, config_defaults)
164     setup_logging(config.log_config_files)
165     root_handler = build_root(config, args, services.services)
166     activate_ip_forward()
167     PyminDaemon(root_handler, (config.bind_addr, config.bind_port)).run()
168     logging.shutdown()
169
170 if __name__ == '__main__':
171     main()
172