#!/usr/bin/env python # # Project: PROJECT # http://www.edna-site.org # # File: "$Id: $" # # Copyright (C) DLS # # Principal author: irakli # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser 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 Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License # and the GNU Lesser General Public License along with this program. # If not, see . # # # Set up PYTHON path for the EDNA kernel # # First locate EDNA_HOME and EDNA_SITE # import sys, os.path, glob, shlex strProgramPath = sys.argv[0] strProgramDir = os.path.split(strProgramPath)[0] #pyStrMXv1Path = os.path.split(pyStrBinPath)[0] #pyStrXNCFPath = os.path.split(pyStrBinPath)[0] #pyStrEdnaHomePath = os.path.split(pyStrMXv1Path)[0] #pyStrEdnaSite = 'SDANoQSubDLS' #os.environ["EDNA_HOME"] = pyStrEdnaHomePath #os.environ["EDNA_SITE"] = pyStrEdnaSite if (not "EDNA_HOME" in os.environ.keys() or not "EDNA_SITE" in os.environ.keys()): print "Cannot start the EDNA Bottle server:" print "Make sure that $EDNA_HOME and $EDNA_SITE are set up before running edna-bottle-server." print "Example:" print "$ export EDNA_SITE= (should be the configuration file suffix XSConfiguration_.xml)" print "Please read the INSTALL.txt file under the \"$EDNA_HOME/mxv1\" directory for more details" print "" sys.exit(1) pyStrEdnaHomePath = os.environ["EDNA_HOME"] strMxv1ConfigurationFilePath = os.path.join(pyStrEdnaHomePath, "mxv1", "conf", "XSConfiguration_" + os.environ["EDNA_SITE"] + ".xml") strXncfConfigurationFilePath = os.path.join(pyStrEdnaHomePath, "xncf", "conf", "XSConfiguration_" + os.environ["EDNA_SITE"] + ".xml") strScxdapConfigurationFilePath = os.path.join(pyStrEdnaHomePath, "scxdap", "conf", "XSConfiguration_" + os.environ["EDNA_SITE"] + ".xml") strDimpleConfigurationFilePath = os.path.join(pyStrEdnaHomePath, "dimplev0", "conf", "XSConfiguration_" + os.environ["EDNA_SITE"] + ".xml") # # Then add kernel/src and mxv1/src to PYTHONPATH # sys.path.append(os.path.join(pyStrEdnaHomePath, "kernel", "src")) sys.path.append(os.path.join(pyStrEdnaHomePath, "mxv1", "src")) sys.path.append(os.path.join(pyStrEdnaHomePath, "xncf", "src")) sys.path.append(os.path.join(pyStrEdnaHomePath, "scxdap", "src")) sys.path.append(os.path.join(pyStrEdnaHomePath, "dimplev0", "src")) sys.path.append(os.path.join(pyStrEdnaHomePath, "dimplev0", "plugins", "EDPluginControlDIMPLEPipelineCalcDiffMap-v1.0", "plugins")) # Are the appends below necessary? sys.path.append(os.path.join(pyStrEdnaHomePath, "xncf", "plugins", "EDPluginControlXAFSDataBatchProcessing-v0.1","plugins")) sys.path.append(os.path.join(pyStrEdnaHomePath, "xncf", "plugins", "EDPluginControlXAFSDataProcessing-v0.1","plugins")) sys.path.append(os.path.join(pyStrEdnaHomePath, "xncf", "plugins", "EDPluginExecIfeffit-v0.1","plugins")) sys.path.append(os.path.join(pyStrEdnaHomePath, "xncf", "plugins", "EDPluginExecIfeffit-v0.1","plugins")) import bottle, cherrypy from bottle import CherryPyServer, route, run, debug, request, template from cherrypy import wsgiserver import thread import os, pwd from EDVerbose import EDVerbose from EDUtilsFile import EDUtilsFile from EDConfiguration import EDConfiguration from EDFactoryPluginStatic import EDFactoryPluginStatic from XSDataCommon import XSDataString from EDApplicationMXv1Characterisation import EDApplicationMXv1Characterisation from XSDataMXv1 import XSDataInputCharacterisation @route('/pipeline/form', method='GET') def form(): return template('new_job.tpl') @route('/pipeline', method='POST') def pipeline(): # Get the three different parameters as defined in the WSDL strPipelineName = str(request.params.get('name', '').strip()) strXML = str(request.params.get('xml', '').strip()) strDir = "/tmp" if request.params.dict.has_key('dir'): strDir = str(request.params.get('dir', '').strip()) if strDir != None: if os.path.exists(strDir): os.chdir(strDir) iJobId = server.jobscount server.jobs[iJobId] = "Started" server.jobscount += 1 # store the fedid of user, time/date, host in a file so we can keep track of how often things are being run log = open("/dls_sw/apps/Modules/modulefiles/dawn/logs/ednaBottleServer.log",'a') import time lineToAdd = time.strftime("%Y/%m/%d-%H:%M:%S") + "\t"+ os.environ["USER"] + "\t" + os.environ["HOSTNAME"] + "\t" + strPipelineName + "\n" log.write(lineToAdd) log.close() # Start the appropriate pipeline in a separate thread if strPipelineName == "XAFSPipeline": thread.start_new_thread(runXAFSPipeline, (strXML, iJobId)) elif strPipelineName == "XAFSProcessing": dataPattern = listArgs['nxs'].strip() thread.start_new_thread(runXAFSProcessing, (dataPattern, iJobId)) elif strPipelineName == "MXv1Pipeline": thread.start_new_thread(runMXv1Pipeline, (strXML, iJobId)) elif strPipelineName == "Fast_dp Pipeline": thread.start_new_thread(runFastDPEDNA, (strXML, iJobId)) elif strPipelineName == "Xia2 Commandline Pipeline": thread.start_new_thread(runXia2CommandLineEDNA, (strXML, iJobId)) elif strPipelineName == "Extract MTZ column headers": thread.start_new_thread(extractMTZColumnHeaders, (strXML, iJobId)) elif strPipelineName == "Dimple Pipeline": thread.start_new_thread(runDimplePipeline, (strXML, iJobId)) else: return bottle.HTTPError(404, "Pipeline not found") # 404 Not found # ... and return the job ID return handleXML(str(iJobId), server.jobs[iJobId][0]) @route('/pipeline/:id', method='GET') def status(id): strXML= statusXML() intid = int(id) if server.jobscount > intid: strXML= statusXML(str(id), server.jobs[intid][0], server.jobs[intid][1]) # id, message, result if server.jobs[intid][0] == 'Success': EDVerbose.screen("EDNA Bottle status: " +str(strXML) + " job id "+ str(intid)) return strXML @route('/server/status', method='GET') def serverStatus(): return 'WSGI server running!' @route('/server/owner', method='GET') def serverOwner(): strOwner = pwd.getpwuid(os.getuid()).pw_name #strXML = XSDataString(strOwner).marshal() strXML = xsDataStringXML(strOwner) EDVerbose.screen("Returning serverOwner: " + strXML) return strXML @route('/session', method='POST') def authenticate(): # We don't care about authentication in this implementation, so simply return a random text return "scoobydoo" @route('/session', method='DELETE') def logout(): # Logout means the client side is no longer interested, so we take that as a cue to shut down server.stop() # Find more graceful way to send shutdown message bottle.abort(200, "WSGI server is shutting down...") exit() @route('/directory') def returnWorkingDirectory(): return os.getcwd(); def runXAFSProcessing(_dataPattern): if request.GET.get('nxs', '').strip(): #_dataPattern = request.GET.get('nxs', '').strip() dataPattern = '/'.join(_dataPattern.split('\\\\')) print dataPattern _strDatasetFileNames = [os.path.abspath(tmp) for tmp in filter(lambda tmp: os.path.isfile(tmp), glob.glob(dataPattern))] if (_strDatasetFileNames is not None): for _tmpName in _strDatasetFileNames: EDVerbose.screen("Reading input data from : %s" % _tmpName) else: return edApplicationXAFSBatchProcessing.usage() edApplicationXAFSBatchProcessing.setDatasetFileNames(_strDatasetFileNames) edApplicationXAFSBatchProcessing.execute() #return os.path.abspath(edApplicationXAFSBatchProcessing.getXAFSPlugin().getWorkingDirectory()) return os.path.join(os.path.abspath(edApplicationXAFSBatchProcessing.getXAFSPlugin().getWorkingDirectory()), 'results.nxs') else: return edApplicationXAFSBatchProcessing.usage() def runXAFSPipeline(_xmlXSDataInput, _iJobId): if _xmlXSDataInput: server.jobs[_iJobId] = ("Loading XNCF Pipeline ...", None) edPluginControlXAFSDataProcessing = EDFactoryPluginStatic.loadPlugin('EDPluginControlXAFSDataBatchProcessingv0_1') edPluginControlXAFSDataProcessing.setDataInput(_xmlXSDataInput) server.jobs[_iJobId] = ("Running XNCF Pipeline ...", None) edPluginControlXAFSDataProcessing.executeSynchronous() server.jobs[_iJobId] = ("Success", os.path.join(edPluginControlXAFSDataProcessing.getWorkingDirectory(), \ edPluginControlXAFSDataProcessing.returnResultsFilename())) else: server.jobs[_iJobId] = ("Failure", None) def runMXv1Pipeline(_strInput, _iJobId): if _strInput: # Take the parameters string, split off the title and run the bash script to generate the input char. XML listParams = shlex.split(_strInput) strComments = listParams[0] # the 1st item strShortComments = listParams[1] # the 2nd item strWorkingDir = listParams[2] # the 3rd item strHTMLResultDir = listParams[3] # the 4th item os.chdir(strWorkingDir) # Turn on verbose and DEBUG #EDVerbose.setVerboseOn() #EDVerbose.setVerboseDebugOn() EDVerbose.DEBUG("Got parameters ....") server.jobs[_iJobId] = ("Creating input XML file ...", None) strParamString = " ".join(listParams[4:]) # all but the first four items edPluginMxv1ParamsToXML = EDFactoryPluginStatic.loadPlugin('EDPluginMxv1ParamsToXMLv1_0') edPluginMxv1ParamsToXML.setDataInput(XSDataString(strParamString), "paramString") EDVerbose.DEBUG("About to run MX scripts ....") edPluginMxv1ParamsToXML.executeSynchronous() EDVerbose.DEBUG("Done running MX scripts ....") xsDataFile = edPluginMxv1ParamsToXML.getDataOutput() server.jobs[_iJobId] = ("Reading and parsing input XML file ...", None) # Read the XML and parse it into an object hierarchy strXMLFile = xsDataFile.getPath().getValue() f = open(strXMLFile, 'r') xml = f.read() f.close() xsDataInputCharacterisation = XSDataInputCharacterisation.parseString(xml) EDVerbose.DEBUG("Moving on ....") server.jobs[_iJobId] = ("Running characterisation ...", None) # Run the MXv1 application pipeline edApplicationMXv1Characterisation = EDApplicationMXv1Characterisation(_strPluginName="EDPluginControlInterfacev1_2", \ _strConfigurationFileName=strMxv1ConfigurationFilePath, \ _xsDataInputCharacterisation=xsDataInputCharacterisation, \ _strComments=strComments, \ _strShortComments=strShortComments) edApplicationMXv1Characterisation.execute() server.jobs[_iJobId] = ("Generating HTML ...", None) # Run the EDNA2HTML generator on the output from the MXv1 application edPluginExecOutputHTML = EDFactoryPluginStatic.loadPlugin('EDPluginExecOutputHTMLv1_0') edPluginExecOutputHTML.setDataInput(XSDataString(strComments), "title") strWorkingDir = os.path.join(strWorkingDir, edApplicationMXv1Characterisation.getWorkingDir()) edPluginExecOutputHTML.setDataInput(XSDataString(strWorkingDir), "workingDir") #strBaseDir = os.path.join(strHTMLResultDir, "edna") #edPluginExecOutputHTML.setDataInput(XSDataString(strBaseDir), "basename") edPluginExecOutputHTML.setDataInput(XSDataString(strHTMLResultDir), "basename") edPluginExecOutputHTML.executeSynchronous() strPathToHTMLFile = "" if (edPluginExecOutputHTML.hasDataOutput("htmlFile")): strPathToHTMLFile = edPluginExecOutputHTML.getDataOutput("htmlFile")[0].getPath().getValue() else: EDVerbose.ERROR("edna-mxv1-wsgi-server: edPluginExecOutputHTML has no dataOutput htmlFile!") if strPathToHTMLFile =="": EDVerbose.ERROR("edna-mxv1-wsgi-server: Returning empty string") server.jobs[_iJobId] = ("Failure", None) server.jobs[_iJobId] = ("Success", strPathToHTMLFile) else: server.jobs[_iJobId] = ("Failure", None) def extractMTZColumnHeaders(_strInput, _iJobId): server.jobs[_iJobId] = ("Extracting MTZ column headers ...", None) strResult = None import iotbx.mtz from XSDataCommon import XSDataFile #from XSDataCCP4DIMPLE import XSDataListOfStrings xsDataFile = XSDataFile().parseString(_strInput) xsDataString = xsDataFile.getPath() mtz_obj = iotbx.mtz.object(str(xsDataString.getValue())) strResult = "" for crystal in mtz_obj.crystals(): for dataset in crystal.datasets(): for column in dataset.columns(): strResult += "%s," % column.label() # edPluginExecDIMPLEGetMTZColumnHeadersv1_0 = EDFactoryPluginStatic.loadPlugin('EDPluginExecDIMPLEGetMTZColumnHeadersv1_0') # edPluginExecDIMPLEGetMTZColumnHeadersv1_0.setDataInput(_strInput) # EDVerbose.DEBUG("About to run MTZ column headers extraction script ....") # edPluginExecDIMPLEGetMTZColumnHeadersv1_0.executeSynchronous() # EDVerbose.DEBUG("Done running MTZ column headers extraction script ....") # xsDataString = edPluginExecDIMPLEGetMTZColumnHeadersv1_0.getDataOutput() # if not (xsDataString is None): # strResult = xsDataString.getValue() if len(strResult) > 0: server.jobs[_iJobId] = ("Success", strResult) else: server.jobs[_iJobId] = ("Failure", None) def runDimplePipeline(_strInput, _iJobId): from XSDataCCP4DIMPLE import CCP4DataInputControlPipelineCalcDiffMap from XSDataCCP4DIMPLE import CCP4DataResultControlPipelineCalcDiffMap from EDApplicationDimplev0 import EDApplicationDimplev0 strLogFileName = os.path.join(returnWorkingDirectory(), "wsgi-server.log") EDVerbose.setLogFileName(strLogFileName) if _strInput: server.jobs[_iJobId] = ("Parsing input to data model", None) xsInputData = CCP4DataInputControlPipelineCalcDiffMap().parseString(_strInput) server.jobs[_iJobId] =("Starting Dimple run", None) # Load configuration edApplicationDimplev0 = EDApplicationDimplev0(_strPluginName="EDPluginControlDIMPLEPipelineCalcDiffMapv10", \ _strConfigurationFileName=strDimpleConfigurationFilePath, \ _ccp4DataInputControlPipelineCalcDiffMap=xsInputData) server.jobs[_iJobId] =("Running Dimple (Please wait)", None) edApplicationDimplev0.execute() # CODE FOR GETTING THE R-FACTOR VALUES DIRECTLY # ccp4DataResultControlPipelineCalcDiffMap = edApplicationDimplev0.getDataOutput() # strResult = None # if ccp4DataResultControlPipelineCalcDiffMap is not None: # fInitR = ccp4DataResultControlPipelineCalcDiffMap.getInitialR().getValue() # fInitRFree = ccp4DataResultControlPipelineCalcDiffMap.getInitialRFree().getValue() # fFinalR = ccp4DataResultControlPipelineCalcDiffMap.getFinalR().getValue() # fFinalRFree = ccp4DataResultControlPipelineCalcDiffMap.getFinalRFree().getValue() # strResult = "%f,%f,%f,%f" % (fInitR, fInitRFree, fFinalR, fFinalRFree) # # else: # strResult = "" strResult = edApplicationDimplev0.getPathToLogFile() server.jobs[_iJobId] =("Success", strResult) else: server.jobs[_iJobId] = ("Failure", None) return def runFastDPEDNA(_strInput, _iJobId ): from XSDataExecSCXDAutomaticProcessing import XSDataSCXDFastDPInput from EDApplicationSCXDAPFastDPSingle import EDApplicationSCXDAPFastDPSingle strLogFileName = os.path.join(returnWorkingDirectory(), "wsgi-server.log") EDVerbose.setLogFileName(strLogFileName) if _strInput: server.jobs[_iJobId] = ("Parsing input to data model", None) xsInputData = XSDataSCXDFastDPInput().parseString(_strInput) server.jobs[_iJobId] =("Starting fast_dp run", None) edApplicationSCXDAPFastDP = EDApplicationSCXDAPFastDPSingle( _strPluginName="EDPluginControlFastDPSingleProcessingv0_1", _strConfigurationFileName=strScxdapConfigurationFilePath, _xsDataSCXDAPFastDPInput=xsInputData) server.jobs[_iJobId] =("Running fast_dp (Please wait)", None) edApplicationSCXDAPFastDP.setWorkingDir(xsInputData.getResultsLocation().getPath().getValue()) edApplicationSCXDAPFastDP.execute() strWorkingDir = edApplicationSCXDAPFastDP.getWorkingDir() server.jobs[_iJobId] =("Success", edApplicationSCXDAPFastDP._strFastDPLogPath) print "EDNA bottle server: fast_dp log file %s" % edApplicationSCXDAPFastDP._strFastDPLogPath else: server.jobs[_iJobId] = ("Failure", None) return def runXia2CommandLineEDNA(_strXML, _iJobId ): from EDApplicationSCXDAPXia2Commandline import EDApplicationSCXDAPXia2Commandline from XSDataExecSCXDAutomaticProcessing import XSDataInputSCXDSetup strPathToHTMLFile = None strStatus = "Failure" strLogFileName = os.path.join(returnWorkingDirectory(), "wsgi-server.log") EDVerbose.setLogFileName(strLogFileName) if _strXML: server.jobs[_iJobId]=("Parsing input Data",None) try: listInput = _strXML.split("{{}}") except Exception: server.jobs[_iJobId] = ("Failure", None) xsDataInput = XSDataInputSCXDSetup.parseString(listInput[0]) strXia2Mode = xsDataInput.getProcessingMode().getValue() server.jobs[_iJobId]=("Starting xia2 "+strXia2Mode, None) edApplicationXia2Commandline = EDApplicationSCXDAPXia2Commandline( _strPluginName="EDPluginControlXia2Commandlinev0_1", _strConfigurationFileName=strScxdapConfigurationFilePath, _xsDataSCXDAPXia2Commandline=xsDataInput, _xsDataFileResultsPath=listInput[1]) server.jobs[_iJobId]=("Running xia2 "+strXia2Mode+" (This will take a while)", None) edApplicationXia2Commandline.setWorkingDir(listInput[1]) edApplicationXia2Commandline.execute() #server.jobs[_iJobId] =("Success",edApplicationXia2Commandline._strXia2LogPath) print "EDNA bottle server: xia2 log file %s" % edApplicationXia2Commandline._strXia2LogPath server.jobs[_iJobId] = ("Generating HTML ...", None) # Run the XIA2HTML generator on the output from xia2 strDir = os.path.dirname(edApplicationXia2Commandline._strXia2LogPath) if os.path.exists(strDir): os.chdir(strDir) from subprocess import call call(["xia2html"]) if os.path.isfile("xia2.html"): strPathToHTMLFile = os.path.join(strDir, "xia2.html") strStatus = "Success" else: EDVerbose.DEBUG("File not found: "+ "xia2.html") else: EDVerbose.DEBUG("Directory not found: "+ strDir) server.jobs[_iJobId] = (strStatus, strPathToHTMLFile) return def handleXML(id, message): return \ ""\ " "+id+""\ " "+message+""\ "" def statusXML(id=None, message=None, result=None): strXML= "" if id != None: strXML +=" %s" % id if message != None: strXML +=" %s" % message if (result != None): strXML +=" %s" % result strXML +="" return strXML def xsDataStringXML(value=None): strXML="" if value != None: strXML += " %s" % value strXML += "" return strXML bottle.TEMPLATE_PATH.insert(0, strProgramDir) port = 8080 if len(sys.argv) > 1: port = sys.argv[1] else: port = os.environ["EDNA_PORT"] server = wsgiserver.CherryPyWSGIServer(('127.0.0.1', int(port)), bottle.default_app()) # Create a dict for jobs (id, message, result) which is accessible to all threads server.jobs = {} server.jobscount = 0 strLogFileName = os.path.join(returnWorkingDirectory(), "wsgi-server.log") EDVerbose.setLogFileName(strLogFileName) EDVerbose.screen("Starting server on port " + str(port)) server.start()