[IMP] module: use the `openerp.addons.` namespace to load addons.
[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 import sys
46 import imp
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 = logging.getLogger('server')
73     logger.info("OpenERP version %s", __version__)
74     for name, value in [('addons paths', config['addons_path']),
75                         ('database hostname', config['db_host'] or 'localhost'),
76                         ('database port', config['db_port'] or '5432'),
77                         ('database user', config['db_user'])]:
78         logger.info("%s: %s", name, value)
79
80 def setup_pid_file():
81     """ Create a file with the process id written in it.
82
83     This function assumes the configuration has been initialized.
84     """
85     config = openerp.tools.config
86     if config['pidfile']:
87         fd = open(config['pidfile'], 'w')
88         pidtext = "%d" % (os.getpid())
89         fd.write(pidtext)
90         fd.close()
91
92 def preload_registry(dbname):
93     """ Preload a registry, and start the cron."""
94     try:
95         db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
96
97         # jobs will start to be processed later, when openerp.cron.start_master_thread() is called by openerp.service.start_services()
98         registry.schedule_cron_jobs()
99     except Exception:
100         logging.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         db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
106         cr = db.cursor()
107         logger = logging.getLogger('server')
108         logger.info('loading test file %s', test_file)
109         openerp.tools.convert_yaml_import(cr, 'base', file(test_file), {}, 'test', True)
110         cr.rollback()
111         cr.close()
112     except Exception:
113         logging.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
114
115
116 def export_translation():
117     config = openerp.tools.config
118     dbname = config['db_name']
119     logger = logging.getLogger('server')
120
121     if config["language"]:
122         msg = "language %s" % (config["language"],)
123     else:
124         msg = "new language"
125     logger.info('writing translation file for %s to %s', msg,
126         config["translate_out"])
127
128     fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
129     buf = file(config["translate_out"], "w")
130     cr = openerp.pooler.get_db(dbname).cursor()
131     openerp.tools.trans_export(config["language"],
132         config["translate_modules"] or ["all"], buf, fileformat, cr)
133     cr.close()
134     buf.close()
135
136     logger.info('translation file written successfully')
137
138 def import_translation():
139     config = openerp.tools.config
140     context = {'overwrite': config["overwrite_existing_translations"]}
141     dbname = config['db_name']
142
143     cr = openerp.pooler.get_db(dbname).cursor()
144     openerp.tools.trans_load( cr, config["translate_in"], config["language"],
145         context=context)
146     openerp.tools.trans_update_res_ids(cr)
147     cr.commit()
148     cr.close()
149
150 # Variable keeping track of the number of calls to the signal handler defined
151 # below. This variable is monitored by ``quit_on_signals()``.
152 quit_signals_received = 0
153
154 def signal_handler(sig, frame):
155     """ Signal handler: exit ungracefully on the second handled signal.
156
157     :param sig: the signal number
158     :param frame: the interrupted stack frame or None
159     """
160     global quit_signals_received
161     quit_signals_received += 1
162     if quit_signals_received > 1:
163         # logging.shutdown was already called at this point.
164         sys.stderr.write("Forced shutdown.\n")
165         os._exit(0)
166
167 def dumpstacks(sig, frame):
168     """ Signal handler: dump a stack trace for each existing thread."""
169     # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
170     # modified for python 2.5 compatibility
171     thread_map = dict(threading._active, **threading._limbo)
172     id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
173     code = []
174     for threadId, stack in sys._current_frames().items():
175         code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
176         for filename, lineno, name, line in traceback.extract_stack(stack):
177             code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
178             if line:
179                 code.append("  %s" % (line.strip()))
180     logging.getLogger('dumpstacks').info("\n".join(code))
181
182 def setup_signal_handlers():
183     """ Register the signal handler defined above. """
184     SIGNALS = map(lambda x: getattr(signal, "SIG%s" % x), "INT TERM".split())
185     if os.name == 'posix':
186         map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
187         signal.signal(signal.SIGQUIT, dumpstacks)
188     elif os.name == 'nt':
189         import win32api
190         win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
191
192 def quit_on_signals():
193     """ Wait for one or two signals then shutdown the server.
194
195     The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
196     a second one if any will force an immediate exit.
197
198     """
199     # Wait for a first signal to be handled. (time.sleep will be interrupted
200     # by the signal handler.) The try/except is for the win32 case.
201     try:
202         while quit_signals_received == 0:
203             time.sleep(60)
204     except KeyboardInterrupt, e:
205         pass
206
207     if config['pidfile']:
208         os.unlink(config['pidfile'])
209
210     openerp.service.stop_services()
211     sys.exit(0)
212
213 if __name__ == "__main__":
214
215     os.environ["TZ"] = "UTC"
216
217     check_root_user()
218     openerp.tools.config.parse_config(sys.argv[1:])
219
220
221     class ImportHook(object):
222         """
223         Import hook to load OpenERP addons from multiple paths.
224
225         OpenERP implements its own import-hook to load its addons. OpenERP
226         addons are Python modules. Originally, they were each living in their
227         own top-level namespace, e.g. the sale module, or the hr module. For
228         backward compatibility, `import <module>` is still supported. Now they
229         are living in `openerp.addons`. The good way to import such modules is
230         thus `import openerp.addons.module`.
231
232         For backward compatibility, loading an addons puts it in `sys.modules`
233         under both the legacy (short) name, and the new (longer) name. This
234         ensures that
235             import hr
236             import openerp.addons.hr
237         loads the hr addons only once.
238
239         When an OpenERP addons name clashes with some other installed Python
240         module (for instance this is the case of the `resource` addons),
241         obtaining the OpenERP addons is only possible with the long name. The
242         short name will give the expected Python module.
243         """
244
245         def find_module(self, module_name, package_path):
246             module_parts = module_name.split('.')
247             if len(module_parts) == 3 and module_name.startswith('openerp.modules.'):
248                 return self # We act as a loader too.
249
250             # TODO list of loadable modules can be cached instead of always
251             # calling get_module_path().
252             if len(module_parts) == 1 and \
253                 openerp.modules.module.get_module_path(module_parts[0],
254                     display_warning=False):
255                 try:
256                     # Check if the bare module name clashes with another module.
257                     f, path, descr = imp.find_module(module_parts[0])
258                     logger = logging.getLogger('init')
259                     logger.warning("""
260     Ambiguous import: the OpenERP module `%s` is shadowed by another
261     module (available at %s).
262     To import it, use `import openerp.modules.<module>.`.""" % (module_name, path))
263                     return
264                 except ImportError, e:
265                     # Using `import <module_name>` instead of
266                     # `import openerp.modules.<module_name>` is ugly but not harmful
267                     # and kept for backward compatibility.
268                     return self # We act as a loader too.
269
270         def load_module(self, module_name):
271
272             module_parts = module_name.split('.')
273             if len(module_parts) == 3 and module_name.startswith('openerp.modules.'):
274                 module_part = module_parts[2]
275                 if module_name in sys.modules:
276                     return sys.modules[module_name]
277
278             if len(module_parts) == 1:
279                 module_part = module_parts[0]
280                 if module_part in sys.modules:
281                     return sys.modules[module_part]
282
283             try:
284                 # Check if the bare module name shadows another module.
285                 f, path, descr = imp.find_module(module_part)
286                 is_shadowing = True
287             except ImportError, e:
288                 # Using `import <module_name>` instead of
289                 # `import openerp.modules.<module_name>` is ugly but not harmful
290                 # and kept for backward compatibility.
291                 is_shadowing = False
292
293             # Note: we don't support circular import.
294             f, path, descr = imp.find_module(module_part, openerp.modules.module.ad_paths)
295             mod = imp.load_module(module_name, f, path, descr)
296             if not is_shadowing:
297                 sys.modules[module_part] = mod
298             sys.modules['openerp.modules.' + module_part] = mod
299             return mod
300
301     openerp.modules.module.initialize_sys_path()
302     sys.meta_path.append(ImportHook())
303
304     check_postgres_user()
305     openerp.netsvc.init_logger()
306     report_configuration()
307
308     config = openerp.tools.config
309
310     setup_signal_handlers()
311
312     if config["test_file"]:
313         run_test_file(config['db_name'], config['test_file'])
314         sys.exit(0)
315
316     if config["translate_out"]:
317         export_translation()
318         sys.exit(0)
319
320     if config["translate_in"]:
321         import_translation()
322         sys.exit(0)
323
324     if not config["stop_after_init"]:
325         # Some module register themselves when they are loaded so we need the
326         # services to be running before loading any registry.
327         openerp.service.start_services()
328
329     for m in openerp.conf.server_wide_modules:
330         try:
331             __import__(m)
332             # Call any post_load hook.
333             info = openerp.modules.module.load_information_from_description_file(m)
334             if info['post_load']:
335                 getattr(sys.modules[m], info['post_load'])()
336         except Exception:
337             msg = ''
338             if m == 'web':
339                 msg = """
340 The `web` module is provided by the addons found in the `openerp-web` project.
341 Maybe you forgot to add those addons in your addons_path configuration."""
342             logging.exception('Failed to load server-wide module `%s`.%s', m, msg)
343
344     if config['db_name']:
345         for dbname in config['db_name'].split(','):
346             preload_registry(dbname)
347
348     if config["stop_after_init"]:
349         sys.exit(0)
350
351     setup_pid_file()
352     logger = logging.getLogger('server')
353     logger.info('OpenERP server is running, waiting for connections...')
354     quit_on_signals()
355
356 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: