#!/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()