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