[MERGE] merged 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         config = openerp.tools.config
95         update_module = True if config['init'] or config['update'] else False
96         db, registry = openerp.pooler.get_db_and_pool(
97             dbname, update_module=update_module, pooljobs=False,
98             force_demo=not config['without_demo'])
99
100         # jobs will start to be processed later, when openerp.cron.start_master_thread() is called by openerp.service.start_services()
101         registry.schedule_cron_jobs()
102     except Exception:
103         _logger.exception('Failed to initialize database `%s`.', dbname)
104
105 def run_test_file(dbname, test_file):
106     """ Preload a registry, possibly run a test file, and start the cron."""
107     try:
108         update_module = True if config['init'] or config['update'] else False
109         db, registry = openerp.pooler.get_db_and_pool(
110             dbname, update_module=update_module, pooljobs=False, force_demo=not config['without_demo'])
111         cr = db.cursor()
112         _logger.info('loading test file %s', test_file)
113         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'test', True)
114         cr.rollback()
115         cr.close()
116     except Exception:
117         _logger.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
118
119 def export_translation():
120     config = openerp.tools.config
121     dbname = config['db_name']
122
123     if config["language"]:
124         msg = "language %s" % (config["language"],)
125     else:
126         msg = "new language"
127     _logger.info('writing translation file for %s to %s', msg,
128         config["translate_out"])
129
130     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
131     buf = file(config["translate_out"], "w")
132     cr = openerp.pooler.get_db(dbname).cursor()
133     openerp.tools.trans_export(config["language"],
134         config["translate_modules"] or ["all"], buf, fileformat, cr)
135     cr.close()
136     buf.close()
137
138     _logger.info('translation file written successfully')
139
140 def import_translation():
141     config = openerp.tools.config
142     context = {'overwrite': config["overwrite_existing_translations"]}
143     dbname = config['db_name']
144
145     cr = openerp.pooler.get_db(dbname).cursor()
146     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
147         context=context)
148     cr.commit()
149     cr.close()
150
151 # Variable keeping track of the number of calls to the signal handler defined
152 # below. This variable is monitored by ``quit_on_signals()``.
153 quit_signals_received = 0
154
155 def signal_handler(sig, frame):
156     """ Signal handler: exit ungracefully on the second handled signal.
157
158     :param sig: the signal number
159     :param frame: the interrupted stack frame or None
160     """
161     global quit_signals_received
162     quit_signals_received += 1
163     if quit_signals_received > 1:
164         # logging.shutdown was already called at this point.
165         sys.stderr.write("Forced shutdown.\n")
166         os._exit(0)
167
168 def dumpstacks(sig, frame):
169     """ Signal handler: dump a stack trace for each existing thread."""
170     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
171     # modified for python 2.5 compatibility
172     threads_info = dict([(th.ident, {'name': th.name,
173                                     'uid': getattr(th,'uid','n/a')})
174                                 for th in threading.enumerate()])
175     code = []
176     for threadId, stack in sys._current_frames().items():
177         thread_info = threads_info.get(threadId)
178         code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
179                     (thread_info and thread_info['name'] or 'n/a',
180                      threadId,
181                      thread_info and thread_info['uid'] or 'n/a'))
182         for filename, lineno, name, line in traceback.extract_stack(stack):
183             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
184             if line:
185                 code.append("  %s" % (line.strip()))
186     _logger.info("\n".join(code))
187
188 def setup_signal_handlers():
189     """ Register the signal handler defined above. """
190     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
191     if os.name == 'posix':
192         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
193         signal.signal(signal.SIGQUIT, dumpstacks)
194     elif os.name == 'nt':
195         import win32api
196         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
197
198 def quit_on_signals():
199     """ Wait for one or two signals then shutdown the server.
200
201     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
202     a second one if any will force an immediate exit.
203
204     """
205     # Wait for a first signal to be handled. (time.sleep will be interrupted
206     # by the signal handler.) The try/except is for the win32 case.
207     try:
208         while quit_signals_received == 0:
209             time.sleep(60)
210     except KeyboardInterrupt:
211         pass
212
213     config = openerp.tools.config
214     if config['pidfile']:
215         os.unlink(config['pidfile'])
216
217     openerp.service.stop_services()
218     sys.exit(0)
219
220 def configure_babel_localedata_path():
221     # Workaround: py2exe and babel.
222     if hasattr(sys, 'frozen'):
223         import babel
224         babel.localedata._dirname = os.path.join(os.path.dirname(sys.executable), 'localedata')
225
226 def main():
227     os.environ["TZ"] = "UTC"
228
229     check_root_user()
230     openerp.tools.config.parse_config(sys.argv[1:])
231
232     check_postgres_user()
233     openerp.netsvc.init_logger()
234     report_configuration()
235
236     config = openerp.tools.config
237
238     configure_babel_localedata_path()
239
240     setup_signal_handlers()
241
242     if config["test_file"]:
243         run_test_file(config['db_name'], config['test_file'])
244         sys.exit(0)
245
246     if config["translate_out"]:
247         export_translation()
248         sys.exit(0)
249
250     if config["translate_in"]:
251         import_translation()
252         sys.exit(0)
253
254     if not config["stop_after_init"]:
255         setup_pid_file()
256         # Some module register themselves when they are loaded so we need the
257         # services to be running before loading any registry.
258         if config['workers']:
259             openerp.service.start_services_workers()
260         else:
261             openerp.service.start_services()
262
263     if config['db_name']:
264         for dbname in config['db_name'].split(','):
265             preload_registry(dbname)
266
267     if config["stop_after_init"]:
268         sys.exit(0)
269
270     _logger.info('OpenERP server is running, waiting for connections...')
271     quit_on_signals()
272
273 if __name__ == "__main__":
274     main()
275
276 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: