[IMP] account: performance improvement on the creation of new account.move (opw ...
[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', {}, 'test', True)
109         cr.rollback()
110         cr.close()
111     except Exception:
112         _logger.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
113
114 def export_translation():
115     config = openerp.tools.config
116     dbname = config['db_name']
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     cr.commit()
144     cr.close()
145
146 # Variable keeping track of the number of calls to the signal handler defined
147 # below. This variable is monitored by ``quit_on_signals()``.
148 quit_signals_received = 0
149
150 def signal_handler(sig, frame):
151     """ Signal handler: exit ungracefully on the second handled signal.
152
153     :param sig: the signal number
154     :param frame: the interrupted stack frame or None
155     """
156     global quit_signals_received
157     quit_signals_received += 1
158     if quit_signals_received > 1:
159         # logging.shutdown was already called at this point.
160         sys.stderr.write("Forced shutdown.\n")
161         os._exit(0)
162
163 def dumpstacks(sig, frame):
164     """ Signal handler: dump a stack trace for each existing thread."""
165     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
166     # modified for python 2.5 compatibility
167     threads_info = dict([(th.ident, {'name': th.name,
168                                     'uid': getattr(th,'uid','n/a')})
169                                 for th in threading.enumerate()])
170     code = []
171     for threadId, stack in sys._current_frames().items():
172         thread_info = threads_info.get(threadId)
173         code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
174                     (thread_info and thread_info['name'] or 'n/a',
175                      threadId,
176                      thread_info and thread_info['uid'] or 'n/a'))
177         for filename, lineno, name, line in traceback.extract_stack(stack):
178             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
179             if line:
180                 code.append("  %s" % (line.strip()))
181     _logger.info("\n".join(code))
182
183 def setup_signal_handlers():
184     """ Register the signal handler defined above. """
185     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
186     if os.name == 'posix':
187         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
188         signal.signal(signal.SIGQUIT, dumpstacks)
189     elif os.name == 'nt':
190         import win32api
191         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
192
193 def quit_on_signals():
194     """ Wait for one or two signals then shutdown the server.
195
196     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
197     a second one if any will force an immediate exit.
198
199     """
200     # Wait for a first signal to be handled. (time.sleep will be interrupted
201     # by the signal handler.) The try/except is for the win32 case.
202     try:
203         while quit_signals_received == 0:
204             time.sleep(60)
205     except KeyboardInterrupt:
206         pass
207
208     config = openerp.tools.config
209     openerp.service.stop_services()
210
211     if getattr(openerp, 'phoenix', False):
212         # like the phoenix, reborn from ashes...
213         openerp.service._reexec()
214         return
215
216     if config['pidfile']:
217         os.unlink(config['pidfile'])
218     sys.exit(0)
219
220 def main(args):
221     check_root_user()
222     openerp.tools.config.parse_config(args)
223
224     check_postgres_user()
225     openerp.netsvc.init_logger()
226     report_configuration()
227
228     config = openerp.tools.config
229
230     setup_signal_handlers()
231
232     if config["test_file"]:
233         run_test_file(config['db_name'], config['test_file'])
234         sys.exit(0)
235
236     if config["translate_out"]:
237         export_translation()
238         sys.exit(0)
239
240     if config["translate_in"]:
241         import_translation()
242         sys.exit(0)
243
244     if not config["stop_after_init"]:
245         setup_pid_file()
246         # Some module register themselves when they are loaded so we need the
247         # services to be running before loading any registry.
248         if config['workers']:
249             openerp.service.start_services_workers()
250         else:
251             openerp.service.start_services()
252
253     if config['db_name']:
254         for dbname in config['db_name'].split(','):
255             preload_registry(dbname)
256
257     if config["stop_after_init"]:
258         sys.exit(0)
259
260     _logger.info('OpenERP server is running, waiting for connections...')
261     quit_on_signals()
262
263 class Server(Command):
264     def run(self, args):
265         main(args)
266
267 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: