Merge remote-tracking branch 'upstream/7.0' into 7.0-travis
[odoo/odoo.git] / openerp / cli / server.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 """
23 OpenERP - Server
24 OpenERP is an ERP+CRM program for small and medium businesses.
25
26 The whole source code is distributed under the terms of the
27 GNU Public Licence.
28
29 (c) 2003-TODAY, Fabien Pinckaers - OpenERP SA
30 """
31
32 import logging
33 import os
34 import signal
35 import sys
36 import threading
37 import traceback
38 import time
39
40 import openerp
41
42 from . import Command
43
44 __author__ = openerp.release.author
45 __version__ = openerp.release.version
46
47 # Also use the `openerp` logger for the main script.
48 _logger = logging.getLogger('openerp')
49
50 def check_root_user():
51     """ Exit if the process's user is 'root' (on POSIX system)."""
52     if os.name == 'posix':
53         import pwd
54         if pwd.getpwuid(os.getuid())[0] == 'root' :
55             sys.stderr.write("Running as user 'root' is a security risk, aborting.\n")
56             sys.exit(1)
57
58 def check_postgres_user():
59     """ Exit if the configured database user is 'postgres'.
60
61     This function assumes the configuration has been initialized.
62     """
63     config = openerp.tools.config
64     if config['db_user'] == 'postgres':
65         sys.stderr.write("Using the database user 'postgres' is a security risk, aborting.")
66         sys.exit(1)
67
68 def report_configuration():
69     """ Log the server version and some configuration values.
70
71     This function assumes the configuration has been initialized.
72     """
73     config = openerp.tools.config
74     _logger.info("OpenERP version %s", __version__)
75     for name, value in [('addons paths', config['addons_path']),
76                         ('database hostname', config['db_host'] or 'localhost'),
77                         ('database port', config['db_port'] or '5432'),
78                         ('database user', config['db_user'])]:
79         _logger.info("%s: %s", name, value)
80
81 def setup_pid_file():
82     """ Create a file with the process id written in it.
83
84     This function assumes the configuration has been initialized.
85     """
86     config = openerp.tools.config
87     if config['pidfile']:
88         fd = open(config['pidfile'], 'w')
89         pidtext = "%d" % (os.getpid())
90         fd.write(pidtext)
91         fd.close()
92
93 def preload_registry(dbname):
94     """ Preload a registry, and start the cron."""
95     try:
96         update_module = True if openerp.tools.config['init'] or openerp.tools.config['update'] else False
97         db, registry = openerp.pooler.get_db_and_pool(dbname,update_module=update_module)
98     except Exception:
99         _logger.exception('Failed to initialize database `%s`.', dbname)
100
101 def run_test_file(dbname, test_file):
102     """ Preload a registry, possibly run a test file, and start the cron."""
103     try:
104         config = openerp.tools.config
105         db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'])
106         cr = db.cursor()
107         _logger.info('loading test file %s', test_file)
108         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'init')
109
110         if config['test_commit']:
111             _logger.info('test %s has been commited', test_file)
112             cr.commit()
113         else:
114             _logger.info('test %s has been rollbacked', test_file)
115             cr.rollback()
116
117         cr.close()
118     except Exception:
119         _logger.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
120
121 def export_translation():
122     config = openerp.tools.config
123     dbname = config['db_name']
124
125     if config["language"]:
126         msg = "language %s" % (config["language"],)
127     else:
128         msg = "new language"
129     _logger.info('writing translation file for %s to %s', msg,
130         config["translate_out"])
131
132     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
133     buf = file(config["translate_out"], "w")
134     cr = openerp.pooler.get_db(dbname).cursor()
135     openerp.tools.trans_export(config["language"],
136         config["translate_modules"] or ["all"], buf, fileformat, cr)
137     cr.close()
138     buf.close()
139
140     _logger.info('translation file written successfully')
141
142 def import_translation():
143     config = openerp.tools.config
144     context = {'overwrite': config["overwrite_existing_translations"]}
145     dbname = config['db_name']
146
147     cr = openerp.pooler.get_db(dbname).cursor()
148     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
149         context=context)
150     cr.commit()
151     cr.close()
152
153 # Variable keeping track of the number of calls to the signal handler defined
154 # below. This variable is monitored by ``quit_on_signals()``.
155 quit_signals_received = 0
156
157 def signal_handler(sig, frame):
158     """ Signal handler: exit ungracefully on the second handled signal.
159
160     :param sig: the signal number
161     :param frame: the interrupted stack frame or None
162     """
163     global quit_signals_received
164     quit_signals_received += 1
165     if quit_signals_received > 1:
166         # logging.shutdown was already called at this point.
167         sys.stderr.write("Forced shutdown.\n")
168         os._exit(0)
169
170 def dumpstacks(sig, frame):
171     """ Signal handler: dump a stack trace for each existing thread."""
172     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
173     # modified for python 2.5 compatibility
174     threads_info = dict([(th.ident, {'name': th.name,
175                                     'uid': getattr(th,'uid','n/a')})
176                                 for th in threading.enumerate()])
177     code = []
178     for threadId, stack in sys._current_frames().items():
179         thread_info = threads_info.get(threadId)
180         code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
181                     (thread_info and thread_info['name'] or 'n/a',
182                      threadId,
183                      thread_info and thread_info['uid'] or 'n/a'))
184         for filename, lineno, name, line in traceback.extract_stack(stack):
185             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
186             if line:
187                 code.append("  %s" % (line.strip()))
188     _logger.info("\n".join(code))
189
190 def setup_signal_handlers():
191     """ Register the signal handler defined above. """
192     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
193     if os.name == 'posix':
194         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
195         signal.signal(signal.SIGQUIT, dumpstacks)
196     elif os.name == 'nt':
197         import win32api
198         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
199
200 def quit_on_signals():
201     """ Wait for one or two signals then shutdown the server.
202
203     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
204     a second one if any will force an immediate exit.
205
206     """
207     # Wait for a first signal to be handled. (time.sleep will be interrupted
208     # by the signal handler.) The try/except is for the win32 case.
209     try:
210         while quit_signals_received == 0:
211             time.sleep(60)
212     except KeyboardInterrupt:
213         pass
214
215     config = openerp.tools.config
216     openerp.service.stop_services()
217
218     if getattr(openerp, 'phoenix', False):
219         # like the phoenix, reborn from ashes...
220         openerp.service._reexec()
221         return
222
223     if config['pidfile']:
224         os.unlink(config['pidfile'])
225     sys.exit(0)
226
227 def main(args):
228     check_root_user()
229     openerp.tools.config.parse_config(args)
230
231     check_postgres_user()
232     openerp.netsvc.init_logger()
233     report_configuration()
234
235     config = openerp.tools.config
236
237     setup_signal_handlers()
238
239     if config["test_file"]:
240         run_test_file(config['db_name'], config['test_file'])
241         sys.exit(0)
242
243     if config["translate_out"]:
244         export_translation()
245         sys.exit(0)
246
247     if config["translate_in"]:
248         import_translation()
249         sys.exit(0)
250
251     if not config["stop_after_init"]:
252         setup_pid_file()
253         # Some module register themselves when they are loaded so we need the
254         # services to be running before loading any registry.
255         if config['workers']:
256             openerp.service.start_services_workers()
257         else:
258             openerp.service.start_services()
259
260     if config['db_name']:
261         for dbname in config['db_name'].split(','):
262             preload_registry(dbname)
263
264     if config["stop_after_init"]:
265         sys.exit(0)
266
267     _logger.info('OpenERP server is running, waiting for connections...')
268     quit_on_signals()
269
270 class Server(Command):
271     def run(self, args):
272         main(args)
273
274 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: