diff --git a/lib/python b/lib/python new file mode 120000 index 0000000000000000000000000000000000000000..e4cf62c850039f10e219270c1c50543b81b5a138 --- /dev/null +++ b/lib/python @@ -0,0 +1 @@ +../src/python \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..08cd0778ef8c24fc3bc116557b17aab37182e112 --- /dev/null +++ b/setup.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +# DM setup script for Bourne-type shells +# This file is typically sourced in user's .bashrc file + +myDir=`dirname $BASH_SOURCE` +currentDir=`pwd` && cd $myDir +if [ ! -z "$DM_ROOT_DIR" -a "$DM_ROOT_DIR" != `pwd` ]; then + echo "WARNING: Resetting DM_ROOT_DIR environment variable (old value: $DM_ROOT_DIR)" +fi +export DM_ROOT_DIR=`pwd` + +if [ -z $DM_DATA_DIR ]; then + export DM_DATA_DIR=$DM_ROOT_DIR/../data + if [ -d $DM_DATA_DIR ]; then + cd $DM_DATA_DIR + export DM_DATA_DIR=`pwd` + fi +fi +if [ ! -d $DM_DATA_DIR ]; then + #echo "WARNING: $DM_DATA_DIR directory does not exist. Developers should point DM_DATA_DIR to the desired area." + unset DM_DATA_DIR +fi + +if [ -z $DM_VAR_DIR ]; then + export DM_VAR_DIR=$DM_ROOT_DIR/../var + if [ -d $DM_VAR_DIR ]; then + cd $DM_VAR_DIR + export DM_VAR_DIR=`pwd` + fi +fi + +# Check support setup +if [ -z $DM_SUPPORT_DIR ]; then + export DM_SUPPORT_DIR=$DM_ROOT_DIR/../support + if [ -d $DM_SUPPORT_DIR ]; then + cd $DM_SUPPORT_DIR + export DM_SUPPORT_DIR=`pwd` + fi +fi +if [ ! -d $DM_SUPPORT_DIR ]; then + echo "ERROR: $DM_SUPPORT_DIR directory does not exist. Developers should point DM_SUPPORT_DIR to the desired area." + return 1 +fi +export DM_HOST_ARCH=`uname | tr [A-Z] [a-z]`-`uname -m` + +# Add to path only if directory exists. +prependPathIfDirExists() { + _dir=$1 + if [ -d ${_dir} ]; then + PATH=${_dir}:$PATH + fi +} + +# Setup epics variables +PATH=$DM_ROOT_DIR/bin:$PATH +PATH=.:$PATH +export PATH + +if [ -z $LD_LIBRARY_PATH ]; then + LD_LIBRARY_PATH=. +else + LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH +fi +export LD_LIBRARY_PATH + +# Setup python path. First check for wx, then try to find it locally, and +# then re-check for it. +# Check if we have local python +if [ -z $DM_PYTHON_DIR ]; then + pythonDir=$DM_SUPPORT_DIR/python/$DM_HOST_ARCH +else + pythonDir=$DM_PYTHON_DIR +fi +if [ -d $pythonDir ]; then + cd $pythonDir + pythonDir=`pwd` + export PATH=`pwd`/bin:$PATH + export LD_LIBRARY_PATH=`pwd`/lib:$LD_LIBRARY_PATH + export DM_PYTHON_DIR=$pythonDir +fi + +if [ -z $PYTHONPATH ]; then + PYTHONPATH=$DM_ROOT_DIR/lib/python +else + PYTHONPATH=$DM_ROOT_DIR/lib/python:$PYTHONPATH +fi +export PYTHONPATH + +# Get back to where we were before invoking the setup script +cd $currentDir + +# Print out user environment +echo +echo "Your DM environment is defined as follows:" +echo +env | grep DM_ | grep -v GDM_ +echo +echo + + diff --git a/src/python/dm/__init__.py b/src/python/dm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/__init__.py b/src/python/dm/common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/constants/__init__.py b/src/python/dm/common/constants/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/constants/dmHttpHeaders.py b/src/python/dm/common/constants/dmHttpHeaders.py new file mode 100644 index 0000000000000000000000000000000000000000..fc8af0e6610ebe37e2811e563e04091e1338a383 --- /dev/null +++ b/src/python/dm/common/constants/dmHttpHeaders.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +####################################################################### + +DM_SESSION_ROLE_HTTP_HEADER = 'Dm-Session-Role' +DM_STATUS_CODE_HTTP_HEADER = 'Dm-Status-Code' +DM_STATUS_MESSAGE_HTTP_HEADER = 'Dm-Status-Message' +DM_EXCEPTION_TYPE_HTTP_HEADER = 'Dm-Exception-Type' + diff --git a/src/python/dm/common/constants/dmRole.py b/src/python/dm/common/constants/dmRole.py new file mode 100644 index 0000000000000000000000000000000000000000..801a9391de13d8542c93f1b839573c24028e8efa --- /dev/null +++ b/src/python/dm/common/constants/dmRole.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +####################################################################### + +DM_ADMIN_ROLE = 'admin' +DM_USER_ROLE = 'user' + + diff --git a/src/python/dm/common/constants/dmServiceConstants.py b/src/python/dm/common/constants/dmServiceConstants.py new file mode 100644 index 0000000000000000000000000000000000000000..1a1419358cf3136cd95f1d98c844e427a38f6d7f --- /dev/null +++ b/src/python/dm/common/constants/dmServiceConstants.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +####################################################################### + +DM_SERVICE_PROTOCOL_HTTP = 'http' +DM_SERVICE_PROTOCOL_HTTPS = 'https' diff --git a/src/python/dm/common/constants/dmStatus.py b/src/python/dm/common/constants/dmStatus.py new file mode 100644 index 0000000000000000000000000000000000000000..0169057f420f757838a2e859e2d3a85c657ff4d2 --- /dev/null +++ b/src/python/dm/common/constants/dmStatus.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +####################################################################### + +DM_OK = 0 +DM_ERROR = 1 +DM_CONFIGURATION_ERROR = 2 +DM_INTERNAL_ERROR = 3 +DM_INVALID_ARGUMENT_ERROR = 4 +DM_INVALID_REQUEST_ERROR = 5 +DM_COMMAND_FAILED_ERROR = 6 + + diff --git a/src/python/dm/common/exceptions/__init__.py b/src/python/dm/common/exceptions/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/exceptions/commandFailed.py b/src/python/dm/common/exceptions/commandFailed.py new file mode 100755 index 0000000000000000000000000000000000000000..b2834472a119e145434bec4ad609c4ef9d6c00bc --- /dev/null +++ b/src/python/dm/common/exceptions/commandFailed.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# +# Command failed exception class. +# + +####################################################################### + +from dm.common.constants import dmStatus +from dm.common.exceptions.dmException import DmException + +####################################################################### + +class CommandFailed(DmException): + def __init__ (self, error='', **kwargs): + DmException.__init__(self, error, dmStatus.DM_COMMAND_FAILED_ERROR, **kwargs) diff --git a/src/python/dm/common/exceptions/configurationError.py b/src/python/dm/common/exceptions/configurationError.py new file mode 100755 index 0000000000000000000000000000000000000000..cd6ae7a9113a1902ca97afcce96cd256c95ed3d0 --- /dev/null +++ b/src/python/dm/common/exceptions/configurationError.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# +# Configuration error class. +# + +####################################################################### + +from dm.common.constants import dmStatus +from dm.common.exceptions.dmException import DmException + +####################################################################### + +class ConfigurationError(DmException): + def __init__ (self, error='', **kwargs): + DmException.__init__(self, error, dmStatus.DM_CONFIGURATION_ERROR, **kwargs) diff --git a/src/python/dm/common/exceptions/dmException.py b/src/python/dm/common/exceptions/dmException.py new file mode 100755 index 0000000000000000000000000000000000000000..7cd9c2c47f54965871d0658dbadb5871cf0fd4cc --- /dev/null +++ b/src/python/dm/common/exceptions/dmException.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +# +# Base DM exception class. +# + +####################################################################### + +import exceptions +import json + +from dm.common.constants import dmStatus + +####################################################################### + +class DmException(exceptions.Exception): + """ + Base DM exception class. + + Usage: + DmException(errorMessage, errorCode) + DmException(args=errorMessage) + DmException(exception=exceptionObject) + """ + def __init__(self, error='', code=dmStatus.DM_ERROR, **kwargs): + args = error + if args == '': + args = kwargs.get('args', '') + ex = kwargs.get('exception', None) + if ex != None: + if isinstance(ex, exceptions.Exception): + exArgs = '%s' % (ex) + if args == '': + args = exArgs + else: + args = "%s (%s)" % (args, exArgs) + exceptions.Exception.__init__(self, args) + self._code = code + + def getArgs(self): + return self.args + + def getErrorCode(self): + return self._code + + def getErrorMessage(self): + return '%s' % (self.args) + + def getClassName(self): + return '%s' % (self.__class__.__name__) + + def getExceptionType(self): + return '%s' % (self.__class__.__name__).split('.')[-1] + + def getJsonRep(self): + return json.dumps({ + 'errorMessage' : self.getErrorMessage(), + 'errorCode' : self.getErrorCode(), + 'exceptionType' : self.getExceptionType(), + }) + diff --git a/src/python/dm/common/exceptions/dmExceptionMap.py b/src/python/dm/common/exceptions/dmExceptionMap.py new file mode 100755 index 0000000000000000000000000000000000000000..7729d3e79824978c65ab8cd1a3ee1901bd9d0f3a --- /dev/null +++ b/src/python/dm/common/exceptions/dmExceptionMap.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +# +# DM exception map +# + +####################################################################### + +from dm.common.constants import dmStatus + +exceptionMap = { + dmStatus.DM_ERROR : 'dmException.DmException', + dmStatus.DM_CONFIGURATION_ERROR : 'configurationError.ConfigurationError', + dmStatus.DM_INTERNAL_ERROR : 'internalError.InternalError', + dmStatus.DM_INVALID_ARGUMENT_ERROR : 'invalidArgument.InvalidArgument', + dmStatus.DM_INVALID_REQUEST_ERROR : 'invalidRequest.InvalidRequest', + dmStatus.DM_COMMAND_FAILED_ERROR : 'commandFailed.CommandFailed', +} + +####################################################################### +# Testing + +if __name__ == '__main__': + for item in exceptionMap.items(): + print item + diff --git a/src/python/dm/common/exceptions/internalError.py b/src/python/dm/common/exceptions/internalError.py new file mode 100755 index 0000000000000000000000000000000000000000..a393f0c7f209a5c0bc63ce641ce75f9ba2c5ce01 --- /dev/null +++ b/src/python/dm/common/exceptions/internalError.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# +# Internal error class. +# + +####################################################################### + +from dm.common.constants import dmStatus +from dm.common.exceptions.dmException import DmException + +####################################################################### + +class InternalError(DmException): + def __init__ (self, error='', **kwargs): + DmException.__init__(self, error, dmStatus.DM_INTERNAL_ERROR, **kwargs) diff --git a/src/python/dm/common/exceptions/invalidArgument.py b/src/python/dm/common/exceptions/invalidArgument.py new file mode 100755 index 0000000000000000000000000000000000000000..34b352a2cdd5fb1efe9c833bf42ef2912ee3fbe6 --- /dev/null +++ b/src/python/dm/common/exceptions/invalidArgument.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# +# Invalid argument error class. +# + +####################################################################### + +from dm.common.constants import dmStatus +from dm.common.exceptions.dmException import DmException + +####################################################################### + +class InvalidArgument(DmException): + def __init__ (self, error='', **kwargs): + DmException.__init__(self, error, dmStatus.DM_INVALID_ARGUMENT_ERROR, **kwargs) diff --git a/src/python/dm/common/exceptions/invalidRequest.py b/src/python/dm/common/exceptions/invalidRequest.py new file mode 100755 index 0000000000000000000000000000000000000000..e285eff332f341343c7407a3927d30b02bad93ea --- /dev/null +++ b/src/python/dm/common/exceptions/invalidRequest.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# +# Invalid request error class. +# + +####################################################################### + +from dm.common.constants import dmStatus +from dm.common.exceptions.dmException import DmException + +####################################################################### + +class InvalidRequest(DmException): + def __init__ (self, error='', **kwargs): + DmException.__init__(self, error, dmStatus.DM_INVALID_REQUEST_ERROR, **kwargs) diff --git a/src/python/dm/common/objects/__init__.py b/src/python/dm/common/objects/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/objects/dmObject.py b/src/python/dm/common/objects/dmObject.py new file mode 100755 index 0000000000000000000000000000000000000000..c1144b501db0818e7bc43487462b4a4b69ad0054 --- /dev/null +++ b/src/python/dm/common/objects/dmObject.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# +# DM Object class. +# + +####################################################################### + +import UserDict +import UserList +import types +import json + +from dm.common.exceptions.invalidArgument import InvalidArgument +from dm.common.utility import loggingManager + +class DmObject(UserDict.UserDict): + """ Base dm object class. """ + def __init__(self, dict={}): + if isinstance(dict, types.DictType): + UserDict.UserDict.__init__(self, dict) + elif isinstance(dict, UserDict.UserDict): + UserDict.UserDict.__init__(self, dict.data) + else: + raise InvalidArgument('DmObject must be initialized using dictionary.') + self._jsonPreprocessKeyList = [] + self._logger = None + + def getLogger(self): + if not self._logger: + self._logger = loggingManager.getLogger(self.__class__.__name__) + return self._logger + + @classmethod + def getFromDict(cls, dict): + inst = cls() + for key in dict.keys(): + inst[key] = dict[key] + return inst + + def getDictRep(self): + # Dict representation is dict + dictRep = {} + for (key,obj) in self.data.items(): + if isinstance(obj, DmObject): + dictRep[key] = obj.getDictRep() + else: + if obj is not None: + dictRep[key] = obj + return dictRep + + def getDictJsonPreprocessedRep(self): + dictRep = self.getDictRep() + # Convert designated keys into string values. + for key in self._jsonPreprocessKeyList: + value = dictRep.get(key) + if value is not None: + dictRep[key] = '%s' % value + return dictRep + + def getJsonRep(self): + dictRep = self.getDictJsonPreprocessedRep() + return json.dumps(dictRep) + + @classmethod + def fromJsonString(cls, jsonString): + return cls.getFromDict(json.loads(jsonString)) + +####################################################################### +# Testing. + +if __name__ == '__main__': + x = {'name' : 'XYZ', 'one':1, 'two':2 } + o = DmObject(x) + print 'DM Object: ', o + print 'Type of DM object: ', type(o) + print 'JSON Rep: ', o.getJsonRep() + print 'Type of JSON rep: ', type(o.getJsonRep()) + j = '{"name" : "XYZ", "one":1, "two":2 }' + print 'String: ', j + x2 = DmObject.fromJsonString(j) + print 'DM Object 2: ', x2 + print 'Type of DM object 2: ', type(x2) + + diff --git a/src/python/dm/common/objects/dmObjectManager.py b/src/python/dm/common/objects/dmObjectManager.py new file mode 100644 index 0000000000000000000000000000000000000000..0d7864caa6c73b0b35acdd7779a7da176a100de2 --- /dev/null +++ b/src/python/dm/common/objects/dmObjectManager.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +# +# Base object manager class. +# + +####################################################################### + +import threading +from dm.common.utility import loggingManager + +####################################################################### + +class DmObjectManager: + """ Base object manager class. """ + + def __init__(self): + self._logger = loggingManager.getLogger(self.__class__.__name__) + self._lock = threading.RLock() + + def getLogger(self): + return self._logger + + def acquireLock(self): + self._lock.acquire() + + def releaseLock(self): + self._lock.release() + diff --git a/src/python/dm/common/service/__init__.py b/src/python/dm/common/service/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/service/authorizationController.py b/src/python/dm/common/service/authorizationController.py new file mode 100644 index 0000000000000000000000000000000000000000..0c0c20a49904d8269067570aa2dfb8ecb021a381 --- /dev/null +++ b/src/python/dm/common/service/authorizationController.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python + +# +# Authorization controller class +# + +####################################################################### + +import cherrypy +import urllib +from cherrypy.lib import httpauth + +from dm.common.constants import dmStatus +from dm.common.constants import dmRole + +from dm.common.exceptions.dmException import DmException +from dm.common.exceptions.dmHttpError import DmHttpError +from dm.common.exceptions.authorizationError import AuthorizationError +from dm.common.utility import loggingManager +from dm.common.service.dmController import DmController +from dm.common.impl import authManager + +import dmSession + +####################################################################### + +cherrypy.lib.sessions.DmSession = dmSession.DmSession + +SESSION_USERNAME_KEY = '_cp_username' +SESSION_ROLE_KEY = 'role' + +def checkCredentials(username, password): + """ Verifies credentials for username and password.""" + logger = loggingManager.getLogger('checkCredentials') + logger.debug('Checking credential for User: %s, Password: %s' % (username, password)) + logger.debug('Session id: %s' % cherrypy.serving.session.id) + principal = authManager.getInstance().getAuthPrincipal(username, password) + logger.debug('Principal: %s' % (principal)) + if principal: + cherrypy.session[SESSION_ROLE_KEY] = principal.getRole() + logger.debug('Successful login from user: %s (role: %s)' % (username, principal.getRole())) + else: + logger.debug('Login denied for user: %s' % username) + username = cherrypy.session.get(SESSION_USERNAME_KEY, None) + + if username is not None: + cherrypy.request.login = None + cherrypy.session[dmSession.INVALID_DM_SESSION_KEY] = True + raise AuthorizationError('Incorrect username or password.') + return principal + +def parseBasicAuthorizationHeaders(): + try: + logger = loggingManager.getLogger('parseBasicAuthorizationHeader') + username = None + password = None + authorization = cherrypy.request.headers['authorization'] + authorizationHeader = httpauth.parseAuthorization(authorization) + logger.debug('Authorization header: %s' % authorizationHeader) + if authorizationHeader['auth_scheme'] == 'basic': + username = authorizationHeader['username'] + password = authorizationHeader['password'] + logger.debug('Got username/password from headers: %s/%s' % (username, password)) + if username and password: + return (username, password) + else: + raise AuthorizationError('Username and/or password not supplied.') + except Exception, ex: + errorMsg = 'Could not extract username/password from authorization header: %s' % ex + raise AuthorizationError(errorMsg) + +def checkAuth(*args, **kwargs): + """ + A tool that looks in config for 'auth.require'. If found and it + is not None, a login is required and the entry is evaluated as a list of + conditions that the user must fulfill. + """ + logger = loggingManager.getLogger('checkAuth') + conditions = cherrypy.request.config.get('auth.require', None) + logger.debug('Headers: %s' % (cherrypy.request.headers)) + logger.debug('Request params: %s' % (cherrypy.request.params)) + logger.debug('Request query string: %s' % (cherrypy.request.query_string)) + + method = urllib.quote(cherrypy.request.request_line.split()[0]) + params = urllib.quote(cherrypy.request.request_line.split()[1]) + logger.debug('Session: %s' % ((cherrypy.session.__dict__))) + if conditions is not None: + sessionId = cherrypy.serving.session.id + sessionCache = cherrypy.session.cache + logger.debug('Session: %s' % ((cherrypy.session.__dict__))) + logger.debug('Session cache length: %s' % (len(sessionCache))) + logger.debug('Session cache: %s' % (sessionCache)) + # Check session. + if not sessionCache.has_key(sessionId): + errorMsg = 'Invalid or expired session id: %s.' % sessionId + logger.debug(errorMsg) + raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', AuthorizationError(errorMsg)) + + username = cherrypy.session.get(SESSION_USERNAME_KEY) + logger.debug('Session id %s is valid (username: %s)' % (sessionId, username)) + if username: + cherrypy.request.login = username + for condition in conditions: + # A condition is just a callable that returns true or false + if not condition(): + logger.debug('Authorization check %s failed for username %s' % (condition.func_name, username)) + errorMsg = 'Authorization check %s failed for user %s.' % (condition.func_name, username) + raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', AuthorizationError(errorMsg)) + else: + logger.debug('Username is not supplied') + raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', ex) + +# Add before_handler for authorization +cherrypy.tools.auth = cherrypy.Tool('before_handler', checkAuth) +#cherrypy.tools.auth = cherrypy.Tool('on_start_resource', checkAuth) + +def require(*conditions): + """ + A decorator that appends conditions to the auth.require config variable. + """ + def decorate(f): + if not hasattr(f, '_cp_config'): + f._cp_config = dict() + if 'auth.require' not in f._cp_config: + f._cp_config['auth.require'] = [] + f._cp_config['auth.require'].extend(conditions) + return f + return decorate + +# Conditions are callables that return True if the user +# fulfills the conditions they define, False otherwise. +# They can access the current username as cherrypy.request.login +def isAdminRole(): + return (cherrypy.session.get(SESSION_ROLE_KEY, None) == dmRole.DM_ADMIN_ROLE) + +def isUserOrAdminRole(): + role = cherrypy.session.get(SESSION_ROLE_KEY, None) + if role == dmRole.DM_ADMIN_ROLE or role == dmRole.DM_USER_ROLE: + return True + return False + +def isUser(username): + result = (cherrypy.session.get(SESSION_USERNAME_KEY, None) == username) + return result + +def getSessionUser(): + return cherrypy.session.get(SESSION_USERNAME_KEY, None) + +def memberOf(groupname): + return cherrypy.request.login == 'dm' and groupname == 'admin' + +def nameIs(reqd_username): + return lambda: reqd_username == cherrypy.request.login + +def anyOf(*conditions): + """ Returns True if any of the conditions match. """ + def check(): + for c in conditions: + if c(): + return True + return False + return check + +def allOf(*conditions): + """ Returns True if all of the conditions match. """ + def check(): + for c in conditions: + if not c(): + return False + return True + return check + +class AuthController(DmController): + """ Controller to provide login and logout actions. """ + _cp_config = { + 'tools.sessions.on' : True, + 'tools.sessions.storage_type' : 'dm', + 'tools.auth.on' : True + } + + def __init__(self): + DmController.__init__(self) + + def onLogin(self, username): + """ Called on successful login. """ + return + + def onLogout(self, username): + """ Called on logout. """ + return + + @cherrypy.expose + def login(self, username=None, password=None, fromPage='/'): + logger = loggingManager.getLogger('login') + try: + if username is None or password is None: + (username, password) = parseBasicAuthorizationHeaders() + principal = checkCredentials(username, password) + except DmHttpError, ex: + raise + except DmException, ex: + logger.debug('Authorization failed (username %s): %s' % (username, ex)) + self.addDmExceptionHeaders(ex) + raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', ex) + + # Authorization worked. + cherrypy.session[SESSION_USERNAME_KEY] = cherrypy.request.login = username + self.onLogin(username) + self.addDmSessionRoleHeaders(principal.getRole()) + self.addDmResponseHeaders() + + @cherrypy.expose + def logout(self, fromPage='/'): + sess = cherrypy.session + username = sess.get(SESSION_USERNAME_KEY, None) + if username: + del sess[SESSION_USERNAME_KEY] + cherrypy.request.login = None + self.onLogout(username) diff --git a/src/python/dm/common/service/dmController.py b/src/python/dm/common/service/dmController.py new file mode 100644 index 0000000000000000000000000000000000000000..86501bc93714b70f4d84489cd8f5c8fa822e709e --- /dev/null +++ b/src/python/dm/common/service/dmController.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# +# Base DM controller class. +# + +####################################################################### + +import cherrypy +import httplib +from sys import exc_info +from dm.common.utility import loggingManager +from dm.common.constants import dmStatus +from dm.common.constants import dmHttpHeaders +from dm.common.exceptions.dmException import DmException +from dm.common.exceptions import dmExceptionMap +from dm.common.exceptions.internalError import InternalError + +####################################################################### + +class DmController(object): + """ Base controller class. """ + def __init__(self): + self._logger = loggingManager.getLogger(self.__class__.__name__) + + def getLogger(self): + return self._logger + + @classmethod + def addDmResponseHeaders(cls, status=dmStatus.DM_OK, msg='Success', exceptionType=None): + cherrypy.response.headers[dmHttpHeaders.DM_STATUS_CODE_HTTP_HEADER] = status + cherrypy.response.headers[dmHttpHeaders.DM_STATUS_MESSAGE_HTTP_HEADER] = msg + if exceptionType is not None: + cherrypy.response.headers[dmHttpHeaders.DM_EXCEPTION_TYPE_HTTP_HEADER] = exceptionType + + @classmethod + def addDmSessionRoleHeaders(cls, role): + cherrypy.response.headers[dmHttpHeaders.DM_SESSION_ROLE_HTTP_HEADER] = role + + @classmethod + def addDmExceptionHeaders(cls, ex): + cls.handleException(ex) + + @classmethod + def handleCpException(cls): + cherrypy.response.status = httplib.OK + ex = exc_info()[1] + if ex == None: + ex = InternalError('Internal Webservice Error') + cls.handleException(ex) + + @classmethod + def handleException(cls, ex): + exClass = ex.__class__.__name__.split('.')[-1] + status = None + msg = '%s' % ex + msg = msg.split('\n')[0] + for code in dmExceptionMap.exceptionMap.keys(): + exStr = dmExceptionMap.exceptionMap.get(code).split('.')[-1] + if exStr == exClass: + status = code + if not status: + status = dmStatus.DM_ERROR + cls.addDmResponseHeaders(status, msg, exClass) + + def formatJsonResponse(self, response): + cherrypy.response.headers['Content-Type'] = 'application/json' + return '%s' % (response) + diff --git a/src/python/dm/common/service/dmRestWebService.py b/src/python/dm/common/service/dmRestWebService.py new file mode 100755 index 0000000000000000000000000000000000000000..04eaf74ef72a18e22c1a05210192673afa6a64e4 --- /dev/null +++ b/src/python/dm/common/service/dmRestWebService.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python + +# +# Base web service class. +# + +#################################################################### +import sys +import cherrypy +from cherrypy.process import plugins +from cherrypy import server + +from dm.common.constants import dmStatus +from dm.common.utility import configurationManager +from dm.common.utility import loggingManager +from dm.common.utility import dmModuleManager + +#################################################################### + +class DmRestWebService: + + DEFAULT_N_SERVER_REQUEST_THREADS = 10 + DEFAULT_SERVER_SOCKET_TIMEOUT = 30 + + class SignalHandler: + def __init__(self, signal, oldSignalHandler): + self._signal = signal + self._oldSignalHandler = oldSignalHandler + self._logger = loggingManager.getInstance().getLogger(self.__class__.__name__) + + def signalHandler(self): + self._logger.debug('%s signal handler called' % self._signal) + dmModuleManager.getInstance().stopModules() + self._oldSignalHandler() + + def __init__(self, routeMapper): + self._configurationManager = configurationManager.getInstance() + self._routeMapper = routeMapper + self._options = None + self._args = None + self._logger = loggingManager.getInstance().getLogger(self.__class__.__name__) + self._logger.info('Initializing service') + + def prepareOptions(self): + from optparse import OptionParser + p = OptionParser() + p.add_option('-d', '--daemon', action="store_true", + dest='daemonFlag', default=False, + help="Run as a daemon.") + p.add_option('-p', '--pidfile', + dest='pidfile', default=None, + help="Store the process id in the given file.") + p.add_option('-P', '--port', + dest='serverPort', default=self.getDefaultServerPort(), + help="Server port.") + p.add_option('-H', '--host', + dest='serverHost', default=self.getDefaultServerHost(), + help="Server host.") + p.add_option('-C', '--ssl-ca-cert', + dest='sslCaCert', default=None, + help='SSL CA certificate path (used for client SSL certificate verification). Requires --ssl-key and --ssl-cert.') + p.add_option('-c', '--ssl-cert', + dest='sslCert', default=None, + help='SSL certificate path. SSL operation requires both --ssl-key and --ssl-cert. Client SSL certificate verification also requires --ssl-ca-cert.') + p.add_option('-k', '--ssl-key', + dest='sslKey', default=None, + help='SSL key path. SSL operation requires both --ssl-key and --ssl-cert. Client SSL certificate verification also requires --ssl-ca-cert.') + p.add_option('', '--n-server-threads', + dest='nServerThreads', default=DmRestWebService.DEFAULT_N_SERVER_REQUEST_THREADS, + help='Number of service request handler threads (defaut: %s).' % DmRestWebService.DEFAULT_N_SERVER_REQUEST_THREADS) + return p + + def initDmModules(self): + pass + + def getDefaultServerHost(self): + return None + + def getDefaultServerPort(self): + return None + + # Instantiate modified signal handler that stops dm modules first, + # and then does the default action. + def modifySignalHandlers(self, engine): + pluginsSignalHandler = plugins.SignalHandler(engine) + handlers = pluginsSignalHandler.handlers + + # Add handler for interrupt + handlers['SIGINT'] = engine.exit + + # Modify all signal handlers + for signal in handlers.keys(): + self._logger.debug('Modifying signal: %s' % signal) + oldSignalHandler = handlers[signal] + self._logger.debug('Old signal handler: %s' % oldSignalHandler) + signalHandler = DmRestWebService.SignalHandler(signal, oldSignalHandler) + self._logger.debug('Setting signal handler to: %s' % signalHandler.signalHandler) + handlers[signal] = signalHandler.signalHandler + pluginsSignalHandler.subscribe() + + def initServerLog(self): + cherrypyLogLevel = self._configurationManager.getCherrypyLogLevel() + cherrypy.log.error_log.setLevel(cherrypyLogLevel) + cherrypy.log.error_file = self._configurationManager.getCherrypyLogFile() + cherrypy.log.error_log.propagate = False + cherrypy.log.access_log.setLevel(cherrypyLogLevel) + cherrypy.log.access_file = self._configurationManager.getCherrypyAccessFile() + cherrypy.log.access_log.propagate = False + + def updateServerConfig(self): + configDict = { + 'server.socket_host' : self._options.serverHost, + 'server.socket_port' : int(self._options.serverPort), + 'server.thread_pool' : int(self._options.nServerThreads), + 'log.screen' : (self._options.daemonFlag != True), + } + cherrypy.config.update(configDict) + + def prepareServer(self): + self._logger.debug('Preparing service configuration') + try: + optionParser = self.prepareOptions() + (self._options, self._args) = optionParser.parse_args() + dispatch = self._routeMapper.setupRoutes() + self._logger.debug('Using route dispatch: %s' % dispatch) + + config = { + '/' : { + 'request.dispatch' : dispatch, + }, + } + + # No root controller as we provided our own. + cherrypy.tree.mount(root=None, config=config) + self.initServerLog() + self.updateServerConfig() + + self._logger.info('Using host %s' % self._options.serverHost) + self._logger.info('Using port %s' % self._options.serverPort) + self._logger.debug('Using %s request handler threads' % self._options.nServerThreads) + except Exception, ex: + self._logger.exception(ex) + sys.exit(dmStatus.DM_ERROR) + + # Run server. + def __runServer(self): + self._logger.info('Starting service') + engine = cherrypy.engine + + # Set up Deamonization + if self._options.daemonFlag: + plugins.Daemonizer(engine).subscribe() + self._logger.debug('Daemon mode: %s' % self._options.daemonFlag) + + if self._options.pidfile != None: + plugins.PIDFile(engine, self._options.pidfile).subscribe() + self._logger.debug('Using PID file: %s' % self._options.pidfile) + + if self._options.sslCert != None and self._options.sslKey != None: + server.ssl_ca_certificate = None + if self._options.sslCaCert != None: + server.ssl_ca_certificate = self._options.sslCaCert + self._configurationManager.setSslCaCertFile(self._options.sslCaCert) + self._logger.info('Using SSL CA cert file: %s' % self._options.sslCaCert) + server.ssl_certificate = self._options.sslCert + self._configurationManager.setSslCertFile(self._options.sslCert) + self._logger.info('Using SSL cert file: %s' % self._options.sslCert) + + server.ssl_private_key = self._options.sslKey + self._configurationManager.setSslKeyFile(self._options.sslKey) + self._logger.info('Using SSL key file: %s' % self._options.sslKey) + + server.ssl_module = 'builtin' + #server.ssl_module = 'pyopenssl' + + # Increase timeout to prevent early SSL connection terminations + server.socket_timeout = DmRestWebService.DEFAULT_SERVER_SOCKET_TIMEOUT + + # Setup the signal handler to stop the application while running. + if hasattr(engine, 'signal_handler'): + engine.signal_handler.subscribe() + if hasattr(engine, 'console_control_handler'): + engine.console_control_handler.subscribe() + self.modifySignalHandlers(engine) + + # Turn off autoreloader. + self._logger.debug('Turning off autoreloader') + engine.autoreload.unsubscribe() + + # Start the engine. + try: + self._logger.debug('Starting engine') + engine.start() + + # Prepare dm services. + # Doing this before engine starts may cause issues with existing timers. + self._logger.debug('Starting modules') + self.initDmModules() + dmModuleManager.getInstance().startModules() + except Exception, ex: + self._logger.exception('Service exiting: %s' % ex) + dmModuleManager.getInstance().stopModules() + return dmStatus.DM_ERROR + self._logger.info('Service ready') + engine.block() + dmModuleManager.getInstance().stopModules() + self._logger.info('Service done') + return dmStatus.DM_OK + + # Run server instance. + def run(self): + self.prepareOptions() + self.prepareServer() + sys.exit(self.__runServer()) + +#################################################################### +# Testing + +if __name__ == '__main__': + pass diff --git a/src/python/dm/common/utility/__init__.py b/src/python/dm/common/utility/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/utility/configurationManager.py b/src/python/dm/common/utility/configurationManager.py new file mode 100755 index 0000000000000000000000000000000000000000..2ace128aec658d4252d43f8e8abf36661f9f990a --- /dev/null +++ b/src/python/dm/common/utility/configurationManager.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python + +# +# Configuration manager singleton. +# + +####################################################################### + +import os +import socket +import UserDict +import pwd + +from dm.common.constants import dmServiceConstants +from dm.common.exceptions.configurationError import ConfigurationError + +####################################################################### + +# Defaults. + +DEFAULT_DM_ROOT_DIR = '/opt/dm' +DEFAULT_DM_CONFIG_FILE = '%s/etc/dm.conf' # requires root dir + +DEFAULT_DM_LOG_FILE = '%s/var/log/dm.log' # requires root dir +DEFAULT_DM_LOG_CONFIG_FILE = '%s/etc/dm.log.conf' # requires root dir +DEFAULT_DM_CONSOLE_LOG_LEVEL = 'critical' +DEFAULT_DM_FILE_LOG_LEVEL = 'info' +#DEFAULT_DM_LOG_RECORD_FORMAT = '%(asctime)s,%(msecs)03d [%(levelname)s] %(module)s:%(lineno)d %(user)s@%(host)s %(name)s (%(process)d): %(message)s' +#DEFAULT_DM_LOG_RECORD_FORMAT = '%(asctime)s,%(msecs)03d %(levelname)s %(module)s:%(lineno)d %(process)d: %(message)s' +DEFAULT_DM_LOG_RECORD_FORMAT = '%(asctime)s,%(msecs)03d %(levelname)s %(process)d: %(message)s' +DEFAULT_DM_LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' + +DEFAULT_DM_CHERRYPY_LOG_LEVEL = 'ERROR' +DEFAULT_DM_CHERRYPY_LOG_FILE = '%s/var/log/cherrypy.error' # requires root dir +DEFAULT_DM_CHERRYPY_ACCESS_FILE = '%s/var/log/cherrypy.access' # requires root dir + +DEFAULT_DM_SERVICE_PORT = 10036 # 100DM +DEFAULT_DM_SERVICE_HOST = '0.0.0.0' +DEFAULT_DM_SERVICE_PROTOCOL = dmServiceConstants.DM_SERVICE_PROTOCOL_HTTP +DEFAULT_DM_ADMIN_USERNAME = '' +DEFAULT_DM_ADMIN_PASSWORD = '' + +DEFAULT_DM_DB = 'postgresql' +DEFAULT_DM_DB_HOST = 'localhost' +DEFAULT_DM_DB_PORT = 20036 # 200DM +DEFAULT_DM_DB_PASSWORD = '' +DEFAULT_DM_DB_USER = '' +DEFAULT_DM_DB_SCHEMA = 'dm' +DEFAULT_DM_DB_PASSWORD_FILE = '%s/lib/postgresql/etc/db.passwd' # requires root dir + +# Session cache file +DEFAULT_DM_SESSION_CACHE_FILE = None + +# Enforce session credentials. +DEFAULT_DM_REQUIRE_SESSION_CREDENTIALS = False + +# SSL variables +DEFAULT_DM_SSL_CA_CERT_FILE = None +DEFAULT_DM_SSL_CERT_FILE = None +DEFAULT_DM_SSL_KEY_FILE = None + +# Get singleton instance. +def getInstance(): + """ Get configuration manager singleton instance. """ + from dm.common.utility.configurationManager import ConfigurationManager + try: + cm = ConfigurationManager() + except ConfigurationManager, ex: + cm = ex + return cm + +class ConfigurationManager(UserDict.UserDict): + """ + Singleton class used for keeping system configuration data. The class + initializes its data using predefined defaults, or from certain + environment variables. + + Usage: + from dm.common.utility import configurationManager + cm = configurationManager.getInstance() + cm.setConsoleLogLevel('info') + level = cm.getConsoleLogLevel() + cm['myKey'] = 'myValue' + value = cm.get('myKey') + """ + + # Singleton. + __instance = None + + def __init__(self): + if ConfigurationManager.__instance: + raise ConfigurationManager.__instance + ConfigurationManager.__instance = self + UserDict.UserDict.__init__(self) + self['user'] = pwd.getpwuid(os.getuid())[0] + self['host'] = socket.gethostname() + + self['defaultRootDir'] = DEFAULT_DM_ROOT_DIR + self.__setFromEnvVar('rootDir', 'DM_ROOT_DIR') + + self['defaultConfigFile'] = DEFAULT_DM_CONFIG_FILE % self.getRootDir() + self['defaultLogFile'] = DEFAULT_DM_LOG_FILE % self.getRootDir() + self['defaultLogConfigFile'] = DEFAULT_DM_LOG_CONFIG_FILE % self.getRootDir() + + self['defaultConsoleLogLevel'] = DEFAULT_DM_CONSOLE_LOG_LEVEL + self['defaultFileLogLevel'] = DEFAULT_DM_FILE_LOG_LEVEL + self['defaultLogRecordFormat'] = DEFAULT_DM_LOG_RECORD_FORMAT + self['defaultLogDateFormat'] = DEFAULT_DM_LOG_DATE_FORMAT + + self['defaultCherrypyLogLevel'] = DEFAULT_DM_CHERRYPY_LOG_LEVEL + self['defaultCherrypyLogFile'] = DEFAULT_DM_CHERRYPY_LOG_FILE % self.getRootDir() + self['defaultCherrypyAccessFile'] = DEFAULT_DM_CHERRYPY_ACCESS_FILE % self.getRootDir() + + self['defaultServicePort'] = DEFAULT_DM_SERVICE_PORT + self['defaultServiceHost'] = DEFAULT_DM_SERVICE_HOST + self['defaultServiceProtocol'] = DEFAULT_DM_SERVICE_PROTOCOL + self['defaultAdminUsername'] = DEFAULT_DM_ADMIN_USERNAME + self['defaultAdminPassword'] = DEFAULT_DM_ADMIN_PASSWORD + self['defaultDb'] = DEFAULT_DM_DB + self['defaultDbHost'] = DEFAULT_DM_DB_HOST + self['defaultDbPort'] = DEFAULT_DM_DB_PORT + self['defaultDbPassword'] = DEFAULT_DM_DB_PASSWORD + self['defaultDbPasswordFile'] = DEFAULT_DM_DB_PASSWORD_FILE % self.getRootDir() + self['defaultDbUser'] = DEFAULT_DM_DB_USER + self['defaultDbSchema'] = DEFAULT_DM_DB_SCHEMA + + self['defaultSessionCacheFile'] = DEFAULT_DM_SESSION_CACHE_FILE + self['defaultRequireSessionCredentials'] = DEFAULT_DM_REQUIRE_SESSION_CREDENTIALS + + self['defaultSslCaCertFile'] = DEFAULT_DM_SSL_CA_CERT_FILE + self['defaultSslCertFile'] = DEFAULT_DM_SSL_CERT_FILE + self['defaultSslKeyFile'] = DEFAULT_DM_SSL_KEY_FILE + # Settings that might come from environment variables. + self.__setFromEnvVar('logFile', 'DM_LOG_FILE') + self.__setFromEnvVar('logConfigFile', 'DM_LOG_CONFIG_FILE') + self.__setFromEnvVar('consoleLogLevel', 'DM_CONSOLE_LOG_LEVEL') + self.__setFromEnvVar('fileLogLevel', 'DM_FILE_LOG_LEVEL') + self.__setFromEnvVar('logRecordFormat', 'DM_LOG_RECORD_FORMAT') + self.__setFromEnvVar('logDateFormat', 'DM_LOG_DATE_FORMAT') + + self.__setFromEnvVar('cherrypyLogLevel', 'DM_CHERRYPY_LOG_LEVEL') + self.__setFromEnvVar('cherrypyLogFile', 'DM_CHERRYPY_LOG_FILE') + self.__setFromEnvVar('cherrypyAccessFile', 'DM_CHERRYPY_ACCESS_FILE') + + self.__setFromEnvVar('serviceProtocol', 'DM_SERVICE_PROTOCOL') + self.__setFromEnvVar('serviceHost', 'DM_SERVICE_HOST') + self.__setFromEnvVar('servicePort', 'DM_SERVICE_PORT') + self.__setFromEnvVar('adminUsername', 'DM_ADMIN_USERNAME') + self.__setFromEnvVar('adminPassword', 'DM_ADMIN_PASSWORD') + + self.__setFromEnvVar('sessionCacheFile', 'DM_SESSION_CACHE_FILE') + + self.__setFromEnvVar('sslCaCertFile', 'DM_SSL_CA_CERT_FILE') + self.__setFromEnvVar('sslCertFile', 'DM_SSL_CERT_FILE') + self.__setFromEnvVar('sslKeyFile', 'DM_SSL_KEY_FILE') + + self.__setFromEnvVar('configFile', 'DM_CONFIG_FILE') + self.__setFromEnvVar('dbPasswordFile', 'DM_DB_PASSWORD_FILE') + + # Settings that might come from file. + self.__setFromVarFile('dbPassword', self.getDbPasswordFile()) + + # This function will ignore errors if environment variable is not set. + def __setFromEnvVar(self, key, envVar): + """ + Set value for the specified key from a given environment variable. + This function ignores errors for env. variables that are not set. + """ + try: + self[key] = os.environ[envVar] + except: + pass + + # This function will ignore errors if variable file is not present. + def __setFromVarFile(self, key, varFile): + """ + Set value for the specified key from a given file. The first line + in the file is variable value. + This function ignores errors. + """ + try: + v = open(varFile, 'r').readline() + self[key] = v.lstrip().rstrip() + except Exception, ex: + pass + + def __getKeyValue(self, key, default='__internal__'): + """ + Get value for a given key. + Keys will be of the form 'logFile', and the default keys have + the form 'defaultLogFile'. + """ + defaultKey = "default" + key[0].upper() + key[1:] + defaultValue = self.get(defaultKey, None) + if default != '__internal__': + defaultValue = default + return self.get(key, defaultValue) + + def getHost(self): + return self['host'] + + def getUser(self): + return self['user'] + + def getDefaultRootDir(self): + return self['defaultRootDir'] + + def setRootDir(self, rootDir): + self['rootDir'] = rootDir + + def getRootDir(self, default='__internal__'): + return self.__getKeyValue('rootDir', default) + + def getDefaultLogFile(self): + return self['defaultLogFile'] + + def setLogFile(self, logFile): + self['logFile'] = logFile + + def getLogFile(self, default='__internal__'): + return self.__getKeyValue('logFile', default) + + def hasLogFile(self): + return self.has_key('logFile') + + def getDefaultLogConfigFile(self): + return self['defaultLogConfigFile'] + + def setLogConfigFile(self, logConfigFile): + self['logConfigFile'] = logConfigFile + + def getLogConfigFile(self, default='__internal__'): + return self.__getKeyValue('logConfigFile', default) + + def hasLogConfigFile(self): + return self.has_key('logConfigFile') + + def getDefaultConsoleLogLevel(self): + return self['defaultConsoleLogLevel'] + + def setConsoleLogLevel(self, level): + self['consoleLogLevel'] = level + + def getConsoleLogLevel(self, default='__internal__'): + return self.__getKeyValue('consoleLogLevel', default) + + def hasConsoleLogLevel(self): + return self.has_key('consoleLogLevel') + + def getDefaultFileLogLevel(self): + return self['defaultFileLogLevel'] + + def setFileLogLevel(self, level): + self['fileLogLevel'] = level + + def getFileLogLevel(self, default='__internal__'): + return self.__getKeyValue('fileLogLevel', default) + + def hasFileLogLevel(self): + return self.has_key('fileLogLevel') + + def getDefaultLogRecordFormat(self): + return self['defaultLogRecordFormat'] + + def setLogRecordFormat(self, format): + self['logRecordFormat'] = format + + def getLogRecordFormat(self, default='__internal__'): + return self.__getKeyValue('logRecordFormat', default) + + def hasLogRecordFormat(self): + return self.has_key('logRecordFormat') + + def getDefaultLogDateFormat(self): + return self['defaultLogDateFormat'] + + def setLogDateFormat(self, format): + self['logDateFormat'] = format + + def getLogDateFormat(self, default='__internal__'): + return self.__getKeyValue('logDateFormat', default) + + def hasLogDateFormat(self): + return self.has_key('logDateFormat') + + def getDefaultCherrypyLogLevel(self): + return self['defaultCherrypyLogLevel'] + + def setCherrypyLogLevel(self, level): + self['cherrypyLogLevel'] = level + + def getCherrypyLogLevel(self, default='__internal__'): + return self.__getKeyValue('cherrypyLogLevel', default) + + def hasCherrypyLogLevel(self): + return self.has_key('cherrypyLogLevel') + + def getDefaultCherrypyLogCherrypy(self): + return self['defaultCherrypyLogFile'] + + def setCherrypyLogFile(self, cherrypyLogFile): + self['cherrypyLogFile'] = cherrypyLogFile + + def getCherrypyLogFile(self, default='__internal__'): + return self.__getKeyValue('cherrypyLogFile', default) + + def hasCherrypyLogFile(self): + return self.has_key('cherrypyLogFile') + + def getDefaultCherrypyAccessFile(self): + return self['defaultCherrypyAccessFile'] + + def setCherrypyAccessFile(self, cherrypyAccessFile): + self['cherrypyAccessFile'] = cherrypyAccessFile + + def getCherrypyAccessFile(self, default='__internal__'): + return self.__getKeyValue('cherrypyAccessFile', default) + + def hasCherrypyAccessFile(self): + return self.has_key('cherrypyAccessFile') + + def isDbAvailable(self): + if os.access(self.getDbPasswordFile(), os.R_OK): + return True + return False + + def getDefaultServiceProtocol(self): + return self['defaultServiceProtocol'] + + def setServiceProtocol(self, serviceProtocol): + self['serviceProtocol'] = serviceProtocol + + def getServiceProtocol(self, default='__internal__'): + return self.__getKeyValue('serviceProtocol', default) + + def hasServiceProtocol(self): + return self.has_key('serviceProtocol') + + def getDefaultServicePort(self): + return self['defaultServicePort'] + + def setServicePort(self, servicePort): + self['servicePort'] = servicePort + + def getServicePort(self, default='__internal__'): + return int(self.__getKeyValue('servicePort', default)) + + def hasServicePort(self): + return self.has_key('servicePort') + + def getDefaultServiceHost(self): + return self['defaultServiceHost'] + + def setServiceHost(self, serviceHost): + self['serviceHost'] = serviceHost + + def getServiceHost(self, default='__internal__'): + return self.__getKeyValue('serviceHost', default) + + def hasServiceHost(self): + return self.has_key('serviceHost') + + def getDefaultAdminUsername(self): + return self['defaultAdminUsername'] + + def setAdminUsername(self, adminUsername): + self['adminUsername'] = adminUsername + + def getAdminUsername(self, default='__internal__'): + return self.__getKeyValue('adminUsername', default) + + def hasAdminUsername(self): + return self.has_key('adminUsername') + + def getDefaultAdminPassword(self): + return self['defaultAdminPassword'] + + def setAdminPassword(self, adminPassword): + self['adminPassword'] = adminPassword + + def getAdminPassword(self, default='__internal__'): + return self.__getKeyValue('adminPassword', default) + + def hasAdminPassword(self): + return self.has_key('adminPassword') + + def getDefaultDb(self): + return self['defaultDb'] + + def setDb(self, db): + self['db'] = db + + def getDb(self, default='__internal__'): + return self.__getKeyValue('db', default) + + def hasDb(self): + return self.has_key('db') + + def getDefaultDbHost(self): + return self['defaultDbHost'] + + def setDbHost(self, dbHost): + self['dbHost'] = dbHost + + def getDbHost(self, default='__internal__'): + return self.__getKeyValue('dbHost', default) + + def hasDbHost(self): + return self.has_key('dbHost') + + def getDefaultDbPort(self): + return self['defaultDbPort'] + + def setDbPort(self, dbPort): + self['dbPort'] = dbPort + + def getDbPort(self, default='__internal__'): + return self.__getKeyValue('dbPort', default) + + def hasDbPort(self): + return self.has_key('dbPort') + + def getDefaultDbPassword(self): + return self['defaultDbPassword'] + + def setDbPassword(self, dbPassword): + self['dbPassword'] = dbPassword + + def getDbPassword(self, default='__internal__'): + return self.__getKeyValue('dbPassword', default) + + def hasDbPassword(self): + return self.has_key('dbPassword') + + def getDefaultDbPasswordFile(self): + return self['defaultDbPasswordFile'] + + def getDbPasswordFile(self, default='__internal__'): + return self.__getKeyValue('dbPasswordFile', default) + + def setDbPasswordFile(self, f): + self['dbPasswordFile'] = f + + def hasDbPasswordFile(self): + return self.has_key('dbPasswordFile') + + def getDefaultDbUser(self): + return self['defaultDbUser'] + + def getDbUser(self, default='__internal__'): + return self.__getKeyValue('dbUser', default) + + def setDbUser(self, u): + self['dbUser'] = u + + def hasDbUser(self): + return self.has_key('dbUser') + + def getDbSchema(self): + return self['dbSchema'] + + def getDefaultConfigFile(self): + return self['defaultConfigFile'] + + def setConfigFile(self, configFile): + self['configFile'] = configFile + + def getConfigFile(self, default='__internal__'): + return self.__getKeyValue('configFile', default) + + def hasConfigFile(self): + return self.has_key('configFile') + + def getDefaultSessionCacheFile(self): + return self['defaultSessionCacheFile'] + + def setSessionCacheFile(self, sessionCacheFile): + self['sessionCacheFile'] = sessionCacheFile + + def getSessionCacheFile(self, default='__internal__'): + return self.__getKeyValue('sessionCacheFile', default) + + def hasSessionCacheFile(self): + return self.has_key('sessionCacheFile') + + def getDefaultRequireSessionCredentials(self): + return self['defaultRequireSessionCredentials'] + + def setRequireSessionCredentials(self, requireSessionCredentials): + self['requireSessionCredentials'] = requireSessionCredentials + + def getRequireSessionCredentials(self, default='__internal__'): + return self.__getKeyValue('requireSessionCredentials', default) + + def hasRequireSessionCredentials(self): + return self.has_key('requireSessionCredentials') + + def getDefaultSslCaCertFile(self): + return self['defaultSslCaCertFile'] + + def setSslCaCertFile(self, sslCaCertFile): + self['sslCaCertFile'] = sslCaCertFile + + def getSslCaCertFile(self, default='__internal__'): + return self.__getKeyValue('sslCaCertFile', default) + + def hasSslCaCertFile(self): + return self.has_key('sslCaCertFile') + + def getDefaultSslCertFile(self): + return self['defaultSslCertFile'] + + def setSslCertFile(self, sslCertFile): + self['sslCertFile'] = sslCertFile + + def getSslCertFile(self, default='__internal__'): + return self.__getKeyValue('sslCertFile', default) + + def hasSslCertFile(self): + return self.has_key('sslCertFile') + + def getDefaultSslKeyFile(self): + return self['defaultSslKeyFile'] + + def setSslKeyFile(self, sslKeyFile): + self['sslKeyFile'] = sslKeyFile + + def getSslKeyFile(self, default='__internal__'): + return self.__getKeyValue('sslKeyFile', default) + + def hasSslKeyFile(self): + return self.has_key('sslKeyFile') + +####################################################################### +# Testing. + +if __name__ == '__main__': + cm = getInstance() + print cm diff --git a/src/python/dm/common/utility/consoleLoggingHandler.py b/src/python/dm/common/utility/consoleLoggingHandler.py new file mode 100755 index 0000000000000000000000000000000000000000..bf58677313621ef7eddfc8025c88be3b7814040e --- /dev/null +++ b/src/python/dm/common/utility/consoleLoggingHandler.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# +# Console logging handler class +# + +####################################################################### + +import socket +import pwd +import os +from logging import StreamHandler + +####################################################################### + +class ConsoleLoggingHandler(StreamHandler): + """ Class that enables console logging. """ + def __init__(self, *args): + StreamHandler.__init__(self, *args) + self._user = pwd.getpwuid(os.getuid())[0] + self._host = socket.gethostname() + + def emit(self, record): + record.__dict__['user'] = self._user + record.__dict__['host'] = self._host + return StreamHandler.emit(self, record) + +####################################################################### +# Testing. + +if __name__ == '__main__': + import sys + import logging + exec 'sh = ConsoleLoggingHandler(sys.stdout,)' + sh.setLevel(logging.INFO) + rootLogger = logging.getLogger('') + logging.basicConfig(level=logging.DEBUG) + + mainLogger = logging.getLogger('main') + mainLogger.debug("main debug") + mainLogger.info("main info") + diff --git a/src/python/dm/common/utility/dmModuleManager.py b/src/python/dm/common/utility/dmModuleManager.py new file mode 100755 index 0000000000000000000000000000000000000000..62a85e90ab3edc89b293acf5b19f5c2ebe3142c9 --- /dev/null +++ b/src/python/dm/common/utility/dmModuleManager.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# +# Module manager class. +# + +####################################################################### + +import threading + +####################################################################### + +# Get singleton instance. +def getInstance(): + from dm.common.utility.dmModuleManager import DmModuleManager + try: + mgr = DmModuleManager() + except DmModuleManager, ex: + mgr = ex + return mgr + +class DmModuleManager: + """ Singleton class used for managing dm modules. """ + + # Singleton. + __instanceLock = threading.RLock() + __instance = None + + def __init__(self): + DmModuleManager.__instanceLock.acquire() + try: + if DmModuleManager.__instance: + raise DmModuleManager.__instance + DmModuleManager.__instance = self + from dm.common.utility import loggingManager + self._logger = loggingManager.getLogger(self.__class__.__name__) + self._lock = threading.RLock() + self._moduleList = [] + self._modulesRunning = False + finally: + DmModuleManager.__instanceLock.release() + + def addModule(self, m): + self._lock.acquire() + try: + self._logger.debug('Adding dm module: %s' % m.__class__.__name__) + self._moduleList.append(m) + finally: + self._lock.release() + + def startModules(self): + self._lock.acquire() + try: + if self._modulesRunning: + return + for m in self._moduleList: + self._logger.debug('Starting dm module: %s' % m.__class__.__name__) + m.start() + self._modulesRunning = True + finally: + self._lock.release() + + def stopModules(self): + self._lock.acquire() + try: + if not self._modulesRunning: + return + n = len(self._moduleList) + for i in range(0, n): + m = self._moduleList[n-1-i] + self._logger.debug('Stopping dm module: %s' % m.__class__.__name__) + m.stop() + self._modulesRunning = False + finally: + self._lock.release() + +####################################################################### +# Testing. + +if __name__ == '__main__': + pass diff --git a/src/python/dm/common/utility/dmSubprocess.py b/src/python/dm/common/utility/dmSubprocess.py new file mode 100755 index 0000000000000000000000000000000000000000..46a9964c5309cbfd17ad6e67078569644f9701b5 --- /dev/null +++ b/src/python/dm/common/utility/dmSubprocess.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +# +# Subprocess class +# + +####################################################################### + +import os +import subprocess +import platform + +from dm.common.utility import loggingManager +from dm.common.exceptions.commandFailed import CommandFailed + +####################################################################### + +class DmSubprocess(subprocess.Popen): + + def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=None, close_fds=False, shell=True, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, useExceptions=True, quietMode=False): + """ Overrides Popen constructor with defaults more appropriate DM. """ + subprocess.Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags) + self._logger = loggingManager.getLogger(self.__class__.__name__) + self._stdout = None + self._stderr = None + self._args = args + self._useExceptions = useExceptions + self._quietMode = quietMode + + def _commandLog(self): + # Not very useful to show the name of this file. + # Walk up the stack to find the caller. + import traceback + stack = traceback.extract_stack() + for i in range(2, len(stack)): + if stack[-i][0] != stack[-1][0]: + fileName, lineNumber, functionName, text = stack[-i] + break + else: + fileName = lineNumber = functionName = text = '?' + + self._logger.debug('From [%s:%s] Invoking: [%s]' % (os.path.basename(fileName), lineNumber, self._args)) + + def run(self, input=None): + """ Run subprocess. """ + if not self._quietMode: + self._commandLog() + (self._stdout, self._stderr) = subprocess.Popen.communicate(self, input) + if not self._quietMode: + self._logger.debug('Exit status: %s' % self.returncode) + if self.returncode != 0 and self._useExceptions: + if not self._quietMode: + self._logger.debug('StdOut: %s' % self._stdout) + self._logger.debug('StdErr: %s' % self._stderr) + error = self._stderr.strip() + if error == '': + error = self._stdout.strip() + raise CommandFailed('%s' % (error)) + return (self._stdout, self._stderr) + + def getLogger(self): + return self._logger + + def getArgs(self): + return self._args + + def getStdOut(self): + return self._stdout + + def getStdErr(self): + return self._stderr + + def getExitStatus(self): + return self.returncode + +# Convenience function for getting subprocess. +def getSubprocess(command): + if platform.system() != 'Windows': + close_fds = True + else: + close_fds = False + p = DmSubprocess(command, close_fds=close_fds) + return p + +# Convenience function for executing command. +def executeCommand(command): + """ Create subprocess and run it, return subprocess object. """ + p = getSubprocess(command) + p.run() + return p + +# Convenience function for executing command that may fail, and we do not +# care about the failure. +def executeCommandAndIgnoreFailure(command): + """ Create subprocess, run it, igore any failures, and return subprocess object. """ + p = getSubprocess(command) + try: + p.run() + except CommandFailed, ex: + p.getLogger().debug('Command failed, stdout: %s, stderr: %s' % (p.getStdOut(), p.getStdErr())) + return p + +def executeCommandAndLogToStdOut(command): + """ Execute command, display output to stdout, maintain log file and return subprocess object. """ + p = getSubprocess(command) + p._commandLog() + + while True: + outp = p.stdout.readline() + if not outp: + break + print outp, + + retval = p.wait() + + p._logger.debug('Exit status: %s' % retval) + + if retval != 0: + error = '' + while True: + err = p.stderr.readline() + if not err: + break + error += err + raise CommandFailed(error) + return p + +####################################################################### +# Testing. + +if __name__ == '__main__': + p = DmSubprocess('ls -l', useExceptions=False) + p.run() + print p.getStdOut() + print p.getStdErr() + print p.getExitStatus() + diff --git a/src/python/dm/common/utility/loggingManager.py b/src/python/dm/common/utility/loggingManager.py new file mode 100755 index 0000000000000000000000000000000000000000..98663847027d9f3ac500f0500dcca56528cd1750 --- /dev/null +++ b/src/python/dm/common/utility/loggingManager.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python + +# +# Logging manager singleton. +# + +####################################################################### + +import re +import sys +import os.path +import logging +import ConfigParser +from dm.common.utility import configurationManager +from dm.common.exceptions.configurationError import ConfigurationError + +####################################################################### + +# Get singleton instance. +def getInstance(): + from dm.common.utility.loggingManager import LoggingManager + try: + lm = LoggingManager() + except LoggingManager, ex: + lm = ex + return lm + +def getLogger(name='defaultLogger'): + """ Convenience function to obtain logger. """ + return getInstance().getLogger(name) + +def setConsoleLogLevel(level): + """ Convenience function to set the console log level. """ + return getInstance().setConsoleLogLevel(level) + +def setFileLogLevel(level): + """ Convenience function to set the file log level. """ + return getInstance().setFileLogLevel(level) + +class LoggingManager: + """ + Configuration: + The log manager class is initialized via a configuration file + that may have the following sections: + ConsoleLogging # Used for output on the screen + FileLogging # Used for logging into a file + + Each section in the configuration file should have the following + keys: + handler # Indicates which handler class to use + level # Indicates logging level + format # Indicates format for log messages + dateformat # Indicates date format used for log messages + + Given below is an example of a valid configuration file: + + [ConsoleLogging] + handler=ConsoleLoggingHandler(sys.stdout,) + level=info + format=[%(levelname)s] %(message)s + dateformat=%m/%d/%y %H:%M:%S + [FileLogging] + handler=TimedRotatingFileLoggingHandler('/tmp/dm.log') + level=debug + format=%(asctime)s,%(msecs)d [%(levelname)s] %(module)s:%(lineno)d %(user)s@%(host)s %(name)s (%(process)d): %(message)s + dateformat=%m/%d/%y %H:%M:%S + """ + + # Singleton. + __instance = None + + def __init__(self): + if LoggingManager.__instance: + raise LoggingManager.__instance + LoggingManager.__instance = self + self._consoleHandler = None + self._fileHandlerList = [] + self._maxIntLevel = logging.CRITICAL + self._minIntLevel = logging.NOTSET + self._levelRegExList = [] + self._logger = logging.getLogger(self.__class__.__name__) + self._initFlag = False + + def setMinLogLevel(self, minLogLevel=logging.INFO): + self._minIntLevel = minLogLevel + + def parseLevelRegEx(self, levelRegExList): + """ Parse a list of expressions of the form <regex>=<log-level>. """ + lines = levelRegExList.split('\n') + for line in lines: + try: + # Use the right split so we can have '='s in the regex + (regex, level) = line.rsplit('=', 1) + pattern = re.compile(regex) + tuple = (pattern, logging.getLevelName(level.upper())) + self._levelRegExList.append(tuple) + except Exception, ex: + # Do not fail, but log an error. + self._logger.error('Parser error in log configuration file: %s' % line) + self._logger.exception(ex) + + # Get Log Level based on a string representation + def getIntLogLevel(self, levelStr): + level = logging.getLevelName(levelStr) + # Level should be an integer + try: + return int(level) + except ValueError, ex: + raise ConfigurationError('"%s" is not valid log level' % levelStr) + + # Configure log handlers. + def configureHandlers(self): + """ Configure log handlers from the config file. """ + cm = configurationManager.getInstance() + configFile = cm.getLogConfigFile() + configSections = self.__getConfigSections(configFile) + + # Console handler. + defaults = { + 'level' : cm.getConsoleLogLevel(), + 'format' : cm.getLogRecordFormat(), + 'dateformat' : cm.getLogDateFormat(), + 'handler' : 'ConsoleLoggingHandler(sys.stdout,)' + } + consoleHandler = self.__configureHandler(configFile, 'ConsoleLogging', defaults) + + if consoleHandler != None: + self._consoleHandler = consoleHandler + + # File logging. # Do not configure if log directory does + # not exist. + defaults['handler'] = None + defaults['level'] = cm.getFileLogLevel() + if not os.path.exists(configFile): + # No config file, we'll configure default. + defaultLogFile = cm.getLogFile() + defaultLogDir = os.path.dirname(defaultLogFile) + if os.path.exists(defaultLogDir): + handler = 'TimedRotatingFileLoggingHandler("%s")' % defaultLogFile + defaults['handler'] = handler + fileHandler = self.__configureHandler(configFile, 'FileLogging', defaults) + if fileHandler != None: + self._fileHandlerList.append(fileHandler) + + else: + # Parse all file loggers present in the config file + for configSection in configSections: + if configSection.startswith('FileLogging'): + fileHandler = self.__configureHandler(configFile, configSection, defaults) + if fileHandler != None: + self._fileHandlerList.append(fileHandler) + + # Add handlers to the root logger. Use logging class here + # to make sure we can have a logger when we parse the + # logger expressions + rootLogger = logging.getLogger('') + for handler in [self._consoleHandler] + self._fileHandlerList: + rootLogger.addHandler(handler) + + # Get a logger factory based on our current config + self.configureLoggers(configFile, defaultLevel=cm.getFileLogLevel()) + + def configureLoggers(self, configFile, defaultLevel='error'): + configParser = ConfigParser.ConfigParser() + configParser.read(configFile) + + rootLogLevel = 'error' + levelRegEx = '^.*$=%s' % (defaultLevel) + if configParser.has_section('LoggerLevels'): + rootLogLevel = configParser.get('LoggerLevels', 'root', rootLogLevel) + levelRegEx = configParser.get('LoggerLevels', 'levelregex', levelRegEx) + + rootLevelInt = logging.getLevelName(rootLogLevel.upper()) + logging.getLogger('').root.setLevel(rootLevelInt) + logging.getLogger('').debug('Set root logger to %s' % rootLevelInt) + + if not levelRegEx: + return + + # Parse expressions of the form <regex>=<log-level>. """ + lines = levelRegEx.split('\n') + for line in lines: + try: + # Use the right split so we can have '='s in the regex + (regex, level) = line.rsplit('=', 1) + pattern = re.compile(regex) + tuple = (pattern, logging.getLevelName(level.upper())) + self._levelRegExList.append(tuple) + except Exception, ex: + # Do not fail + self._logger.error('Parser error in log configuration file: %s' % line) + self._logger.exception(ex) + + def __getOptionFromConfigFile(self, configParser, configSection, key, defaultValue=None): + """ Get specified option from the configuration file. """ + if configParser.has_section(configSection): + return configParser.get(configSection, key, True) + else: + return defaultValue + + # Get the sections in the config file + def __getConfigSections(self, configFile): + """ Return a list of the sections in the given config file """ + configParser = ConfigParser.RawConfigParser() + configParser.read(configFile) + return configParser.sections() + + # Configure particular handler with given defaults. + def __configureHandler(self, configFile, configSection, defaults): + """ Configure specified handler with a given defaults. """ + configParser = ConfigParser.ConfigParser(defaults) + configParser.read(configFile) + handlerOption = defaults['handler'] + try: + handlerOption = configParser.get(configSection, 'handler', True) + except Exception, ex: + pass + + # If handlerOption is empty, handler cannot be instantiated. + handler = None + if handlerOption != None: + # Handler argument format: MyHandler(arg1, arg2, ...) + # Module will be in lowercase letters, but the class + # should be capitalized. + handlerName = re.sub('\(.*', '', handlerOption) + moduleName = handlerName[0].lower() + handlerName[1:] + try: + exec 'from dm.common.utility import %s' % (moduleName) + exec '_handler = %s.%s' % (moduleName, handlerOption) + except IOError, ex: + errno, _emsg = ex + import errno + + # If the exception raised is an I/O permissions error, ignore + # it and disable this log handler. This allows non-root users + # to use the (system-wide) default log configuration + if _errno != errno.EACCES: + raise + _handler = None + except Exception, ex: + raise ConfigurationError(exception=ex) + + # Only request setting from the config file if it was + # not set via environment variable, or programmatically. + if _handler != None: + try: + _level = self.__getOptionFromConfigFile(configParser, + configSection, 'level', defaults['level']) + intLevel = self.getIntLogLevel(_level.upper()) + _handler.setLevel(intLevel) + + _format = self.__getOptionFromConfigFile(configParser, configSection, 'format', defaults['format']) + _dateformat = self.__getOptionFromConfigFile(configParser, configSection, 'dateformat', defaults['dateformat']) + + _handler.setFormatter(logging.Formatter(_format, _dateformat)) + except Exception, ex: + raise ConfigurationError(exception=ex) + + # Look to see if there is a filter to apply to the handler + filter = None + try: + filter = configParser.get(configSection, 'filter') + except Exception, ex: + pass + + if filter: + _handler.addFilter(logging.Filter(filter)) + return _handler + + def getLogger(self, name='defaultLogger'): + if not self._initFlag: + self._initFlag = True + self.configureHandlers() + logger = logging.getLogger(name) + logger.setLevel(self.getLevel(name)) + return logger + + def getLevel(self, name): + # Match from the known regex list. + level = logging.NOTSET + + # The last regex is most important. + for e in reversed(self._levelRegExList): + (pattern, level) = e + + # If we return not None it is a match + if not None == pattern.match(name): + break + + if level > self._maxIntLevel: + level = self._maxIntLevel + if level < self._minIntLevel: + level = self._minIntLevel + return level + + def setConsoleLogLevel(self, level): + try: + # We need to override the logger levels and the handler + intLevel = self.getIntLogLevel(level.upper()) + self._consoleHandler.setLevel(intLevel) + self._maxIntLevel = intLevel + self._logger.setLevel(intLevel) + except Exception, ex: + raise ConfigurationError(exception=ex) + + def setFileLogLevel(self, level): + try: + # We need to override the logger levels and the handler + intLevel = self.getIntLogLevel(level.upper()) + for handler in self._fileHandlerList: + handler.setLevel(intLevel) + self._maxIntLevel = intLevel + self._logger.setLevel(intLevel) + except Exception, ex: + raise ConfigurationError(exception=ex) + +####################################################################### +# Testing. + +if __name__ == '__main__': + lm = getInstance() + logger = lm.getLogger('Main') + logger.info('Info In Main') + logger = lm.getLogger('Main') + logger.info('Info In Main 2') + logger = lm.getLogger('') + logger.info('Info using root logger') + logger = lm.getLogger('Main.2') + logger.info('Info in Main.2') + logger.debug('You should not see this message') + lm.setConsoleLogLevel('debug') + logger.debug('Debug in Main.2') diff --git a/src/python/dm/common/utility/timedRotatingFileLoggingHandler.py b/src/python/dm/common/utility/timedRotatingFileLoggingHandler.py new file mode 100755 index 0000000000000000000000000000000000000000..717fe1ea042b40fc79f79ae884f1499342584ccf --- /dev/null +++ b/src/python/dm/common/utility/timedRotatingFileLoggingHandler.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +####################################################################### + +import socket +import pwd +import os +from logging.handlers import TimedRotatingFileHandler + +####################################################################### + +class TimedRotatingFileLoggingHandler(TimedRotatingFileHandler): + """ Class that enables logging into files. """ + def __init__(self, filename, when='D', interval=1, backupCount=0, encoding=None): + TimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding) + self._user = pwd.getpwuid(os.getuid())[0] + self._host = socket.gethostname() + + def emit(self, record): + record.__dict__['user'] = self._user + record.__dict__['host'] = self._host + return TimedRotatingFileHandler.emit(self, record) + +####################################################################### +# Testing. + +if __name__ == '__main__': + pass diff --git a/src/python/dm/fs_service/__init__.py b/src/python/dm/fs_service/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/fs_service/impl/__init__.py b/src/python/dm/fs_service/impl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/fs_service/impl/fsControllerImpl.py b/src/python/dm/fs_service/impl/fsControllerImpl.py new file mode 100644 index 0000000000000000000000000000000000000000..798464d68fcf32cd5481d7fb2d34883eca369979 --- /dev/null +++ b/src/python/dm/fs_service/impl/fsControllerImpl.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +# +# Implementation for file system controller. +# + +####################################################################### + +import threading + +from dm.common.objects.dmObject import DmObject +from dm.common.objects.dmObjectManager import DmObjectManager +from dm.common.utility.dmSubprocess import DmSubprocess + +####################################################################### + +class FsControllerImpl(DmObjectManager): + """ FS controller implementation class. """ + + def __init__(self): + DmObjectManager.__init__(self) + + def getDirectoryList(self, path): + p = DmSubprocess('ls -l %s' % path) + p.run() + return DmObject({'path' : path, 'directoryList' : p.getStdOut()}) diff --git a/src/python/dm/fs_service/service/__init__.py b/src/python/dm/fs_service/service/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/fs_service/service/fsController.py b/src/python/dm/fs_service/service/fsController.py new file mode 100755 index 0000000000000000000000000000000000000000..050121c2de7367e5721cc7cfabd821398da918d7 --- /dev/null +++ b/src/python/dm/fs_service/service/fsController.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +####################################################################### + +import cherrypy + +from dm.common.service.dmController import DmController +from dm.common.objects.dmObject import DmObject +from dm.common.exceptions.dmException import DmException +from dm.common.exceptions.internalError import InternalError +from dm.common.exceptions.invalidRequest import InvalidRequest + +from dm.fs_service.impl.fsControllerImpl import FsControllerImpl + +####################################################################### + +class FsController(DmController): + + def __init__(self): + DmController.__init__(self) + self._fsControllerImpl = FsControllerImpl() + + @cherrypy.expose + def getDirectoryList(self, **kwargs): + try: + if not kwargs.has_key('path'): + raise InvalidRequest('Missing directory path.') + path = kwargs.get('path') + response = '%s' % self._fsControllerImpl.getDirectoryList(path).getJsonRep() + self.getLogger().debug('Returning: %s' % response) + except DmException, ex: + self.getLogger().error('%s' % ex) + self.handleException(ex) + response = ex.getJsonRep() + except Exception, ex: + self.getLogger().error('%s' % ex) + self.handleException(ex) + response = InternalError(ex).getJsonRep() + return self.formatJsonResponse(response) + diff --git a/src/python/dm/fs_service/service/fsService.py b/src/python/dm/fs_service/service/fsService.py new file mode 100755 index 0000000000000000000000000000000000000000..49f524d36814c96722a814b880fa892e1bc01901 --- /dev/null +++ b/src/python/dm/fs_service/service/fsService.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +# +# File system controller service +# + +#################################################################### + +from dm.common.service.dmRestWebService import DmRestWebService +from dm.common.utility import dmModuleManager +from dm.fs_service.service import fsServiceRouteMapper + +#################################################################### + +class FsService(DmRestWebService): + + def __init__(self): + DmRestWebService.__init__(self, fsServiceRouteMapper) + + def initDmModules(self): + self._logger.debug('Initializing dm modules') + + # Add modules that will be started. + moduleManager = dmModuleManager.getInstance() + self._logger.debug('Initialized dm modules') + +#################################################################### +# Run service + +if __name__ == '__main__': + service = FsService(); + service.run() diff --git a/src/python/dm/fs_service/service/fsServiceRouteMapper.py b/src/python/dm/fs_service/service/fsServiceRouteMapper.py new file mode 100755 index 0000000000000000000000000000000000000000..73cf05c89047e90f65fb1ec7b487b4e0e39ea767 --- /dev/null +++ b/src/python/dm/fs_service/service/fsServiceRouteMapper.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# +# Route mapper for file system service. +# + +####################################################################### + +import sys +import os + +import cherrypy +from dm.common.utility import loggingManager +from dm.fs_service.service.fsController import FsController + +####################################################################### + +def setupRoutes(): + """ Setup RESTFul routes. """ + + # Static instances shared between different routes + fsController = FsController() + + # Define routes. + # Make sure to have leading '/' for consistency. + routes = [ + # ('GET') routes do not require authorization. + # ('PUT', 'POST', 'DELETE') routes require authorization. + + # + # FS Controller routes + # + + # Get directory listing + { + 'name' : 'getDirectoryList', + 'path' : '/directory/list', + 'controller' : fsController, + 'action' : 'getDirectoryList', + 'method' : ['GET'] + }, + ] + + # Add routes to dispatcher. + d = cherrypy.dispatch.RoutesDispatcher() + logger = loggingManager.getLogger('setupRoutes') + for route in routes: + logger.debug('Connecting route: %s' % route) + d.connect(route['name'], route['path'], action=route['action'], controller=route['controller'], conditions=dict(method=route['method'])) + return d +