2 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Management Solution
6 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
21 ##############################################################################
25 OpenERP is an ERP+CRM program for small and medium businesses.
27 The whole source code is distributed under the terms of the
30 (c) 2003-TODAY, Fabien Pinckaers - OpenERP SA
42 __author__ = openerp.release.author
43 __version__ = openerp.release.version
48 def check_root_user():
49 """ Exit if the process's user is 'root' (on POSIX system)."""
50 if os.name == 'posix':
52 if pwd.getpwuid(os.getuid())[0] == 'root' :
53 sys.stderr.write("Running as user 'root' is a security risk, aborting.\n")
56 def check_postgres_user():
57 """ Exit if the configured database user is 'postgres'.
59 This function assumes the configuration has been initialized.
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.")
66 def report_configuration():
67 """ Log the server version and some configuration values.
69 This function assumes the configuration has been initialized.
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)
81 """ Create a file with the process id written in it.
83 This function assumes the configuration has been initialized.
85 config = openerp.tools.config
87 fd = open(config['pidfile'], 'w')
88 pidtext = "%d" % (os.getpid())
92 def preload_registry(dbname):
93 """ Preload a registry, and start the cron."""
95 db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
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()
100 logging.exception('Failed to initialize database `%s`.', dbname)
102 def run_test_file(dbname, test_file):
103 """ Preload a registry, possibly run a test file, and start the cron."""
105 db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
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)
113 logging.exception('Failed to initialize database `%s` and run test file `%s`.', dbname, test_file)
116 def export_translation():
117 config = openerp.tools.config
118 dbname = config['db_name']
119 logger = logging.getLogger('server')
121 if config["language"]:
122 msg = "language %s" % (config["language"],)
125 logger.info('writing translation file for %s to %s', msg,
126 config["translate_out"])
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)
136 logger.info('translation file written successfully')
138 def import_translation():
139 config = openerp.tools.config
140 context = {'overwrite': config["overwrite_existing_translations"]}
141 dbname = config['db_name']
143 cr = openerp.pooler.get_db(dbname).cursor()
144 openerp.tools.trans_load( cr, config["translate_in"], config["language"],
146 openerp.tools.trans_update_res_ids(cr)
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
154 def signal_handler(sig, frame):
155 """ Signal handler: exit ungracefully on the second handled signal.
157 :param sig: the signal number
158 :param frame: the interrupted stack frame or None
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")
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()])
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))
179 code.append(" %s" % (line.strip()))
180 logging.getLogger('dumpstacks').info("\n".join(code))
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':
190 win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
192 def quit_on_signals():
193 """ Wait for one or two signals then shutdown the server.
195 The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
196 a second one if any will force an immediate exit.
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.
202 while quit_signals_received == 0:
204 except KeyboardInterrupt, e:
207 if config['pidfile']:
208 os.unlink(config['pidfile'])
210 openerp.service.stop_services()
213 if __name__ == "__main__":
215 os.environ["TZ"] = "UTC"
218 openerp.tools.config.parse_config(sys.argv[1:])
221 class ImportHook(object):
223 Import hook to load OpenERP addons from multiple paths.
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`.
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
236 import openerp.addons.hr
237 loads the hr addons only once.
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.
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.addons.'):
248 return self # We act as a loader too.
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):
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')
260 Ambiguous import: the OpenERP module `%s` is shadowed by another
261 module (available at %s).
262 To import it, use `import openerp.addons.<module>.`.""" % (module_name, path))
264 except ImportError, e:
265 # Using `import <module_name>` instead of
266 # `import openerp.addons.<module_name>` is ugly but not harmful
267 # and kept for backward compatibility.
268 return self # We act as a loader too.
270 def load_module(self, module_name):
272 module_parts = module_name.split('.')
273 if len(module_parts) == 3 and module_name.startswith('openerp.addons.'):
274 module_part = module_parts[2]
275 if module_name in sys.modules:
276 return sys.modules[module_name]
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]
284 # Check if the bare module name shadows another module.
285 f, path, descr = imp.find_module(module_part)
287 except ImportError, e:
288 # Using `import <module_name>` instead of
289 # `import openerp.addons.<module_name>` is ugly but not harmful
290 # and kept for backward compatibility.
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)
297 sys.modules[module_part] = mod
298 sys.modules['openerp.addons.' + module_part] = mod
301 openerp.modules.module.initialize_sys_path()
302 sys.meta_path.append(ImportHook())
304 check_postgres_user()
305 openerp.netsvc.init_logger()
306 report_configuration()
308 config = openerp.tools.config
310 setup_signal_handlers()
312 if config["test_file"]:
313 run_test_file(config['db_name'], config['test_file'])
316 if config["translate_out"]:
320 if config["translate_in"]:
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()
329 for m in openerp.conf.server_wide_modules:
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'])()
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)
344 if config['db_name']:
345 for dbname in config['db_name'].split(','):
346 preload_registry(dbname)
348 if config["stop_after_init"]:
352 logger = logging.getLogger('server')
353 logger.info('OpenERP server is running, waiting for connections...')
356 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: