19c60397a08c6ed7511e07e92350b4f6f6c1cff0
[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 imp
34 import logging
35 import os
36 import signal
37 import sys
38 import threading
39 import traceback
40 import time
41
42 import openerp
43 __author__ = openerp.release.author
44 __version__ = openerp.release.version
45
46 # Also use the `openerp` logger for the main script.
47 _logger = logging.getLogger('openerp')
48
49 def check_root_user():
50     """ Exit if the process's user is 'root' (on POSIX system)."""
51     if os.name == 'posix':
52         import pwd
53         if pwd.getpwuid(os.getuid())[0] == 'root' :
54             sys.stderr.write("Running as user 'root' is a security risk, aborting.\n")
55             sys.exit(1)
56
57 def check_postgres_user():
58     """ Exit if the configured database user is 'postgres'.
59
60     This function assumes the configuration has been initialized.
61     """
62     config = openerp.tools.config
63     if config['db_user'] == 'postgres':
64         sys.stderr.write("Using the database user 'postgres' is a security risk, aborting.")
65         sys.exit(1)
66
67 def report_configuration():
68     """ Log the server version and some configuration values.
69
70     This function assumes the configuration has been initialized.
71     """
72     config = openerp.tools.config
73     _logger.info("OpenERP version %s", __version__)
74     for name, value in [('addons paths', config['addons_path']),
75                         ('database hostname', config['db_host'] or 'localhost'),
76                         ('database port', config['db_port'] or '5432'),
77                         ('database user', config['db_user'])]:
78         _logger.info("%s: %s", name, value)
79
80 def setup_pid_file():
81     """ Create a file with the process id written in it.
82
83     This function assumes the configuration has been initialized.
84     """
85     config = openerp.tools.config
86     if config['pidfile']:
87         fd = open(config['pidfile'], 'w')
88         pidtext = "%d" % (os.getpid())
89         fd.write(pidtext)
90         fd.close()
91
92 def preload_registry(dbname):
93     """ Preload a registry, and start the cron."""
94     try:
95         db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
96     except Exception:
97         _logger.exception('Failed to initialize database `%s`.', dbname)
98
99 def run_test_file(dbname, test_file):
100     """ Preload a registry, possibly run a test file, and start the cron."""
101     try:
102         db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
103         cr = db.cursor()
104         _logger.info('loading test file %s', test_file)
105         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), {}, 'test', True)
106         cr.rollback()
107         cr.close()
108     except Exception:
109         _logger.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
110
111
112 def export_translation():
113     config = openerp.tools.config
114     dbname = config['db_name']
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     cr.commit()
142     cr.close()
143
144 # Variable keeping track of the number of calls to the signal handler defined
145 # below. This variable is monitored by ``quit_on_signals()``.
146 quit_signals_received = 0
147
148 def signal_handler(sig, frame):
149     """ Signal handler: exit ungracefully on the second handled signal.
150
151     :param sig: the signal number
152     :param frame: the interrupted stack frame or None
153     """
154     global quit_signals_received
155     quit_signals_received += 1
156     if quit_signals_received > 1:
157         # logging.shutdown was already called at this point.
158         sys.stderr.write("Forced shutdown.\n")
159         os._exit(0)
160
161 def dumpstacks(sig, frame):
162     """ Signal handler: dump a stack trace for each existing thread."""
163     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
164     # modified for python 2.5 compatibility
165     thread_map = dict(threading._active, **threading._limbo)
166     id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
167     code = []
168     for threadId, stack in sys._current_frames().items():
169         code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,'n/a'), threadId))
170         for filename, lineno, name, line in traceback.extract_stack(stack):
171             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
172             if line:
173                 code.append("  %s" % (line.strip()))
174     _logger.info("\n".join(code))
175
176 def setup_signal_handlers():
177     """ Register the signal handler defined above. """
178     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
179     if os.name == 'posix':
180         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
181         signal.signal(signal.SIGQUIT, dumpstacks)
182     elif os.name == 'nt':
183         import win32api
184         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
185
186 def quit_on_signals():
187     """ Wait for one or two signals then shutdown the server.
188
189     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
190     a second one if any will force an immediate exit.
191
192     """
193     # Wait for a first signal to be handled. (time.sleep will be interrupted
194     # by the signal handler.) The try/except is for the win32 case.
195     try:
196         while quit_signals_received == 0:
197             time.sleep(60)
198     except KeyboardInterrupt, e:
199         pass
200
201     if config['pidfile']:
202         os.unlink(config['pidfile'])
203
204     openerp.service.stop_services()
205     sys.exit(0)
206
207 def configure_babel_localedata_path():
208     # Workaround: py2exe and babel.
209     if hasattr(sys, 'frozen'):
210         import babel
211         babel.localedata._dirname = os.path.join(os.path.dirname(sys.executable), 'localedata')
212
213 if __name__ == "__main__":
214
215     os.environ["TZ"] = "UTC"
216
217     check_root_user()
218     openerp.tools.config.parse_config(sys.argv[1:])
219
220     check_postgres_user()
221     openerp.netsvc.init_logger()
222     report_configuration()
223
224     config = openerp.tools.config
225
226     configure_babel_localedata_path()
227
228     setup_signal_handlers()
229
230     if config["test_file"]:
231         run_test_file(config['db_name'], config['test_file'])
232         sys.exit(0)
233
234     if config["translate_out"]:
235         export_translation()
236         sys.exit(0)
237
238     if config["translate_in"]:
239         import_translation()
240         sys.exit(0)
241
242     if not config["stop_after_init"]:
243         # Some module register themselves when they are loaded so we need the
244         # services to be running before loading any registry.
245         openerp.service.start_services()
246
247     for m in openerp.conf.server_wide_modules:
248         try:
249             openerp.modules.module.load_openerp_module(m)
250         except Exception:
251             msg = ''
252             if m == 'web':
253                 msg = """
254 The `web` module is provided by the addons found in the `openerp-web` project.
255 Maybe you forgot to add those addons in your addons_path configuration."""
256             _logger.exception('Failed to load server-wide module `%s`.%s', m, msg)
257
258     if config['db_name']:
259         for dbname in config['db_name'].split(','):
260             preload_registry(dbname)
261
262     if config["stop_after_init"]:
263         sys.exit(0)
264
265     setup_pid_file()
266     _logger.info('OpenERP server is running, waiting for connections...')
267     quit_on_signals()
268
269 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: