[FIX] stock, sale_stock, purchase: fixed the dropshipping flow
[odoo/odoo.git] / openerp / cli / server.py
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
43 from . import Command
44
45 __author__ = openerp.release.author
46 __version__ = openerp.release.version
47
48 # Also use the `openerp` logger for the main script.
49 _logger = logging.getLogger('openerp')
50
51 def check_root_user():
52     """ Exit if the process's user is 'root' (on POSIX system)."""
53     if os.name == 'posix':
54         import pwd
55         if pwd.getpwuid(os.getuid())[0] == 'root' :
56             sys.stderr.write("Running as user 'root' is a security risk, aborting.\n")
57             sys.exit(1)
58
59 def check_postgres_user():
60     """ Exit if the configured database user is 'postgres'.
61
62     This function assumes the configuration has been initialized.
63     """
64     config = openerp.tools.config
65     if config['db_user'] == 'postgres':
66         sys.stderr.write("Using the database user 'postgres' is a security risk, aborting.")
67         sys.exit(1)
68
69 def report_configuration():
70     """ Log the server version and some configuration values.
71
72     This function assumes the configuration has been initialized.
73     """
74     config = openerp.tools.config
75     _logger.info("OpenERP version %s", __version__)
76     for name, value in [('addons paths', config['addons_path']),
77                         ('database hostname', config['db_host'] or 'localhost'),
78                         ('database port', config['db_port'] or '5432'),
79                         ('database user', config['db_user'])]:
80         _logger.info("%s: %s", name, value)
81
82 def setup_pid_file():
83     """ Create a file with the process id written in it.
84
85     This function assumes the configuration has been initialized.
86     """
87     config = openerp.tools.config
88     if config['pidfile']:
89         fd = open(config['pidfile'], 'w')
90         pidtext = "%d" % (os.getpid())
91         fd.write(pidtext)
92         fd.close()
93
94 def preload_registry(dbname):
95     """ Preload a registry, and start the cron."""
96     try:
97         update_module = True if openerp.tools.config['init'] or openerp.tools.config['update'] else False
98         openerp.modules.registry.RegistryManager.new(dbname, update_module=update_module)
99     except Exception:
100         _logger.exception('Failed to initialize database `%s`.', dbname)
101
102 def run_test_file(dbname, test_file):
103     """ Preload a registry, possibly run a test file, and start the cron."""
104     try:
105         config = openerp.tools.config
106         registry = openerp.modules.registry.RegistryManager.new(dbname, update_module=config['init'] or config['update'])
107         cr = registry.db.cursor()
108         _logger.info('loading test file %s', test_file)
109         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'test', True)
110         cr.rollback()
111         cr.close()
112     except Exception:
113         _logger.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
114
115 def export_translation():
116     config = openerp.tools.config
117     dbname = config['db_name']
118
119     if config["language"]:
120         msg = "language %s" % (config["language"],)
121     else:
122         msg = "new language"
123     _logger.info('writing translation file for %s to %s', msg,
124         config["translate_out"])
125
126     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
127     buf = file(config["translate_out"], "w")
128     registry = openerp.modules.registry.RegistryManager.new(dbname)
129     cr = registry.db.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     registry = openerp.modules.registry.RegistryManager.new(dbname)
143     cr = registry.db.cursor()
144     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
145         context=context)
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     threads_info = dict([(th.ident, {'name': th.name,
171                                     'uid': getattr(th,'uid','n/a')})
172                                 for th in threading.enumerate()])
173     code = []
174     for threadId, stack in sys._current_frames().items():
175         thread_info = threads_info.get(threadId)
176         code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
177                     (thread_info and thread_info['name'] or 'n/a',
178                      threadId,
179                      thread_info and thread_info['uid'] or 'n/a'))
180         for filename, lineno, name, line in traceback.extract_stack(stack):
181             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
182             if line:
183                 code.append("  %s" % (line.strip()))
184     _logger.info("\n".join(code))
185
186 def setup_signal_handlers(signal_handler):
187     """ Register the given signal handler. """
188     SIGNALS = (signal.SIGINT, signal.SIGTERM)
189     if os.name == 'posix':
190         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
191         signal.signal(signal.SIGQUIT, dumpstacks)
192     elif os.name == 'nt':
193         import win32api
194         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
195
196 def quit_on_signals():
197     """ Wait for one or two signals then shutdown the server.
198
199     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
200     a second one if any will force an immediate exit.
201
202     """
203     # Wait for a first signal to be handled. (time.sleep will be interrupted
204     # by the signal handler.) The try/except is for the win32 case.
205     try:
206         while quit_signals_received == 0:
207             time.sleep(60)
208     except KeyboardInterrupt:
209         pass
210
211     config = openerp.tools.config
212     openerp.service.stop_services()
213
214     if getattr(openerp, 'phoenix', False):
215         # like the phoenix, reborn from ashes...
216         openerp.service._reexec()
217         return
218
219     if config['pidfile']:
220         os.unlink(config['pidfile'])
221     sys.exit(0)
222
223 def main(args):
224     check_root_user()
225     openerp.tools.config.parse_config(args)
226
227     if openerp.tools.config.options["gevent"]:
228         openerp.evented = True
229         _logger.info('Using gevent mode')
230         import gevent.monkey
231         gevent.monkey.patch_all()
232         import gevent_psycopg2
233         gevent_psycopg2.monkey_patch()
234
235     check_postgres_user()
236     openerp.netsvc.init_logger()
237     report_configuration()
238
239     config = openerp.tools.config
240
241     setup_signal_handlers(signal_handler)
242
243     if config["test_file"]:
244         run_test_file(config['db_name'], config['test_file'])
245         sys.exit(0)
246
247     if config["translate_out"]:
248         export_translation()
249         sys.exit(0)
250
251     if config["translate_in"]:
252         import_translation()
253         sys.exit(0)
254
255     if not config["stop_after_init"]:
256         setup_pid_file()
257         # Some module register themselves when they are loaded so we need the
258         # services to be running before loading any registry.
259         if config['workers']:
260             openerp.service.start_services_workers()
261         else:
262             openerp.service.start_services()
263
264     if config['db_name']:
265         for dbname in config['db_name'].split(','):
266             preload_registry(dbname)
267
268     if config["stop_after_init"]:
269         sys.exit(0)
270
271     _logger.info('OpenERP server is running, waiting for connections...')
272     quit_on_signals()
273
274 class Server(Command):
275     def run(self, args):
276         main(args)
277
278 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: