[IMP] openerp-server: handle more startup exceptions w/o exiting main thread
[odoo/odoo.git] / openerp-server
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 ##############################################################################
4 #
5 #    OpenERP, Open Source Management Solution
6 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as
10 #    published by the Free Software Foundation, either version 3 of the
11 #    License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 """
24 OpenERP - Server
25 OpenERP is an ERP+CRM program for small and medium businesses.
26
27 The whole source code is distributed under the terms of the
28 GNU Public Licence.
29
30 (c) 2003-TODAY, Fabien Pinckaers - OpenERP SA
31 """
32
33 import logging
34 import os
35 import signal
36 import sys
37 import threading
38 import traceback
39 import time
40
41 import openerp
42 __author__ = openerp.release.author
43 __version__ = openerp.release.version
44
45 def check_root_user():
46     """ Exit if the process's user is 'root' (on POSIX system)."""
47     if os.name == 'posix':
48         import pwd
49         if pwd.getpwuid(os.getuid())[0] == 'root' :
50             sys.stderr.write("Running as user 'root' is a security risk, aborting.\n")
51             sys.exit(1)
52
53 def check_postgres_user():
54     """ Exit if the configured database user is 'postgres'.
55
56     This function assumes the configuration has been initialized.
57     """
58     config = openerp.tools.config
59     if config['db_user'] == 'postgres':
60         sys.stderr.write("Using the database user 'postgres' is a security risk, aborting.")
61         sys.exit(1)
62
63 def report_configuration():
64     """ Log the server version and some configuration values.
65
66     This function assumes the configuration has been initialized.
67     """
68     config = openerp.tools.config
69     logger = logging.getLogger('server')
70     logger.info("OpenERP version %s", __version__)
71     for name, value in [('addons paths', config['addons_path']),
72                         ('database hostname', config['db_host'] or 'localhost'),
73                         ('database port', config['db_port'] or '5432'),
74                         ('database user', config['db_user'])]:
75         logger.info("%s: %s", name, value)
76
77 def setup_pid_file():
78     """ Create a file with the process id written in it.
79
80     This function assumes the configuration has been initialized.
81     """
82     config = openerp.tools.config
83     if config['pidfile']:
84         fd = open(config['pidfile'], 'w')
85         pidtext = "%d" % (os.getpid())
86         fd.write(pidtext)
87         fd.close()
88
89 def preload_registry(dbname):
90     """ Preload a registry, and start the cron."""
91     try:
92         db, pool = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
93         pool.get('ir.cron').restart(db.dbname)
94     except Exception:
95         logging.exception('Failed to initialize database `%s`.', dbname)
96
97 def run_test_file(dbname, test_file):
98     """ Preload a registry, possibly run a test file, and start the cron."""
99     try:
100         db, pool = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
101         cr = db.cursor()
102         logger = logging.getLogger('server')
103         logger.info('loading test file %s', test_file)
104         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), {}, 'test', True)
105         cr.rollback()
106         cr.close()
107     except Exception:
108         logging.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
109
110
111 def export_translation():
112     config = openerp.tools.config
113     dbname = config['db_name']
114     logger = logging.getLogger('server')
115
116     if config["language"]:
117         msg = "language %s" % (config["language"],)
118     else:
119         msg = "new language"
120     logger.info('writing translation file for %s to %s', msg,
121         config["translate_out"])
122
123     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
124     buf = file(config["translate_out"], "w")
125     cr = openerp.pooler.get_db(dbname).cursor()
126     openerp.tools.trans_export(config["language"],
127         config["translate_modules"] or ["all"], buf, fileformat, cr)
128     cr.close()
129     buf.close()
130
131     logger.info('translation file written successfully')
132
133 def import_translation():
134     config = openerp.tools.config
135     context = {'overwrite': config["overwrite_existing_translations"]}
136     dbname = config['db_name']
137
138     cr = openerp.pooler.get_db(dbname).cursor()
139     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
140         context=context)
141     openerp.tools.trans_update_res_ids(cr)
142     cr.commit()
143     cr.close()
144
145 # Variable keeping track of the number of calls to the signal handler defined
146 # below. This variable is monitored by ``quit_on_signals()``.
147 quit_signals_received = 0
148
149 def signal_handler(sig, frame):
150     """ Signal handler: exit ungracefully on the second handled signal.
151
152     :param sig: the signal number
153     :param frame: the interrupted stack frame or None
154     """
155     global quit_signals_received
156     quit_signals_received += 1
157     if quit_signals_received > 1:
158         # logging.shutdown was already called at this point.
159         sys.stderr.write("Forced shutdown.\n")
160         os._exit(0)
161
162 def dumpstacks(sig, frame):
163     """ Signal handler: dump a stack trace for each existing thread."""
164     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
165     # modified for python 2.5 compatibility
166     thread_map = dict(threading._active, **threading._limbo)
167     id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
168     code = []
169     for threadId, stack in sys._current_frames().items():
170         code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
171         for filename, lineno, name, line in traceback.extract_stack(stack):
172             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
173             if line:
174                 code.append("  %s" % (line.strip()))
175     logging.getLogger('dumpstacks').info("\n".join(code))
176
177 def setup_signal_handlers():
178     """ Register the signal handler defined above. """
179     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
180     map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
181     if os.name == 'posix':
182         signal.signal(signal.SIGQUIT, dumpstacks)
183
184 def quit_on_signals():
185     """ Wait for one or two signals then shutdown the server.
186
187     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
188     a second one if any will force an immediate exit.
189
190     """
191     # Wait for a first signal to be handled. (time.sleep will be interrupted
192     # by the signal handler.)
193     while quit_signals_received == 0:
194         time.sleep(60)
195
196     if config['pidfile']:
197         os.unlink(config['pidfile'])
198
199     openerp.service.stop_services()
200     sys.exit(0)
201
202 if __name__ == "__main__":
203
204     os.environ["TZ"] = "UTC"
205
206     check_root_user()
207     openerp.tools.config.parse_config(sys.argv[1:])
208     check_postgres_user()
209     openerp.netsvc.init_logger()
210     report_configuration()
211
212     config = openerp.tools.config
213
214     setup_signal_handlers()
215
216     if config["test_file"]:
217         run_test_file(config['db_name'], config['test_file'])
218         sys.exit(0)
219
220     if config["translate_out"]:
221         export_translation()
222         sys.exit(0)
223
224     if config["translate_in"]:
225         import_translation()
226         sys.exit(0)
227
228     if not config["stop_after_init"]:
229         # Some module register themselves when they are loaded so we need the
230         # services to be running before loading any registry.
231         openerp.service.start_services()
232
233     if config['db_name']:
234         for dbname in config['db_name'].split(','):
235             preload_registry(dbname)
236
237     if config["stop_after_init"]:
238         sys.exit(0)
239
240     for m in openerp.conf.server_wide_modules:
241         try:
242             __import__(m)
243             # Call any post_load hook.
244             info = openerp.modules.module.load_information_from_description_file(m)
245             if info['post_load']:
246                 getattr(sys.modules[m], info['post_load'])()
247         except Exception:
248             logging.exception('Failed to load server-wide module `%s`', m)
249
250     setup_pid_file()
251     logger = logging.getLogger('server')
252     logger.info('OpenERP server is running, waiting for connections...')
253     quit_on_signals()
254
255 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: