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