(!******************************************************
Xpress Executor Example Model
=============================
file blendxe.mos
````````````````
Demonstrates executing the 'blend3c' model using an Xpress Executor configured
to run a submodel extracted from a zipfile, which also contains the input
data.
It will then display the results.
Assumes that you have an Xpress Executor component already configured
with the runzippedsubmodel.bim model.
(c) 2015-2017 Fair Isaac Corporation
author: J. Farmer, Jul. 2017
*******************************************************!)
model "Blend XE"
uses "mmhttp", "mmssl", "mmsystem", "mmxml", "mmjobs"
parameters
! You should set the DMP_XE_REST_ENDPOINT, DMP_SOLUTION_CLIENT_ID and DMP_SOLUTION_SECRET parameters
! to point to the component you want to test.
! The REST endpoint of the Xpress Executor DMP component
! Obtain this by clicking "View Links" for the Xpress Executor component on the DMP UI
DMP_XE_REST_ENDPOINT=""
! The client ID of solution containing the Xpress Executor DMP component
! Obtain this through the DMP UI
DMP_SOLUTION_CLIENT_ID=""
! The secret of the solution containing the Xpress Executor DMP component
! Obtain this through the DMP UI
DMP_SOLUTION_SECRET=""
! The root DMP manager URL. This will be different depending on which instance of DMP you are using.
DMP_MANAGER_URL="https://manager-svc.prd-platform.ficoanalyticcloud.com"
! The remote model file
MODELFILE="../model/blend3c.mos"
! The input file for the remote model
INPUTFILE="../data/blend.csv"
! File into which to save the results
RESULTFILE="blendresults.csv"
end-parameters
declarations
! XML document containing final status of execution
executionStatus: xmldoc
! Array of parameters
modelParameterNames: set of string
modelParameters: dynamic array(modelParameterNames) of text
! Array of input files to include in zip (not including submodel bim)
! In this array, key is the local file, value is path of file in the zipfile
modelInputSourceFiles: set of string
modelInputFiles: dynamic array(modelInputSourceFiles) of text
! Token used to authorise access to the Xpress Executor component
bearerToken: text
end-declarations
forward procedure authoriseDmpComponent
forward procedure createInputZip(modelfile:text,modelInputFiles:array(modelInputSourceFiles) of text)
forward function startExecution(inputzipfile:text,modelParameters:array(modelParameterNames) of text) : xmldoc
forward procedure waitForCompletion(executionStatus:xmldoc)
forward procedure deleteExecution(executionStatus:xmldoc)
forward function getURLFromPath(path:text) : text
forward function getNodeValue(doc:xmldoc,path:text) : text
forward function getNodeValue(doc:xmldoc,path:string) : text
forward function fetchPathFromComponent(path:text,destfile:text) : integer
public declarations
! Temporary variables used in assembling HTTP requests
httpPostBody: text
httpResponseBody: text
inputBase64: text
runLog: text
end-declarations
! Configure Mosel to use TLS1.2 ciphers with HTTPS to allow us to talk to DMP
setparam("https_ciphers","TLSv1.2+HIGH:\!SSLv2:\!aNULL:\!eNULL:\!3DES:@STRENGTH")
! Log into DMP
writeln("Requesting authorization token from DMP")
authoriseDmpComponent
! Execute our model
writeln("Initiating model execution")
modelParameters("INPUTFILE"):="blend.csv" ! Read inputs from the file blend.csv
modelParameters("RESULTFILE"):="result" ! In Xpress Executor, we can only access model results from a file called "result"
modelInputFiles(INPUTFILE) := "blend.csv" ! Include blend.csv in the zipfile we upload
createInputZip(MODELFILE,modelInputFiles)
executionStatus := startExecution("input.zip",modelParameters)
! Model will be executing asynchronously; wait for it to complete.
writeln("Waiting for completion of execution")
waitForCompletion(executionStatus)
! Check execution was successful
writeln("Processing model results")
! In event of failure, echo the remote model status, exit code & run log to aid with troubleshooting
if getNodeValue(executionStatus,"/jsv/status")<>"OK" or getNodeValue(executionStatus,"/jsv/exitCode")<>"0" then
writeln("Execution failed!")
writeln("Execution status: ", getNodeValue(executionStatus,"/jsv/status"))
writeln("Execution exit code: ", getNodeValue(executionStatus,"/jsv/exitCode"))
writeln
writeln("Execution log:")
! Fetch the remote execution log as it may contain error messages from the model
if fetchPathFromComponent( getNodeValue(executionStatus,"/jsv/runLogPath"), "text:runLog" )<>200 then
writeln("Execution log not available")
end-if
writeln(runLog)
exit(1)
end-if
! Download results file
if fetchPathFromComponent( getNodeValue(executionStatus,"/jsv/resultPath"), RESULTFILE )<>200 then
writeln("Model results are not available!")
exit(1)
end-if
! Parse results and display to user
declarations
ORES=1..2
use: array(ORES) of real
end-declarations
initializations from "mmsheet.csv:"+RESULTFILE
use as "[A1:B2]"
end-initializations
writeln
writeln("Solution:")
forall(o in ORES) writeln(" use(" + o + "): ", use(o))
! Delete execution from component
deleteExecution( executionStatus )
! Use the client ID and secret from the solution to request a token that we can use to
! authorise access to the Xpress Executor component
procedure authoriseDmpComponent
declarations
payloadDoc: xmldoc
responseCode: integer
end-declarations
! Assemble payload to send to DMP
reset(payloadDoc)
rootElem := addnode(payloadDoc, 0, XML_ELT, "jsv")
setattr(payloadDoc, rootElem, "jst", "obj")
clientIdElem := addnode(payloadDoc, rootElem, XML_ELT, "clientId")
setattr(payloadDoc, clientIdElem, "jst", "str")
setvalue(payloadDoc, clientIdElem, DMP_SOLUTION_CLIENT_ID)
secretElem := addnode(payloadDoc, rootElem, XML_ELT, "secret")
setattr(payloadDoc, secretElem, "jst", "str")
setvalue(payloadDoc, secretElem, DMP_SOLUTION_SECRET)
! Post to server
jsonsave(payloadDoc, "text:httpPostBody")
reset(payloadDoc)
responseCode := httppost( DMP_MANAGER_URL + "/registration/rest/client/token", "text:httpPostBody", "text:httpResponseBody", "Content-Type: application/json" );
reset(httpPostBody)
! Check response
if responseCode<>200 then
writeln("ERROR: Failed to authorise with DMP (HTTP response: "+responseCode+")")
writeln(httpResponseBody)
exit(1)
end-if
! Store bearer token
bearerToken := httpResponseBody
reset(httpResponseBody)
end-procedure
! Build the input zipfile from the given files
procedure createInputZip(modelfile:text,modelInputFiles:array(modelInputSourceFiles) of text)
declarations
zipdir, workdir: string
end-declarations
! Create temporary directory
workdir := getparam("workdir")
zipdir := workdir + "/inputdir"
makedir(zipdir)
if getsysstat<>0 then
writeln("Failed to create ",zipdir)
exit(1)
end-if
! Compile or copy model into directory
if endswith(modelfile,".mos") then
if compile("s",string(modelfile),zipdir+"/submodel.bim")<>0 then
writeln("ERROR: Failed to compile ",modelfile)
exit(1)
end-if
else
fcopy(modelfile,zipdir+"/submodel.bim")
if getsysstat<>0 then
writeln("ERROR: Failed to copy ",modelfile," to ",zipdir,"/submodel.bim")
exit(1)
end-if
end-if
! Copy other input files into directory
forall( sourceFileName in modelInputSourceFiles | exists(modelInputFiles(sourceFileName)) ) do
fcopy( sourceFileName, zipdir+"/"+modelInputFiles(sourceFileName) )
if getsysstat<>0 then
writeln("ERROR: Failed to copy ",sourceFileName," to ",zipdir,"/",modelInputFiles(sourceFileName) )
exit(1)
end-if
end-do
! Ensure old zipfile if present
if getfstat("input.zip")<>0 then
removefiles("input.zip")
end-if
! Create zip
newzip(0, "input.zip", zipdir, [text("submodel.bim")]+ sum(sourceFileName in modelInputSourceFiles | exists(modelInputFiles(sourceFileName))) [modelInputFiles(sourceFileName)] )
if getsysstat<>0 then
writeln("ERROR: Failed to create zipfile" )
exit(1)
end-if
! Clean up
removefiles( SYS_RECURS, zipdir, "*" )
removedir(zipdir)
end-procedure
! Start execution of model, using the given input data files. Returns initial execution status
function startExecution(inputzipfile:text,modelParameters:array(modelParameterNames) of text) : xmldoc
declarations
payloadDoc: xmldoc
responseDoc: xmldoc
responseCode: integer
headers: text
paramstr: text
end-declarations
! Assemble payload to send to DMP
reset(payloadDoc)
rootElem := addnode(payloadDoc, 0, XML_ELT, "jsv")
setattr(payloadDoc, rootElem, "jst", "obj")
! Create parameters string
forall( paramName in modelParameterNames | exists(modelParameters(paramName)) ) do
setmodpar(paramstr, paramName, modelParameters(paramName))
end-do
! Set parameters in JSON doc
paramsElem := addnode(payloadDoc, rootElem, XML_ELT, "parameters")
setattr(payloadDoc, paramsElem, "jst", "obj")
paramElem := addnode(payloadDoc, paramsElem, XML_ELT, "jsv")
setattr(payloadDoc, paramElem, "name", "SUBMODEL_PARAMS")
setattr(payloadDoc, paramElem, "jst", "str")
setvalue(payloadDoc, paramElem, paramstr)
! Set input data
fcopy( inputzipfile, "mmssl.base64:text:inputBase64" )
if getsysstat<>0 then
writeln("Error encoding ",inputzipfile)
exit(1)
end-if
inputDataElem := addnode(payloadDoc, rootElem, XML_ELT, "inputBase64")
setattr(payloadDoc, inputDataElem, "jst", "str")
setvalue(payloadDoc, inputDataElem, inputBase64 )
reset(inputBase64)
! Assemble HTTP headers
headers += "Content-Type: application/json\n"
headers += "Accepts: application/json\n"
headers += "Authorization: Bearer "+bearerToken+"\n"
! Post to server
jsonsave(payloadDoc, "mmsystem.text:httpPostBody")
reset(payloadDoc)
responseCode := httppost( DMP_XE_REST_ENDPOINT, "text:httpPostBody", "text:httpResponseBody", headers );
reset(httpPostBody)
! Check response
if responseCode<>200 then
writeln("ERROR: Failed to start model execution (HTTP response: "+responseCode+")")
writeln(httpResponseBody)
exit(1)
end-if
! Parse response JSON
jsonload(responseDoc, "text:httpResponseBody", 0)
reset(httpResponseBody)
returned := responseDoc
end-function
! Return URL of given path on Xpress Executor component
function getURLFromPath(path:text) : text
declarations
url: text
end-declarations
! Strip the path off the endpoint URL
url := DMP_XE_REST_ENDPOINT
lastSlash := 0
forall (i in 1..3) do
nextSlash := findtext(url,"/",lastSlash+1)
if nextSlash=0 then
writeln("Failed to parse URL!")
exit(1)
end-if
lastSlash := nextSlash
end-do
deltext(url, lastSlash, getsize(url))
! Append given path
url += path
! And done
returned := url
end-function
! Return content of given XPATH expression in given document. Aborts model if was not found.
function getNodeValue(doc:xmldoc,path:string) : text
declarations
nodeId: integer
end-declarations
nodeId := getnode(doc,path)
if nodeId=-1 then
writeln("Failed to find node '",path,"' in document")
exit(1)
end-if
returned := getvalue(doc,nodeId)
end-function
function getNodeValue(doc:xmldoc,path:text) : text
returned := getNodeValue(doc,string(path))
end-function
! Fetch the given path from the Xpress Executor component and save content to the given destination file. Returns response code.
function fetchPathFromComponent(path:text,destfile:text) : integer
declarations
responseCode: integer
headers: text
url: text
end-declarations
reset(headers)
headers += "Accepts: application/json\n"
headers += "Authorization: Bearer "+bearerToken+"\n"
url := getURLFromPath( path )
responseCode := httpget( url, string(destfile), headers )
returned := responseCode
end-function
! Wait for model execution to complete; updates the supplied execution status document
procedure waitForCompletion(executionStatus: xmldoc)
declarations
responseCode: integer
end-declarations
! Loop while status indicates execution not completed
status := getNodeValue(executionStatus,"/jsv/status")
while (status="NOT_COMPLETED" or status="NOT_LOADED") do
! Wait for half a second
sleep(500)
! Refresh status
responseCode := fetchPathFromComponent( getNodeValue(executionStatus,"/jsv/statusPath"), "text:httpResponseBody" )
if responseCode<>200 then
writeln("ERROR: Encountered HTTP error code ",responseCode," when querying execution status")
writeln(httpResponseBody) ! Write out response body, in case it contains an error message.
exit(1)
end-if
jsonload(executionStatus, "text:httpResponseBody", 0)
reset(httpResponseBody)
status := getNodeValue(executionStatus,"/jsv/status")
end-do
end-procedure
! Delete the execution from the service
procedure deleteExecution(executionStatus: xmldoc)
declarations
responseCode: integer
headers: text
end-declarations
reset(headers)
headers += "Accepts: application/json\n"
headers += "Authorization: Bearer "+bearerToken+"\n"
responseCode := httpdel( getURLFromPath(getNodeValue(executionStatus,"/jsv/statusPath")), "null:", headers )
if responseCode<>200 then
writeln("Warning: Received HTTP response ",responseCode," when deleting execution")
end-if
end-procedure
end-model
|