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