diff --git a/all.sas b/all.sas index 8751ea6..312b4b3 100644 --- a/all.sas +++ b/all.sas @@ -12396,6 +12396,161 @@ data _null_; run; filename &fname1 clear; filename &fname2 clear; +%mend; +/** + @file + @brief Extract the status from a running SAS Viya job + @details Extracts the status from a running job and writes it to a fileref. + An output dataset is created like this: + + | uri | state | timestamp | + |---------------------------------------------------------------|---------|--------------------| + | /jobExecution/jobs/5cebd840-2063-42c1-be0c-421ec3e1c175/state | running | 15JAN2021:12:35:08 | + + ## Example + + First, compile the macros: + + filename mc url + "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + Create a long running job (in this case, a web service): + + filename ft15f001 temp; + parmcards4; + data ; + rand=ranuni(0)*1000; + do x=1 to rand; + y=rand*4; + output; + end; + run; + data _null_; + call sleep(5,1); + run; + ;;;; + %mv_createwebservice(path=/Public/temp,name=demo) + + Execute it, grab the uri, and check status: + + %mv_jobexecute(path=/Public/temp + ,name=demo + ,outds=work.info + ) + + data _null_; + set work.info; + if method='GET' and rel='state'; + call symputx('uri',uri); + run; + + %mv_getjobstate(uri=&uri,outds=results) + + + @param [in] access_token_var= The global macro variable to contain the access token + @param [in] grant_type= valid values: + * password + * authorization_code + * detect - will check if access_token exists, if not will use sas_services if + a SASStudioV session else authorization_code. Default option. + * sas_services - will use oauth_bearer=sas_services + @param [in] uri= The uri of the running job for which to fetch the status, + in the format `/jobExecution/jobs/$UUID/state` (unquoted). + @param [out] outds= The output dataset in which to APPEND the status. Three + fields are appended: `CHECK_TM`, `URI` and `STATE`. If the dataset does not + exist, it is created. + + + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core + +

SAS Macros

+ @li mp_abort.sas + @li mf_getplatform.sas + @li mf_getuniquefileref.sas + +**/ + +%macro mv_getjobstate(uri=0,outds=work.mv_getjobstate + ,contextName=SAS Job Execution compute context + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ); +%local oauth_bearer; +%if &grant_type=detect %then %do; + %if %symexist(&access_token_var) %then %let grant_type=authorization_code; + %else %let grant_type=sas_services; +%end; +%if &grant_type=sas_services %then %do; + %let oauth_bearer=oauth_bearer=sas_services; + %let &access_token_var=; +%end; +%put &sysmacroname: grant_type=&grant_type; +%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password + and &grant_type ne sas_services + ) + ,mac=&sysmacroname + ,msg=%str(Invalid value for grant_type: &grant_type) +) + +/* validation in datastep for better character safety */ +%local errmsg errflg; +data _null_; + uri=symget('uri'); + if length(uri)<12 then do; + call symputx('errflg',1); + call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l'); + end; + if scan(uri,-1) ne 'state' or scan(uri,1) ne 'jobExecution' then do; + + call symputx('errflg',1); + call symputx('errmsg', + "URI should be in format /jobExecution/jobs/$$$$UUID$$$$/state" + !!" but is actually like: &uri",'l'); + end; +run; + +%mp_abort(iftrue=(&errflg=1) + ,mac=&sysmacroname + ,msg=%str(&errmsg) +) + +options noquotelenmax; +%local base_uri; /* location of rest apis */ +%let base_uri=%mf_getplatform(VIYARESTAPI); + +%local fname0; +%let fname0=%mf_getuniquefileref(); + +proc http method='GET' out=&fname0 &oauth_bearer url="&base_uri/&uri"; + headers "Accept"="text/plain" + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; ; +run; +%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then +%do; + data _null_;infile &fname0;input;putlog _infile_;run; + %mp_abort(mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) + ) +%end; + +data; + format uri $128. state $32. timestamp datetime19.; + infile &fname0; + uri="&uri"; + timestamp=datetime(); + input; + state=_infile_; +run; + +proc append base=&outds data=&syslast; +run; + +filename &fname0 clear; + %mend; /** @file mv_getrefreshtoken.sas @@ -12807,6 +12962,199 @@ filename &fname1 clear; libname &libref; %mend;/** + @file + @brief Execute a series of job flows + @details Very (very) simple flow manager. Jobs execute in sequential waves, + all previous waves must finish successfully. + + The input table is formed as per below. Each observation represents one job. + Each variable is converted into a macro variable with the same name. + The FLOW column provides the sequential ordering capability. + + + ## Example + + First, compile the macros: + + filename mc url + "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + Next, create some jobs (in this case, as web services): + + filename ft15f001 temp; + parmcards4; + data ; + rand=ranuni(0)*¯ovar1; + do x=1 to rand; + y=rand*¯ovar2; + output; + end; + run; + ;;;; + %mv_createwebservice(path=/Public/temp,name=demo1) + %mv_createwebservice(path=/Public/temp,name=demo2) + + Prepare an input table with 60 executions: + + data work.inputjobs; + do flow=1 to 3; + do job=1 to 10; + _program='/Public/temp/name/demo1'; + macrovar1=10*job; + macrovar2=4*job; + output; + _program='/Public/temp/name/demo2'; + macrovar1=40*job; + macrovar2=44*job; + output; + end; + end; + run; + + Trigger the flow + + %mv_jobflow(inds=work.inputjobs,outds=work.results,maxconcurrency=4) + + + @param [in] access_token_var= The global macro variable to contain the access token + @param [in] grant_type= valid values: + * password + * authorization_code + * detect - will check if access_token exists, if not will use sas_services if + a SASStudioV session else authorization_code. Default option. + * sas_services - will use oauth_bearer=sas_services + @param [in] inds= The input dataset containing a list of jobs and parameters + @param [in] maxconcurrency= The max number of parallel jobs to run + @param [out] outds= The output dataset containing the results + + @version VIYA V.03.05 + @author Allan Bowe, source: https://github.com/sasjs/core + +

SAS Macros

+ @li mp_abort.sas + @li mf_getplatform.sas + @li mf_getuniquefileref.sas + @li mv_getfoldermembers.sas + @li ml_json.sas + +**/ + +%macro mv_jobflow(outref=0,outfile=0 + ,name=0,path=0 + ,contextName=SAS Job Execution compute context + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ); +%local oauth_bearer; +%if &grant_type=detect %then %do; + %if %symexist(&access_token_var) %then %let grant_type=authorization_code; + %else %let grant_type=sas_services; +%end; +%if &grant_type=sas_services %then %do; + %let oauth_bearer=oauth_bearer=sas_services; + %let &access_token_var=; +%end; +%put &sysmacroname: grant_type=&grant_type; +%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password + and &grant_type ne sas_services + ) + ,mac=&sysmacroname + ,msg=%str(Invalid value for grant_type: &grant_type) +) +%mp_abort(iftrue=("&path"="0") + ,mac=&sysmacroname + ,msg=%str(Job Path not provided) +) +%mp_abort(iftrue=("&name"="0") + ,mac=&sysmacroname + ,msg=%str(Job Name not provided) +) +%mp_abort(iftrue=("&outfile"="0" and "&outref"="0") + ,mac=&sysmacroname + ,msg=%str(Output destination (file or fileref) must be provided) +) +options noquotelenmax; +%local base_uri; /* location of rest apis */ +%let base_uri=%mf_getplatform(VIYARESTAPI); +data;run; +%local foldermembers; +%let foldermembers=&syslast; +%mv_getfoldermembers(root=&path + ,access_token_var=&access_token_var + ,grant_type=&grant_type + ,outds=&foldermembers +) +%local joburi; +%let joburi=0; +data _null_; + set &foldermembers; + if name="&name" and uri=:'/jobDefinitions/definitions' + then call symputx('joburi',uri); +run; +%mp_abort(iftrue=("&joburi"="0") + ,mac=&sysmacroname + ,msg=%str(Job &path/&name not found) +) + +/* prepare request*/ +%local fname1; +%let fname1=%mf_getuniquefileref(); +proc http method='GET' out=&fname1 &oauth_bearer + url="&base_uri&joburi"; + headers "Accept"="application/vnd.sas.job.definition+json" + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + ; +run; +%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then +%do; + data _null_;infile &fname1;input;putlog _infile_;run; + %mp_abort(mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) + ) +%end; +%local fname2 fname3 fpath1 fpath2 fpath3; +%let fname2=%mf_getuniquefileref(); +%let fname3=%mf_getuniquefileref(); +%let fpath1=%sysfunc(pathname(&fname1)); +%let fpath2=%sysfunc(pathname(&fname2)); +%let fpath3=%sysfunc(pathname(&fname2)); + +/* compile the lua JSON module */ +%ml_json() +/* read using LUA - this allows the code to be of any length */ +data _null_; + file "&fpath3..lua"; + put ' + infile = io.open (sas.symget("fpath1"), "r") + outfile = io.open (sas.symget("fpath2"), "w") + io.input(infile) + local resp=json.decode(io.read()) + local job=resp["code"] + outfile:write(job) + io.close(infile) + io.close(outfile) + '; +run; +%inc "&fpath3..lua"; +/* export to desired destination */ +data _null_; + %if &outref=0 %then %do; + file "&outfile" lrecl=32767; + %end; + %else %do; + file &outref; + %end; + infile &fname2; + input; + put _infile_; +run; +filename &fname1 clear; +filename &fname2 clear; +%mend; +/** @file @brief Takes a dataset of running jobs and waits for them to complete @details Will poll `/jobs/{jobId}/state` at set intervals until they are all diff --git a/viya/mv_getjobstate.sas b/viya/mv_getjobstate.sas new file mode 100644 index 0000000..f143709 --- /dev/null +++ b/viya/mv_getjobstate.sas @@ -0,0 +1,155 @@ +/** + @file + @brief Extract the status from a running SAS Viya job + @details Extracts the status from a running job and writes it to a fileref. + An output dataset is created like this: + + | uri | state | timestamp | + |---------------------------------------------------------------|---------|--------------------| + | /jobExecution/jobs/5cebd840-2063-42c1-be0c-421ec3e1c175/state | running | 15JAN2021:12:35:08 | + + ## Example + + First, compile the macros: + + filename mc url + "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + Create a long running job (in this case, a web service): + + filename ft15f001 temp; + parmcards4; + data ; + rand=ranuni(0)*1000; + do x=1 to rand; + y=rand*4; + output; + end; + run; + data _null_; + call sleep(5,1); + run; + ;;;; + %mv_createwebservice(path=/Public/temp,name=demo) + + Execute it, grab the uri, and check status: + + %mv_jobexecute(path=/Public/temp + ,name=demo + ,outds=work.info + ) + + data _null_; + set work.info; + if method='GET' and rel='state'; + call symputx('uri',uri); + run; + + %mv_getjobstate(uri=&uri,outds=results) + + + @param [in] access_token_var= The global macro variable to contain the access token + @param [in] grant_type= valid values: + * password + * authorization_code + * detect - will check if access_token exists, if not will use sas_services if + a SASStudioV session else authorization_code. Default option. + * sas_services - will use oauth_bearer=sas_services + @param [in] uri= The uri of the running job for which to fetch the status, + in the format `/jobExecution/jobs/$UUID/state` (unquoted). + @param [out] outds= The output dataset in which to APPEND the status. Three + fields are appended: `CHECK_TM`, `URI` and `STATE`. If the dataset does not + exist, it is created. + + + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core + +

SAS Macros

+ @li mp_abort.sas + @li mf_getplatform.sas + @li mf_getuniquefileref.sas + +**/ + +%macro mv_getjobstate(uri=0,outds=work.mv_getjobstate + ,contextName=SAS Job Execution compute context + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ); +%local oauth_bearer; +%if &grant_type=detect %then %do; + %if %symexist(&access_token_var) %then %let grant_type=authorization_code; + %else %let grant_type=sas_services; +%end; +%if &grant_type=sas_services %then %do; + %let oauth_bearer=oauth_bearer=sas_services; + %let &access_token_var=; +%end; +%put &sysmacroname: grant_type=&grant_type; +%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password + and &grant_type ne sas_services + ) + ,mac=&sysmacroname + ,msg=%str(Invalid value for grant_type: &grant_type) +) + +/* validation in datastep for better character safety */ +%local errmsg errflg; +data _null_; + uri=symget('uri'); + if length(uri)<12 then do; + call symputx('errflg',1); + call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l'); + end; + if scan(uri,-1) ne 'state' or scan(uri,1) ne 'jobExecution' then do; + + call symputx('errflg',1); + call symputx('errmsg', + "URI should be in format /jobExecution/jobs/$$$$UUID$$$$/state" + !!" but is actually like: &uri",'l'); + end; +run; + +%mp_abort(iftrue=(&errflg=1) + ,mac=&sysmacroname + ,msg=%str(&errmsg) +) + +options noquotelenmax; +%local base_uri; /* location of rest apis */ +%let base_uri=%mf_getplatform(VIYARESTAPI); + +%local fname0; +%let fname0=%mf_getuniquefileref(); + +proc http method='GET' out=&fname0 &oauth_bearer url="&base_uri/&uri"; + headers "Accept"="text/plain" + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; ; +run; +%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then +%do; + data _null_;infile &fname0;input;putlog _infile_;run; + %mp_abort(mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) + ) +%end; + +data; + format uri $128. state $32. timestamp datetime19.; + infile &fname0; + uri="&uri"; + timestamp=datetime(); + input; + state=_infile_; +run; + +proc append base=&outds data=&syslast; +run; + +filename &fname0 clear; + +%mend;