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