#!/usr/bin/python # -*- encoding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved # The refactoring about the OpenSSL support come from Tryton # Copyright (C) 2007-2009 Cédric Krier. # Copyright (C) 2007-2009 Bertrand Chenal. # Copyright (C) 2008 B2CK SPRL. # # 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. # # 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. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############################################################################## import SimpleXMLRPCServer import SocketServer import logging import logging.handlers import os import signal import socket import sys import threading import time import xmlrpclib import release SERVICES = {} GROUPS = {} class Service(object): def __init__(self, name, audience=''): SERVICES[name] = self self.__name = name self._methods = {} def joinGroup(self, name): GROUPS.setdefault(name, {})[self.__name] = self def exportMethod(self, method): if callable(method): self._methods[method.__name__] = method def abortResponse(self, error, description, origin, details): if not tools.config['debug_mode']: raise Exception("%s -- %s\n\n%s"%(origin, description, details)) else: raise class LocalService(Service): def __init__(self, name): self.__name = name try: self._service = SERVICES[name] for method_name, method_definition in self._service._methods.items(): setattr(self, method_name, method_definition) except KeyError, keyError: Logger().notifyChannel('module', LOG_ERROR, 'This service does not exists: %s' % (str(keyError),) ) raise def __call__(self, method, *params): return getattr(self, method)(*params) def service_exist(name): return SERVICES.get(name, False) LOG_NOTSET = 'notset' LOG_DEBUG_RPC = 'debug_rpc' LOG_DEBUG = 'debug' LOG_INFO = 'info' LOG_WARNING = 'warn' LOG_ERROR = 'error' LOG_CRITICAL = 'critical' # add new log level below DEBUG logging.DEBUG_RPC = logging.DEBUG - 1 class UnicodeFormatter(logging.Formatter): def __init__(self, enc, fmt=None, datefmt=None): self._enc = enc logging.Formatter.__init__(self, fmt, datefmt) self._fmt = tools.ustr(self._fmt) if self.datefmt is not None: self.datefmt = tools.ustr(self.datefmt) def formatTime(self, *args, **kwargs): return tools.ustr(logging.Formatter.formatTime(self, *args, **kwargs), self._enc) def formatException(self, *args, **kwargs): return tools.ustr(logging.Formatter.formatException(self, *args, **kwargs), self._enc) def format(self, *args, **kwargs): return tools.ustr(logging.Formatter.format(self, *args, **kwargs), self._enc) def init_logger(): import os from tools.translate import resetlocale l = resetlocale() or '' enc = '.' in l and l.split('.')[1] or 'utf-8' logger = logging.getLogger() # create a format for log messages and dates formatter = UnicodeFormatter(enc, '[%(asctime)s] %(levelname)s:%(name)s:%(message)s', '%a %b %d %Y %H:%M:%S') logging_to_stdout = False if tools.config['syslog']: # SysLog Handler if os.name == 'nt': handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version)) else: handler = logging.handlers.SysLogHandler('/dev/log') formatter = UnicodeFormatter(enc, "%s %s" % (release.description, release.version) + ':%(levelname)s:%(name)s:%(message)s') elif tools.config['logfile']: # LogFile Handler logf = tools.config['logfile'] try: dirname = os.path.dirname(logf) if dirname and not os.path.isdir(dirname): os.makedirs(dirname) handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) except Exception, ex: sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n") handler = logging.StreamHandler(sys.stdout) logging_to_stdout = True else: # Normal Handler on standard output handler = logging.StreamHandler(sys.stdout) logging_to_stdout = True # tell the handler to use this format handler.setFormatter(formatter) # add the handler to the root logger logger.addHandler(handler) logger.setLevel(tools.config['log_level'] or '0') if logging_to_stdout and os.name != 'nt': # change color of level names # uses of ANSI color codes # see http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html # maybe use http://code.activestate.com/recipes/574451/ colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', None, 'default'] foreground = lambda f: 30 + colors.index(f) background = lambda f: 40 + colors.index(f) mapping = { 'DEBUG_RPC': ('blue', 'white'), 'DEBUG': ('blue', 'default'), 'INFO': ('green', 'default'), 'WARNING': ('yellow', 'default'), 'ERROR': ('red', 'default'), 'CRITICAL': ('white', 'red'), } for level, (fg, bg) in mapping.items(): msg = "\x1b[%dm\x1b[%dm%s\x1b[0m" % (foreground(fg), background(bg), level) logging.addLevelName(getattr(logging, level), msg) class Logger(object): def notifyChannel(self, name, level, msg): log = logging.getLogger(name) if level == LOG_DEBUG_RPC and not hasattr(log, level): fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG_RPC, msg, *args, **kwargs) setattr(log, LOG_DEBUG_RPC, fct) level_method = getattr(log, level) if isinstance(msg, Exception): msg = tools.exception_to_unicode(msg) result = tools.ustr(msg).strip().split('\n') if len(result)>1: for idx, s in enumerate(result): level_method('[%02d]: %s' % (idx+1, s,)) elif result: level_method(result[0]) def shutdown(self): logging.shutdown() import tools init_logger() class Agent(object): _timers = [] _logger = Logger() def setAlarm(self, fn, dt, args=None, kwargs=None): if not args: args = [] if not kwargs: kwargs = {} wait = dt - time.time() if wait > 0: self._logger.notifyChannel('timers', LOG_DEBUG, "Job scheduled in %s seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name)) timer = threading.Timer(wait, fn, args, kwargs) timer.start() self._timers.append(timer) for timer in self._timers[:]: if not timer.isAlive(): self._timers.remove(timer) def quit(cls): for timer in cls._timers: timer.cancel() quit = classmethod(quit) import traceback class xmlrpc(object): class RpcGateway(object): def __init__(self, name): self.name = name class OpenERPDispatcherException(Exception): def __init__(self, exception, traceback): self.exception = exception self.traceback = traceback class OpenERPDispatcher: def log(self, title, msg): from pprint import pformat Logger().notifyChannel('%s' % title, LOG_DEBUG_RPC, pformat(msg)) def dispatch(self, service_name, method, params): try: self.log('service', service_name) self.log('method', method) self.log('params', params) result = LocalService(service_name)(method, *params) self.log('result', result) return result except Exception, e: self.log('exception', tools.exception_to_unicode(e)) if hasattr(e, 'traceback'): tb = e.traceback else: tb = sys.exc_info() tb_s = "".join(traceback.format_exception(*tb)) if tools.config['debug_mode']: import pdb pdb.post_mortem(tb[2]) raise OpenERPDispatcherException(e, tb_s) class GenericXMLRPCRequestHandler(OpenERPDispatcher): def _dispatch(self, method, params): try: service_name = self.path.split("/")[-1] return self.dispatch(service_name, method, params) except OpenERPDispatcherException, e: raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback) class SSLSocket(object): def __init__(self, socket): if not hasattr(socket, 'sock_shutdown'): from OpenSSL import SSL ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.use_privatekey_file(tools.config['secure_pkey_file']) ctx.use_certificate_file(tools.config['secure_cert_file']) self.socket = SSL.Connection(ctx, socket) else: self.socket = socket def shutdown(self, how): return self.socket.sock_shutdown(how) def __getattr__(self, name): return getattr(self.socket, name) class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler, SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): rpc_paths = map(lambda s: '/xmlrpc/%s' % s, SERVICES.keys()) class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): def setup(self): self.connection = SSLSocket(self.request) self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): def server_bind(self): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self) class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer): def __init__(self, server_address, HandlerClass, logRequests=1): SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass, logRequests) self.socket = SSLSocket(socket.socket(self.address_family, self.socket_type)) self.server_bind() self.server_activate() class HttpDaemon(threading.Thread): def __init__(self, interface, port, secure=False): threading.Thread.__init__(self) self.__port = port self.__interface = interface self.secure = bool(secure) handler_class = (SimpleXMLRPCRequestHandler, SecureXMLRPCRequestHandler)[self.secure] server_class = (SimpleThreadedXMLRPCServer, SecureThreadedXMLRPCServer)[self.secure] if self.secure: from OpenSSL.SSL import Error as SSLError else: class SSLError(Exception): pass try: self.server = server_class((interface, port), handler_class, 0) except SSLError, e: Logger().notifyChannel('xml-rpc-ssl', LOG_CRITICAL, "Can not load the certificate and/or the private key files") sys.exit(1) except Exception, e: Logger().notifyChannel('xml-rpc', LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,)) sys.exit(1) def attach(self, path, gw): pass def stop(self): self.running = False if os.name != 'nt': self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 ) self.server.socket.close() def run(self): self.server.register_introspection_functions() self.running = True while self.running: self.server.handle_request() return True # If the server need to be run recursively # #signal.signal(signal.SIGALRM, self.my_handler) #signal.alarm(6) #while True: # self.server.handle_request() #signal.alarm(0) # Disable the alarm import tiny_socket class TinySocketClientThread(threading.Thread, OpenERPDispatcher): def __init__(self, sock, threads): threading.Thread.__init__(self) self.sock = sock self.threads = threads def run(self): import select self.running = True try: ts = tiny_socket.mysocket(self.sock) except: self.sock.close() self.threads.remove(self) return False while self.running: try: msg = ts.myreceive() except: self.sock.close() self.threads.remove(self) return False try: result = self.dispatch(msg[0], msg[1], msg[2:]) ts.mysend(result) except OpenERPDispatcherException, e: new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling ts.mysend(new_e, exception=True, traceback=e.traceback) self.sock.close() self.threads.remove(self) return True def stop(self): self.running = False class TinySocketServerThread(threading.Thread): def __init__(self, interface, port, secure=False): threading.Thread.__init__(self) self.__port = port self.__interface = interface self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.__interface, self.__port)) self.socket.listen(5) self.threads = [] def run(self): import select try: self.running = True while self.running: (clientsocket, address) = self.socket.accept() ct = TinySocketClientThread(clientsocket, self.threads) self.threads.append(ct) ct.start() self.socket.close() except Exception, e: self.socket.close() return False def stop(self): self.running = False for t in self.threads: t.stop() try: if hasattr(socket, 'SHUT_RDWR'): self.socket.shutdown(socket.SHUT_RDWR) else: self.socket.shutdown(2) self.socket.close() except: return False # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: