Macros for SAS Application Developers
https://github.com/sasjs/core
Loading...
Searching...
No Matches
mv_jobwaitfor.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Takes a table of running jobs and waits for ANY/ALL of them to complete
4 @details Will poll `/jobs/{jobId}/state` at set intervals until ANY or ALL
5 jobs are completed. Completion is determined by reference to the returned
6 _state_, as per the following table:
7
8 | state | Wait? | Notes|
9 |-----------|-------|------|
10 | idle | yes | We assume processing will continue. Beware of idle sessions with no code submitted! |
11 | pending | yes | Job is preparing to run |
12 | running | yes | Job is running|
13 | canceled | no | Job was cancelled|
14 | completed | no | Job finished - does not mean it was successful. Check stateDetails|
15 | failed | no | Job failed to execute, could be a problem when calling the apis|
16
17
18 ## Example
19
20 First, compile the macros:
21
22 filename mc url
23 "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
24 %inc mc;
25
26 Next, create a job (in this case, as a web service):
27
28 filename ft15f001 temp;
29 parmcards4;
30 data ;
31 rand=ranuni(0)*1000000;
32 do x=1 to rand;
33 y=rand*x;
34 output;
35 end;
36 run;
37 ;;;;
38 %mv_createwebservice(path=/Public/temp,name=demo)
39
40 Then, execute the job,multiple times, and wait for them all to finish:
41
42 %mv_jobexecute(path=/Public/temp,name=demo,outds=work.ds1)
43 %mv_jobexecute(path=/Public/temp,name=demo,outds=work.ds2)
44 %mv_jobexecute(path=/Public/temp,name=demo,outds=work.ds3)
45 %mv_jobexecute(path=/Public/temp,name=demo,outds=work.ds4)
46
47 data work.jobs;
48 set work.ds1 work.ds2 work.ds3 work.ds4;
49 where method='GET' and rel='state';
50 run;
51
52 %mv_jobwaitfor(ALL,inds=work.jobs,outds=work.jobstates)
53
54 Delete the job:
55
56 %mv_deletejes(path=/Public/temp,name=demo)
57
58 @param [in] access_token_var= The global macro variable to contain the access
59 token
60 @param [in] grant_type= valid values:
61
62 - password
63 - authorization_code
64 - detect - will check if access_token exists, if not will use sas_services
65 if a SASStudioV session else authorization_code. Default option.
66 - sas_services - will use oauth_bearer=sas_services
67
68 @param [in] action=Either ALL (to wait for every job) or ANY (if one job
69 completes, processing will continue). Default=ALL.
70 @param [in] inds= The input dataset containing the list of job uris, in the
71 following format: `/jobExecution/jobs/&JOBID.` (a trailing `/state`
72 suffix, as returned by the mv_jobexecute state link, is also
73 accepted and will be stripped) and the corresponding job name.
74 The uri should be in a `uri` variable, and the job path/name
75 should be in a `_program` variable.
76 @param [in] raise_err=0 Set to 1 to raise SYSCC when a job does not complete
77 succcessfully
78 @param [in] mdebug= set to 1 to enable DEBUG messages
79 @param [out] outds= The output dataset containing the list of states by job
80 (default=work.mv_jobexecute)
81 @param [out] outref= A fileref to which the spawned job logs should be
82 appended.
83
84 @version VIYA V.03.04
85 @author Allan Bowe, source: https://github.com/sasjs/core
86
87 <h4> Dependencies </h4>
88 @li mp_abort.sas
89 @li mf_getplatform.sas
90 @li mf_getuniquefileref.sas
91 @li mf_getuniquelibref.sas
92 @li mf_existvar.sas
93 @li mf_nobs.sas
94 @li mv_getjoblog.sas
95
96**/
97
98%macro mv_jobwaitfor(action
99 ,access_token_var=ACCESS_TOKEN
100 ,grant_type=sas_services
101 ,inds=0
102 ,outds=work.mv_jobwaitfor
103 ,outref=0
104 ,raise_err=0
105 ,mdebug=0
106 );
107%local dbg;
108%if &mdebug=1 %then %do;
109 %put &sysmacroname entry vars:;
110 %put _local_;
111%end;
112%else %let dbg=*;
113
114%local oauth_bearer;
115%if &grant_type=detect %then %do;
116 %if %symexist(&access_token_var) %then %let grant_type=authorization_code;
117 %else %let grant_type=sas_services;
118%end;
119%if &grant_type=sas_services %then %do;
120 %let oauth_bearer=oauth_bearer=sas_services;
121 %let &access_token_var=;
122%end;
123
124%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
125 and &grant_type ne sas_services
126 )
127 ,mac=&sysmacroname
128 ,msg=%str(Invalid value for grant_type: &grant_type)
129)
130
131%mp_abort(iftrue=("&inds"="0")
132 ,mac=&sysmacroname
133 ,msg=%str(input dataset not provided)
134)
135%mp_abort(iftrue=(%mf_existvar(&inds,uri)=0)
136 ,mac=&sysmacroname
137 ,msg=%str(The URI variable was not found in the input dataset(&inds))
138)
139%mp_abort(iftrue=(%mf_existvar(&inds,_program)=0)
140 ,mac=&sysmacroname
141 ,msg=%str(The _PROGRAM variable was not found in the input dataset(&inds))
142)
143
144%if %mf_nobs(&inds)=0 %then %do;
145 %put NOTE: Zero observations in &inds, &sysmacroname will now exit;
146 %return;
147%end;
148
149options noquotelenmax;
150%local base_uri; /* location of rest apis */
151%let base_uri=%mf_getplatform(VIYARESTAPI);
152
153data _null_;
154 length jobparams $32767;
155 set &inds end=last;
156 /* strip any trailing /state suffix - the poll below GETs the plain
157 job uri (whose root object carries the state field), and
158 downstream consumers (mv_getjoblog, the outds) expect the plain
159 job uri. Do not truncate: job uris are not a fixed length (a
160 definition uri passed in error must fail loudly, not be silently
161 corrupted into a 404). */
162 if length(strip(uri))>6 and substr(strip(uri),length(strip(uri))-5)='/state'
163 then uri=substr(strip(uri),1,length(strip(uri))-6);
164 call symputx(cats('joburi',_n_),strip(uri),'l');
165 call symputx(cats('jobname',_n_),_program,'l');
166 call symputx(cats('jobparams',_n_),jobparams,'l');
167 if last then call symputx('uricnt',_n_,'l');
168run;
169
170%local runcnt;
171%if &action=ALL %then %let runcnt=&uricnt;
172%else %if &action=ANY %then %let runcnt=1;
173%else %let runcnt=&uricnt;
174
175%local fname0 ;
176%let fname0=%mf_getuniquefileref();
177
178data &outds;
179 format _program uri $128. state $32. stateDetails $32. timestamp datetime19.
180 jobparams $32767.;
181 call missing (of _all_);
182 stop;
183run;
184
185%local i;
186%do i=1 %to &uricnt;
187 %if "&&joburi&i" ne "0" %then %do;
188 proc http method='GET' out=&fname0 &oauth_bearer url="&base_uri/&&joburi&i";
189 headers "Accept"="application/json"
190 %if &grant_type=authorization_code %then %do;
191 "Authorization"="Bearer &&&access_token_var"
192 %end; ;
193 run;
194 %if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201
195 %then %do;
196 data _null_;infile &fname0;input;putlog _infile_;run;
197 %mp_abort(mac=&sysmacroname
198 ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
199 )
200 %end;
201
202 %let status=notset;
203
204 %local libref1;
205 %let libref1=%mf_getuniquelibref();
206 libname &libref1 json fileref=&fname0;
207
208 data _null_;
209 length state stateDetails $32;
210 set &libref1..root;
211 call symputx('status',state,'l');
212 call symputx('stateDetails',stateDetails,'l');
213 run;
214
215 libname &libref1 clear;
216
217 %if &status=completed or &status=failed or &status=canceled %then %do;
218 /* &&joburi&i is already the plain job uri (any /state suffix was
219 stripped on input) - use it as-is. */
220 proc sql;
221 insert into &outds set
222 _program="&&jobname&i",
223 uri="&&joburi&i",
224 state="&status",
225 stateDetails=symget("stateDetails"),
226 timestamp=datetime(),
227 jobparams=symget("jobparams&i");
228 /* fetch log */
229 %if %str(&outref) ne 0 %then %do;
230 %mv_getjoblog(uri=&&joburi&i,outref=&outref,mdebug=&mdebug)
231 %end;
232 %let joburi&i=0; /* do not re-check */
233 %end;
234 %else %if &status=idle or &status=pending or &status=running %then %do;
235 data _null_;
236 call sleep(1,1);
237 run;
238 %end;
239 %else %do;
240 %mp_abort(mac=&sysmacroname
241 ,msg=%str(status &status not expected!!)
242 )
243 %end;
244
245 %if (&raise_err) %then %do;
246 %if (&status = canceled or &status = failed or %length(&stateDetails)>0)
247 %then %do;
248 %if ("&stateDetails" = "%str(war)ning") %then %let SYSCC=4;
249 %else %let SYSCC=5;
250 %put %str(ERR)OR: Job &&jobname&i. did not complete. &stateDetails;
251 %return;
252 %end;
253 %end;
254
255 %end;
256 %if &i=&uricnt %then %do;
257 %local goback;
258 %let goback=0;
259 proc sql noprint;
260 select count(*) into:goback from &outds;
261 %if &goback lt &runcnt %then %let i=0;
262 %end;
263%end;
264
265%if &mdebug=1 %then %do;
266 %put &sysmacroname exit vars:;
267 %put _local_;
268%end;
269%else %do;
270 /* clear refs */
271 filename &fname0 clear;
272%end;
273%mend mv_jobwaitfor;