report/render: fixes from xrg branch
[odoo/odoo.git] / bin / openerp-server.py
index 8e8115f..8d210fc 100755 (executable)
@@ -1,7 +1,7 @@
-#!/usr/bin/python
-# -*- encoding: utf-8 -*-
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
 ##############################################################################
 ##############################################################################
-#    
+#
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
@@ -16,7 +16,7 @@
 #    GNU Affero General Public License for more details.
 #
 #    You should have received a copy of the GNU Affero General Public License
 #    GNU Affero General Public License for more details.
 #
 #    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/>.
 #
 ##############################################################################
 
 #
 ##############################################################################
 
@@ -27,60 +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
+import logging
 import os
 import signal
 import os
 import 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.
-#----------------------------------------------------------
-_oldxml1 = '/usr/lib/python%s/site-packages/oldxml' % sys.version[:3]
-_oldxml2 = '/usr/lib/python%s/dist-packages/oldxml' % sys.version[:3]
-if os.path.exists(_oldxml1):
-    sys.path.insert(0,_oldxml1)
-elif os.path.exists(_oldxml2):
-    sys.path.insert(0,_oldxml2)    
-
+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']),
                     ('database hostname', tools.config['db_host'] or 'localhost'),
                     ('database port', tools.config['db_port'] or '5432'),
                     ('database user', tools.config['db_user'])]:
 for name, value in [('addons_path', tools.config['addons_path']),
                     ('database hostname', tools.config['db_host'] or 'localhost'),
                     ('database port', tools.config['db_port'] or '5432'),
                     ('database user', tools.config['db_user'])]:
-    logger.notifyChannel("server", netsvc.LOG_INFO, "%s - %s" % ( name, value ))
+    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
@@ -105,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
@@ -119,16 +136,14 @@ 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 successfully')
+    logger.info('translation file written successfully')
     sys.exit(0)
 
 if tools.config["translate_in"]:
     sys.exit(0)
 
 if tools.config["translate_in"]:
@@ -145,81 +160,82 @@ if tools.config["stop_after_init"]:
 
 
 #----------------------------------------------------------
 
 
 #----------------------------------------------------------
-# Launch Server
+# Launch Servers
 #----------------------------------------------------------
 
 #----------------------------------------------------------
 
-if tools.config['xmlrpc']:
-    port = int(tools.config['port'])
-    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']:
-    netport = int(tools.config['netport'])
-    netinterface = tools.config["netinterface"]
-    tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
-    logger.notifyChannel("web-services", netsvc.LOG_INFO, 
-                         "starting NET-RPC service, port %d" % (netport,))
-
 LST_SIGNALS = ['SIGINT', 'SIGTERM']
 LST_SIGNALS = ['SIGINT', 'SIGTERM']
-if os.name == 'posix':
-    LST_SIGNALS.extend(['SIGUSR1','SIGQUIT'])
-
 
 SIGNALS = dict(
     [(getattr(signal, sign), sign) for sign in LST_SIGNALS]
 )
 
 
 SIGNALS = dict(
     [(getattr(signal, sign), sign) for sign in LST_SIGNALS]
 )
 
-def handler(signum, _):
+netsvc.quit_signals_received = 0
+
+def handler(signum, frame):
     """
     :param signum: the signal number
     """
     :param signum: the signal number
-    :param _: 
+    :param frame: the interrupted stack frame or None
     """
     """
-    if tools.config['netrpc']:
-        tinySocket.stop()
-    if tools.config['xmlrpc']:
-        httpd.stop()
+    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()
+    netsvc.Server.quitAll()
     if tools.config['pidfile']:
         os.unlink(tools.config['pidfile'])
     if tools.config['pidfile']:
         os.unlink(tools.config['pidfile'])
-    logger.notifyChannel('shutdown', netsvc.LOG_INFO, 
-                         "Shutdown Server! - %s" % ( SIGNALS[signum], ))
-    logger.shutdown()
+    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)
 
-for signum in SIGNALS:
-    signal.signal(signum, handler)
-
 if tools.config['pidfile']:
     fd = open(tools.config['pidfile'], 'w')
     pidtext = "%d" % (os.getpid())
     fd.write(pidtext)
     fd.close()
 
 if tools.config['pidfile']:
     fd = open(tools.config['pidfile'], 'w')
     pidtext = "%d" % (os.getpid())
     fd.write(pidtext)
     fd.close()
 
-logger.notifyChannel("web-services", netsvc.LOG_INFO, 
-                     'the server is running, waiting for connections...')
+netsvc.Server.startAll()
 
 
-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: