[MERGE] sync with latest 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 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, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
93
94         # jobs will start to be processed later, when openerp.cron.start_master_thread() is called by openerp.service.start_services()
95         registry.schedule_cron_jobs()
96     except Exception:
97         logging.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 = logging.getLogger('server')
105         logger.info('loading test file %s', test_file)
106         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), {}, 'test', True)
107         cr.rollback()
108         cr.close()
109     except Exception:
110         logging.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
111
112
113 def export_translation():
114     config = openerp.tools.config
115     dbname = config['db_name']
116     logger = logging.getLogger('server')
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     openerp.tools.trans_update_res_ids(cr)
144     cr.commit()
145     cr.close()
146
147 # Variable keeping track of the number of calls to the signal handler defined
148 # below. This variable is monitored by ``quit_on_signals()``.
149 quit_signals_received = 0
150
151 def signal_handler(sig, frame):
152     """ Signal handler: exit ungracefully on the second handled signal.
153
154     :param sig: the signal number
155     :param frame: the interrupted stack frame or None
156     """
157     global quit_signals_received
158     quit_signals_received += 1
159     if quit_signals_received > 1:
160         # logging.shutdown was already called at this point.
161         sys.stderr.write("Forced shutdown.\n")
162         os._exit(0)
163
164 def dumpstacks(sig, frame):
165     """ Signal handler: dump a stack trace for each existing thread."""
166     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
167     # modified for python 2.5 compatibility
168     thread_map = dict(threading._active, **threading._limbo)
169     id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
170     code = []
171     for threadId, stack in sys._current_frames().items():
172         code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
173         for filename, lineno, name, line in traceback.extract_stack(stack):
174             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
175             if line:
176                 code.append("  %s" % (line.strip()))
177     logging.getLogger('dumpstacks').info("\n".join(code))
178
179 def setup_signal_handlers():
180     """ Register the signal handler defined above. """
181     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
182     map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
183     if os.name == 'posix':
184         signal.signal(signal.SIGQUIT, dumpstacks)
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.)
195     while quit_signals_received == 0:
196         time.sleep(60)
197
198     if config['pidfile']:
199         os.unlink(config['pidfile'])
200
201     openerp.service.stop_services()
202     sys.exit(0)
203
204 if __name__ == "__main__":
205
206     os.environ["TZ"] = "UTC"
207
208     check_root_user()
209     openerp.tools.config.parse_config(sys.argv[1:])
210     check_postgres_user()
211     openerp.netsvc.init_logger()
212     report_configuration()
213
214     config = openerp.tools.config
215
216     setup_signal_handlers()
217
218     if config["test_file"]:
219         run_test_file(config['db_name'], config['test_file'])
220         sys.exit(0)
221
222     if config["translate_out"]:
223         export_translation()
224         sys.exit(0)
225
226     if config["translate_in"]:
227         import_translation()
228         sys.exit(0)
229
230     if not config["stop_after_init"]:
231         # Some module register themselves when they are loaded so we need the
232         # services to be running before loading any registry.
233         openerp.service.start_services()
234
235     if config['db_name']:
236         for dbname in config['db_name'].split(','):
237             preload_registry(dbname)
238
239     if config["stop_after_init"]:
240         sys.exit(0)
241
242     for m in openerp.conf.server_wide_modules:
243         try:
244             __import__(m)
245             # Call any post_load hook.
246             info = openerp.modules.module.load_information_from_description_file(m)
247             if info['post_load']:
248                 getattr(sys.modules[m], info['post_load'])()
249         except Exception:
250             logging.exception('Failed to load server-wide module `%s`', m)
251
252     setup_pid_file()
253     logger = logging.getLogger('server')
254     logger.info('OpenERP server is running, waiting for connections...')
255     quit_on_signals()
256
257 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: