merge upstream
[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=openerp.tools.config['init'] or openerp.tools.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', {}, '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 def export_translation():
114     config = openerp.tools.config
115     dbname = config['db_name']
116
117     if config["language"]:
118         msg = "language %s" % (config["language"],)
119     else:
120         msg = "new language"
121     _logger.info('writing translation file for %s to %s', msg,
122         config["translate_out"])
123
124     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
125     buf = file(config["translate_out"], "w")
126     cr = openerp.pooler.get_db(dbname).cursor()
127     openerp.tools.trans_export(config["language"],
128         config["translate_modules"] or ["all"], buf, fileformat, cr)
129     cr.close()
130     buf.close()
131
132     _logger.info('translation file written successfully')
133
134 def import_translation():
135     config = openerp.tools.config
136     context = {'overwrite': config["overwrite_existing_translations"]}
137     dbname = config['db_name']
138
139     cr = openerp.pooler.get_db(dbname).cursor()
140     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
141         context=context)
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     threads_info = dict([(th.ident, {'name': th.name,
167                                     'uid': getattr(th,'uid','n/a')})
168                                 for th in threading.enumerate()])
169     code = []
170     for threadId, stack in sys._current_frames().items():
171         thread_info = threads_info.get(threadId)
172         code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
173                     (thread_info and thread_info['name'] or 'n/a',
174                      threadId,
175                      thread_info and thread_info['uid'] or 'n/a'))
176         for filename, lineno, name, line in traceback.extract_stack(stack):
177             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
178             if line:
179                 code.append("  %s" % (line.strip()))
180     _logger.info("\n".join(code))
181
182 def setup_signal_handlers():
183     """ Register the signal handler defined above. """
184     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
185     if os.name == 'posix':
186         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
187         signal.signal(signal.SIGQUIT, dumpstacks)
188     elif os.name == 'nt':
189         import win32api
190         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
191
192 def quit_on_signals():
193     """ Wait for one or two signals then shutdown the server.
194
195     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
196     a second one if any will force an immediate exit.
197
198     """
199     # Wait for a first signal to be handled. (time.sleep will be interrupted
200     # by the signal handler.) The try/except is for the win32 case.
201     try:
202         while quit_signals_received == 0:
203             time.sleep(60)
204     except KeyboardInterrupt:
205         pass
206
207     config = openerp.tools.config
208     openerp.service.stop_services()
209
210     if getattr(openerp, 'phoenix', False):
211         # like the phoenix, reborn from ashes...
212         strip_args = ['-d', '-u']
213         a = sys.argv[:]
214         args = [x for i, x in enumerate(a) if x not in strip_args and a[max(i - 1, 0)] not in strip_args]
215
216         # FIXME http socket cannot be rebind ?!? socket seems to not be freed, even shutdown() is called
217         os.execv(sys.executable, [sys.executable] + args)
218         return
219
220     if config['pidfile']:
221         os.unlink(config['pidfile'])
222     sys.exit(0)
223
224 def configure_babel_localedata_path():
225     # Workaround: py2exe and babel.
226     if hasattr(sys, 'frozen'):
227         import babel
228         babel.localedata._dirname = os.path.join(os.path.dirname(sys.executable), 'localedata')
229
230 def main():
231     os.environ["TZ"] = "UTC"
232
233     check_root_user()
234     openerp.tools.config.parse_config(sys.argv[1:])
235
236     check_postgres_user()
237     openerp.netsvc.init_logger()
238     report_configuration()
239
240     config = openerp.tools.config
241
242     configure_babel_localedata_path()
243
244     setup_signal_handlers()
245
246     if config["test_file"]:
247         run_test_file(config['db_name'], config['test_file'])
248         sys.exit(0)
249
250     if config["translate_out"]:
251         export_translation()
252         sys.exit(0)
253
254     if config["translate_in"]:
255         import_translation()
256         sys.exit(0)
257
258     if not config["stop_after_init"]:
259         setup_pid_file()
260         # Some module register themselves when they are loaded so we need the
261         # services to be running before loading any registry.
262         if config['workers']:
263             openerp.service.start_services_workers()
264         else:
265             openerp.service.start_services()
266
267     if config['db_name']:
268         for dbname in config['db_name'].split(','):
269             preload_registry(dbname)
270
271     if config["stop_after_init"]:
272         sys.exit(0)
273
274     _logger.info('OpenERP server is running, waiting for connections...')
275     quit_on_signals()
276
277 if __name__ == "__main__":
278     main()
279
280 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: