diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 0c1709dbed5..4ab6aa85748 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -3,7 +3,7 @@ import openerp bind = '127.0.0.1:8069' pidfile = '.gunicorn.pid' # This is the big TODO: safely use more than a single worker. -workers = 1 +workers = 2 # Some application-wide initialization is needed. on_starting = openerp.wsgi.on_starting when_ready = openerp.wsgi.when_ready diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index df848b94e67..8d05b5b776d 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -96,7 +96,8 @@ class db(netsvc.ExportService): def dispatch(self, method, auth, params): if method in [ 'create', 'get_progress', 'drop', 'dump', 'restore', 'rename', - 'change_admin_password', 'migrate_databases' ]: + 'change_admin_password', 'migrate_databases', + 'create_database' ]: passwd = params[0] params = params[1:] security.check_super(passwd) @@ -135,6 +136,20 @@ class db(netsvc.ExportService): self.actions[id]['thread'] = create_thread return id + def exp_create_database(self, db_name, demo, lang, user_password='admin'): + """ Similar to exp_create but blocking.""" + self.id_protect.acquire() + self.id += 1 + id = self.id + self.id_protect.release() + + self.actions[id] = {'clean': False} + + logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower()) + self._create_empty_database(db_name) + _initialize_db(self, id, db_name, demo, lang, user_password) + return True + def exp_get_progress(self, id): if self.actions[id]['thread'].isAlive(): # return openerp.modules.init_progress[db_name] @@ -640,13 +655,55 @@ class report_spool(netsvc.ExportService): def dispatch(self, method, auth, params): (db, uid, passwd ) = params[0:3] params = params[3:] - if method not in ['report','report_get']: + if method not in ['report', 'report_get', 'render_report']: raise KeyError("Method not supported %s" % method) security.check(db,uid,passwd) fn = getattr(self, 'exp_' + method) res = fn(db, uid, *params) return res + def exp_render_report(self, db, uid, object, ids, datas=None, context=None): + if not datas: + datas={} + if not context: + context={} + + self.id_protect.acquire() + self.id += 1 + id = self.id + self.id_protect.release() + + self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} + + cr = pooler.get_db(db).cursor() + import traceback + import sys + try: + obj = netsvc.LocalService('report.'+object) + (result, format) = obj.create(cr, uid, ids, datas, context) + if not result: + tb = sys.exc_info() + self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb) + self._reports[id]['result'] = result + self._reports[id]['format'] = format + self._reports[id]['state'] = True + except Exception, exception: + + tb = sys.exc_info() + tb_s = "".join(traceback.format_exception(*tb)) + logger = netsvc.Logger() + logger.notifyChannel('web-services', netsvc.LOG_ERROR, + 'Exception: %s\n%s' % (str(exception), tb_s)) + if hasattr(exception, 'name') and hasattr(exception, 'value'): + self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value)) + else: + self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb) + self._reports[id]['state'] = True + cr.commit() + cr.close() + + return self._check_report(id) + def exp_report(self, db, uid, object, ids, datas=None, context=None): if not datas: datas={} diff --git a/openerp/wsgi.py b/openerp/wsgi.py index f1ddb04cf31..93bf7182785 100644 --- a/openerp/wsgi.py +++ b/openerp/wsgi.py @@ -37,45 +37,89 @@ import time import openerp import openerp.tools.config as config +def xmlrpc_return(start_response, service, method, params): + """ Helper to call a service's method with some params, using a + wsgi-supplied ``start_response`` callback.""" + result = openerp.netsvc.ExportService.getService(service).dispatch(method, None, params) + response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None) + start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))]) + return [response] + def wsgi_xmlrpc(environ, start_response): - if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'): + """ The main OpenERP WSGI handler.""" + if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/openerp/xmlrpc'): length = int(environ['CONTENT_LENGTH']) data = environ['wsgi.input'].read(length) - path = environ['PATH_INFO'][len('/xmlrpc/'):] # expected to be one of db, object, ... TODO should we expect a possible trailing '/' ? # TODO see SimpleXMLRPCDispatcher._marshaled_dispatch() for some necessary handling. # TODO see OpenERPDispatcher for some othe handling (in particular, auth things). params, method = xmlrpclib.loads(data) - result = openerp.netsvc.ExportService.getService(path).dispatch(method, None, params) - response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None) - start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))]) - return [response] + path = environ['PATH_INFO'][len('/openerp/xmlrpc'):] + if path.startswith('/'): path = path[1:] + if path.endswith('/'): p = path[:-1] + path = path.split('/') + + # All routes are hard-coded. Need a way to register addons-supplied handlers. + + # No need for a db segment. + if len(path) == 1: + service = path[0] + + if service == 'common': + if method in ('create_database', 'list', 'server_version'): + return xmlrpc_return(start_response, 'db', method, params) + else: + return xmlrpc_return(start_response, 'common', method, params) + # A db segment must be given. + elif len(path) == 2: + service, db_name = path + params = (db_name,) + params + + if service == 'model': + return xmlrpc_return(start_response, 'object', method, params) + elif service == 'report': + return xmlrpc_return(start_response, 'report', method, params) + +def legacy_wsgi_xmlrpc(environ, start_response): + if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'): + length = int(environ['CONTENT_LENGTH']) + data = environ['wsgi.input'].read(length) + path = environ['PATH_INFO'][len('/xmlrpc/'):] # expected to be one of db, object, ... + + # TODO see SimpleXMLRPCDispatcher._marshaled_dispatch() for some necessary handling. + # TODO see OpenERPDispatcher for some othe handling (in particular, auth things). + params, method = xmlrpclib.loads(data) + return xmlrpc_return(start_response, path, method, params) def wsgi_jsonrpc(environ, start_response): pass def application(environ, start_response): + """ WSGI entry point.""" # Try all handlers until one returns some result (i.e. not None). - wsgi_handlers = [wsgi_xmlrpc, wsgi_jsonrpc] + wsgi_handlers = [wsgi_xmlrpc, wsgi_jsonrpc, legacy_wsgi_xmlrpc] for handler in wsgi_handlers: result = handler(environ, start_response) if result is None: continue return result - # We never returned from the loop. + # We never returned from the loop. Needs something else than 200 OK. response = 'No handler found.\n' start_response('200 OK', [('Content-Type', 'text/plain'), ('Content-Length', str(len(response)))]) return [response] -# Serve XMLRPC via wsgiref's simple_server. -# Blocking, should probably be called in its own process. def serve(): + """ Serve XMLRPC requests via wsgiref's simple_server. + + Blocking, should probably be called in its own process. + """ httpd = make_server('localhost', config['xmlrpc_port'], application) httpd.serve_forever() +# Master process id, can be used for signaling. arbiter_pid = None # Application setup before we can spawn any worker process. @@ -84,8 +128,8 @@ def on_starting(server): global arbiter_pid arbiter_pid = os.getpid() # TODO check if this is true even after replacing the executable config = openerp.tools.config - config['addons_path'] = '/home/openerp/repos/addons/trunk/' # need a config file - openerp.tools.cache = kill_workers_cache + config['addons_path'] = '/home/openerp/repos/addons/trunk-xmlrpc-no-osv-memory' # need a config file + #openerp.tools.cache = kill_workers_cache openerp.netsvc.init_logger() openerp.osv.osv.start_object_proxy() openerp.service.web_services.start_web_services() @@ -112,7 +156,7 @@ def kill_workers(): return raise -class kill_workers_cache(openerp.tools.real_cache): +class kill_workers_cache(openerp.tools.ormcache): def clear(self, dbname, *args, **kwargs): kill_workers()