[REM] python25-compat
[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     db, pool = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
100
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
108 def export_translation():
109     config = openerp.tools.config
110     dbname = config['db_name']
111     logger = logging.getLogger('server')
112
113     if config["language"]:
114         msg = "language %s" % (config["language"],)
115     else:
116         msg = "new language"
117     logger.info('writing translation file for %s to %s', msg,
118         config["translate_out"])
119
120     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
121     buf = file(config["translate_out"], "w")
122     cr = openerp.pooler.get_db(dbname).cursor()
123     openerp.tools.trans_export(config["language"],
124         config["translate_modules"] or ["all"], buf, fileformat, cr)
125     cr.close()
126     buf.close()
127
128     logger.info('translation file written successfully')
129
130 def import_translation():
131     config = openerp.tools.config
132     context = {'overwrite': config["overwrite_existing_translations"]}
133     dbname = config['db_name']
134
135     cr = openerp.pooler.get_db(dbname).cursor()
136     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
137         context=context)
138     openerp.tools.trans_update_res_ids(cr)
139     cr.commit()
140     cr.close()
141
142 def start_services():
143     http_server = openerp.service.http_server
144     netrpc_server = openerp.service.netrpc_server
145
146     # Instantiate local services (this is a legacy design).
147     openerp.osv.osv.start_object_proxy()
148     # Export (for RPC) services.
149     openerp.service.web_services.start_web_services()
150
151     # Start the NET-RPC server.
152     netrpc_server.init_servers()
153
154     # Start the main cron thread.
155     openerp.netsvc.start_agent()
156
157     # Start the top-level servers threads (normally HTTP, HTTPS, and NETRPC).
158     openerp.netsvc.Server.startAll()
159
160     # Start the WSGI server.
161     openerp.wsgi.start_server()
162
163 # Variable keeping track of the number of calls to the signal handler defined
164 # below. This variable is monitored by ``quit_on_signals()``.
165 quit_signals_received = 0
166
167 def signal_handler(sig, frame):
168     """ Signal handler: exit ungracefully on the second handled signal.
169
170     :param sig: the signal number
171     :param frame: the interrupted stack frame or None
172     """
173     global quit_signals_received
174     quit_signals_received += 1
175     if quit_signals_received > 1:
176         # logging.shutdown was already called at this point.
177         sys.stderr.write("Forced shutdown.\n")
178         os._exit(0)
179
180 def dumpstacks(sig, frame):
181     """ Signal handler: dump a stack trace for each existing thread."""
182     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
183     # modified for python 2.5 compatibility
184     thread_map = dict(threading._active, **threading._limbo)
185     id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
186     code = []
187     for threadId, stack in sys._current_frames().items():
188         code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
189         for filename, lineno, name, line in traceback.extract_stack(stack):
190             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
191             if line:
192                 code.append("  %s" % (line.strip()))
193     logging.getLogger('dumpstacks').info("\n".join(code))
194
195 def setup_signal_handlers():
196     """ Register the signal handler defined above. """
197     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
198     map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
199     if os.name == 'posix':
200         signal.signal(signal.SIGQUIT, dumpstacks)
201
202 def quit_on_signals():
203     """ Wait for one or two signals then shutdown the server.
204
205     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
206     a second one if any will force an immediate exit.
207
208     """
209     # Wait for a first signal to be handled. (time.sleep will be interrupted
210     # by the signal handler.)
211     while quit_signals_received == 0:
212         time.sleep(60)
213
214     openerp.netsvc.Agent.quit()
215     openerp.netsvc.Server.quitAll()
216     openerp.wsgi.stop_server()
217     config = openerp.tools.config
218     if config['pidfile']:
219         os.unlink(config['pidfile'])
220     logger = logging.getLogger('server')
221     logger.info("Initiating shutdown")
222     logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")
223     logging.shutdown()
224
225     # manually join() all threads before calling sys.exit() to allow a second signal
226     # to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
227     # threading.Thread.join() should not mask signals (at least in python 2.5)
228     for thread in threading.enumerate():
229         if thread != threading.currentThread() and not thread.isDaemon():
230             while thread.isAlive():
231                 # need a busyloop here as thread.join() masks signals
232                 # and would present the forced shutdown
233                 thread.join(0.05)
234                 time.sleep(0.05)
235     sys.exit(0)
236
237 if __name__ == "__main__":
238
239     os.environ["TZ"] = "UTC"
240
241     check_root_user()
242     openerp.tools.config.parse_config(sys.argv[1:])
243     check_postgres_user()
244     openerp.netsvc.init_logger()
245     report_configuration()
246
247     config = openerp.tools.config
248
249     setup_signal_handlers()
250
251     if config["test_file"]:
252         run_test_file(config['db_name'], config['test_file'])
253         sys.exit(0)
254
255     if config["translate_out"]:
256         export_translation()
257         sys.exit(0)
258
259     if config["translate_in"]:
260         import_translation()
261         sys.exit(0)
262
263     if not config["stop_after_init"]:
264         # Some module register themselves when they are loaded so we need the
265         # services to be running before loading any registry.
266         start_services()
267
268     if config['db_name']:
269         for dbname in config['db_name'].split(','):
270             preload_registry(dbname)
271
272     if config["stop_after_init"]:
273         sys.exit(0)
274
275     for m in openerp.conf.server_wide_modules:
276         __import__(m)
277         # Call any post_load hook.
278         info = openerp.modules.module.load_information_from_description_file(m)
279         if info['post_load']:
280             getattr(sys.modules[m], info['post_load'])()
281
282     setup_pid_file()
283     logger = logging.getLogger('server')
284     logger.info('OpenERP server is running, waiting for connections...')
285     quit_on_signals()
286
287 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: