osv: Improve the translation logic for the osv.check wrapper
[odoo/odoo.git] / bin / openerp-server.py
index 36a9caf..8d210fc 100755 (executable)
@@ -1,22 +1,21 @@
-#!/usr/bin/python
-# -*- encoding: utf-8 -*-
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
 ##############################################################################
 #
 ##############################################################################
 #
-#    OpenERP, Open Source Management Solution  
-#    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
-#    $Id$
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #
 #    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
 #
 #    This program is distributed in the hope that it will be useful,
 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 #
 #    This program is distributed in the hope that it will be useful,
 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
+#    GNU Affero General Public License for more details.
 #
 #
-#    You should have received a copy of the GNU General Public License
+#    You should have received a copy of the GNU Affero General Public License
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
@@ -28,53 +27,59 @@ OpenERP is an ERP+CRM program for small and medium businesses.
 The whole source code is distributed under the terms of the
 GNU Public Licence.
 
 The whole source code is distributed under the terms of the
 GNU Public Licence.
 
-(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl
+(c) 2003-TODAY, Fabien Pinckaers - OpenERP s.a.
 """
 
 #----------------------------------------------------------
 # python imports
 #----------------------------------------------------------
 """
 
 #----------------------------------------------------------
 # python imports
 #----------------------------------------------------------
-import sys, os, signal
-#----------------------------------------------------------
-# ubuntu 8.04 has obsoleted `pyxml` package and installs here.
-# the path needs to be updated before any `import xml`
-# TODO: remove PyXML dependencies, use lxml instead.
-#----------------------------------------------------------
-_oldxml = '/usr/lib/python%s/site-packages/oldxml' % sys.version[:3]
-if os.path.exists(_oldxml):
-    sys.path.append(_oldxml)
-
+import logging
+import os
+import signal
+import sys
+import threading
+import traceback
 
 import release
 __author__ = release.author
 __version__ = release.version
 
 
 import release
 __author__ = release.author
 __version__ = release.version
 
+if os.name == 'posix':
+    import pwd
+    # We DON't log this using the standard logger, because we might mess
+    # with the logfile's permissions. Just do a quick exit here.
+    if pwd.getpwuid(os.getuid())[0] == 'root' :
+        sys.stderr.write("Attempted to run OpenERP server as root. This is not good, aborting.\n")
+        sys.exit(1)
+
 #----------------------------------------------------------
 # get logger
 #----------------------------------------------------------
 import netsvc
 #----------------------------------------------------------
 # get logger
 #----------------------------------------------------------
 import netsvc
-logger = netsvc.Logger()
+logger = logging.getLogger('server')
 
 #-----------------------------------------------------------------------
 # import the tools module so that the commandline parameters are parsed
 #-----------------------------------------------------------------------
 import tools
 
 #-----------------------------------------------------------------------
 # import the tools module so that the commandline parameters are parsed
 #-----------------------------------------------------------------------
 import tools
-
-logger.notifyChannel("server", netsvc.LOG_INFO, "version - %s" % release.version )
+logger.info("OpenERP version - %s", release.version)
 for name, value in [('addons_path', tools.config['addons_path']),
 for name, value in [('addons_path', tools.config['addons_path']),
-                    ('database hostname', tools.config['db_host'] or 'localhost')]:
-    logger.notifyChannel("server", netsvc.LOG_INFO, "%s - %s" % ( name, value ))
+                    ('database hostname', tools.config['db_host'] or 'localhost'),
+                    ('database port', tools.config['db_port'] or '5432'),
+                    ('database user', tools.config['db_user'])]:
+    logger.info("%s - %s", name, value)
 
 
-import time
+# Don't allow if the connection to PostgreSQL done by postgres user
+if tools.config['db_user'] == 'postgres':
+    logger.error("Connecting to the database as 'postgres' user is forbidden, as it present major security issues. Shutting down.")
+    sys.exit(1)
 
 
-if sys.platform == 'win32':
-    import mx.DateTime
-    mx.DateTime.strptime = lambda x, y: mx.DateTime.mktime(time.strptime(x, y))
+import time
 
 #----------------------------------------------------------
 # init net service
 #----------------------------------------------------------
 
 #----------------------------------------------------------
 # init net service
 #----------------------------------------------------------
-logger.notifyChannel("objects", netsvc.LOG_INFO, 'initialising distributed objects services')
+logger.info('initialising distributed objects services')
 
 #---------------------------------------------------------------
 # connect to the database and initialize it with base if needed
 
 #---------------------------------------------------------------
 # connect to the database and initialize it with base if needed
@@ -82,18 +87,6 @@ logger.notifyChannel("objects", netsvc.LOG_INFO, 'initialising distributed objec
 import pooler
 
 #----------------------------------------------------------
 import pooler
 
 #----------------------------------------------------------
-# launch modules install/upgrade/removes if needed
-#----------------------------------------------------------
-if tools.config['upgrade']:
-    logger.notifyChannel('init', netsvc.LOG_INFO, 'Upgrading new modules...')
-    import tools.upgrade
-    (toinit, toupdate) = tools.upgrade.upgrade()
-    for m in toinit:
-        tools.config['init'][m] = 1
-    for m in toupdate:
-        tools.config['update'][m] = 1
-
-#----------------------------------------------------------
 # import basic modules
 #----------------------------------------------------------
 import osv
 # import basic modules
 #----------------------------------------------------------
 import osv
@@ -111,9 +104,27 @@ import addons
 # Load and update databases if requested
 #----------------------------------------------------------
 
 # Load and update databases if requested
 #----------------------------------------------------------
 
+import service.http_server
+
+if not ( tools.config["stop_after_init"] or \
+    tools.config["translate_in"] or \
+    tools.config["translate_out"] ):
+    service.http_server.init_servers()
+    service.http_server.init_xmlrpc()
+    service.http_server.init_static_http()
+
+    import service.netrpc_server
+    service.netrpc_server.init_servers()
+
 if tools.config['db_name']:
 if tools.config['db_name']:
-    for db in tools.config['db_name'].split(','):
-        pooler.get_db_and_pool(db, update_module=tools.config['init'] or tools.config['update'])
+    for dbname in tools.config['db_name'].split(','):
+        db,pool = pooler.get_db_and_pool(dbname, update_module=tools.config['init'] or tools.config['update'], pooljobs=False)
+        if tools.config["test_file"]:
+            logger.info('loading test file %s', tools.config["test_file"])
+            cr = db.cursor()
+            tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
+            cr.rollback()
+        pool.get('ir.cron')._poolJobs(db.dbname)
 
 #----------------------------------------------------------
 # translation stuff
 
 #----------------------------------------------------------
 # translation stuff
@@ -125,18 +136,20 @@ if tools.config["translate_out"]:
         msg = "language %s" % (tools.config["language"],)
     else:
         msg = "new language"
         msg = "language %s" % (tools.config["language"],)
     else:
         msg = "new language"
-    logger.notifyChannel("init", netsvc.LOG_INFO, 'writing translation file for %s to %s' % (msg, tools.config["translate_out"]))
+    logger.info('writing translation file for %s to %s', msg, tools.config["translate_out"])
 
     fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
     buf = file(tools.config["translate_out"], "w")
     tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat)
     buf.close()
 
 
     fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
     buf = file(tools.config["translate_out"], "w")
     tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat)
     buf.close()
 
-    logger.notifyChannel("init", netsvc.LOG_INFO, 'translation file written succesfully')
+    logger.info('translation file written successfully')
     sys.exit(0)
 
 if tools.config["translate_in"]:
     sys.exit(0)
 
 if tools.config["translate_in"]:
-    tools.trans_load(tools.config["db_name"], tools.config["translate_in"], tools.config["language"])
+    tools.trans_load(tools.config["db_name"], 
+                     tools.config["translate_in"], 
+                     tools.config["language"])
     sys.exit(0)
 
 #----------------------------------------------------------------------------------
     sys.exit(0)
 
 #----------------------------------------------------------------------------------
@@ -147,73 +160,82 @@ if tools.config["stop_after_init"]:
 
 
 #----------------------------------------------------------
 
 
 #----------------------------------------------------------
-# Launch Server
+# Launch Servers
 #----------------------------------------------------------
 
 #----------------------------------------------------------
 
-if tools.config['xmlrpc']:
-    try:
-        port = int(tools.config["port"])
-    except Exception:
-        logger.notifyChannel("init", netsvc.LOG_CRITICAL, "invalid port: %r" % (tools.config["port"],))
-        sys.exit(1)
-    interface = tools.config["interface"]
-    secure = tools.config["secure"]
-
-    httpd = netsvc.HttpDaemon(interface, port, secure)
-
-    xml_gw = netsvc.xmlrpc.RpcGateway('web-services')
-    httpd.attach("/xmlrpc", xml_gw)
-    logger.notifyChannel("web-services", netsvc.LOG_INFO, "starting XML-RPC%s services, port %s" % ((tools.config['secure'] and ' Secure' or ''), port))
-
-#
-#if tools.config["soap"]:
-#   soap_gw = netsvc.xmlrpc.RpcGateway('web-services')
-#   httpd.attach("/soap", soap_gw )
-#   logger.notifyChannel("web-services", netsvc.LOG_INFO, 'starting SOAP services, port '+str(port))
-#
-
-if tools.config['netrpc']:
-    try:
-        netport = int(tools.config["netport"])
-    except Exception:
-        logger.notifyChannel("init", netsvc.LOG_ERROR, "invalid port '%s'!" % (tools.config["netport"],))
-        sys.exit(1)
-    netinterface = tools.config["netinterface"]
+LST_SIGNALS = ['SIGINT', 'SIGTERM']
 
 
-    tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
-    logger.notifyChannel("web-services", netsvc.LOG_INFO, "starting NET-RPC service, port "+str(netport))
+SIGNALS = dict(
+    [(getattr(signal, sign), sign) for sign in LST_SIGNALS]
+)
 
 
+netsvc.quit_signals_received = 0
 
 def handler(signum, frame):
 
 def handler(signum, frame):
-    from tools import config
-    if tools.config['netrpc']:
-        tinySocket.stop()
-    if tools.config['xmlrpc']:
-        httpd.stop()
+    """
+    :param signum: the signal number
+    :param frame: the interrupted stack frame or None
+    """
+    netsvc.quit_signals_received += 1
+    if netsvc.quit_signals_received > 1:
+        sys.stderr.write("Forced shutdown.\n")
+        os._exit(0)
+
+def dumpstacks(signum, frame):
+    # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
+    # modified for python 2.5 compatibility
+    thread_map = dict(threading._active, **threading._limbo)
+    id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
+    code = []
+    for threadId, stack in sys._current_frames().items():
+        code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
+        for filename, lineno, name, line in traceback.extract_stack(stack):
+            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+            if line:
+                code.append("  %s" % (line.strip()))
+    logging.getLogger('dumpstacks').info("\n".join(code))
+
+for signum in SIGNALS:
+    signal.signal(signum, handler)
+
+if os.name == 'posix':
+    signal.signal(signal.SIGQUIT, dumpstacks)
+
+def quit():
     netsvc.Agent.quit()
     netsvc.Agent.quit()
-    if config['pidfile']:
-        os.unlink(config['pidfile'])
-    logger.notifyChannel('shutdown', netsvc.LOG_INFO, "Shutdown Server!")
+    netsvc.Server.quitAll()
+    if tools.config['pidfile']:
+        os.unlink(tools.config['pidfile'])
+    logger = logging.getLogger('shutdown')
+    logger.info("Initiating OpenERP Server shutdown")
+    logger.info("Hit CTRL-C again or send a second signal to immediately terminate the server...")
+    logging.shutdown()
+
+    # manually join() all threads before calling sys.exit() to allow a second signal
+    # to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
+    # threading.Thread.join() should not mask signals (at least in python 2.5)
+    for thread in threading.enumerate():
+        if thread != threading.currentThread() and not thread.isDaemon():
+            while thread.isAlive():
+                # need a busyloop here as thread.join() masks signals
+                # and would present the forced shutdown
+                thread.join(0.05)
+                time.sleep(0.05)
     sys.exit(0)
 
     sys.exit(0)
 
-from tools import config
-if config['pidfile']:
-    fd = open(config['pidfile'], 'w')
+if tools.config['pidfile']:
+    fd = open(tools.config['pidfile'], 'w')
     pidtext = "%d" % (os.getpid())
     fd.write(pidtext)
     fd.close()
 
     pidtext = "%d" % (os.getpid())
     fd.write(pidtext)
     fd.close()
 
-for sign in ('SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGQUIT'):
-    signal.signal(getattr(signal, sign), handler)
+netsvc.Server.startAll()
 
 
-logger.notifyChannel("web-services", netsvc.LOG_INFO, 'the server is running, waiting for connections...')
-if tools.config['netrpc']:
-    tinySocket.start()
-if tools.config['xmlrpc']:
-    httpd.start()
+logger.info('OpenERP server is running, waiting for connections...')
 
 
-while True:
-    time.sleep(1)
+while netsvc.quit_signals_received == 0:
+    time.sleep(60)
 
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+quit()
 
 
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: