1
|
|
|
<?php |
2
|
|
|
// This file is part of BOINC. |
3
|
|
|
// http://boinc.berkeley.edu |
4
|
|
|
// Copyright (C) 2013 University of California |
5
|
|
|
// |
6
|
|
|
// BOINC is free software; you can redistribute it and/or modify it |
7
|
|
|
// under the terms of the GNU Lesser General Public License |
8
|
|
|
// as published by the Free Software Foundation, |
9
|
|
|
// either version 3 of the License, or (at your option) any later version. |
10
|
|
|
// |
11
|
|
|
// BOINC is distributed in the hope that it will be useful, |
12
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
14
|
|
|
// See the GNU Lesser General Public License for more details. |
15
|
|
|
// |
16
|
|
|
// You should have received a copy of the GNU Lesser General Public License |
17
|
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>. |
18
|
|
|
|
19
|
|
|
// This file contains a PHP binding of a web-service interface |
20
|
|
|
// for submitting jobs to a BOINC server. |
21
|
|
|
// |
22
|
|
|
// Functions: |
23
|
|
|
// boinc_abort_batch(): abort a batch |
24
|
|
|
// boinc_estimate_batch(); estimate completion time of a batch |
25
|
|
|
// boinc_get_output_file(): get the URL for an output file |
26
|
|
|
// boinc_get_output_files(): get the URL for zipped batch output |
27
|
|
|
// boinc_query_batch(): get details of a batch |
28
|
|
|
// boinc_query_batches(): get list of batches |
29
|
|
|
// boinc_query_job(): get details of a job |
30
|
|
|
// boinc_retire_batch(): retire a batch; delete output files |
31
|
|
|
// boinc_submit_batch(): submit a batch |
32
|
|
|
// |
33
|
|
|
// boinc_set_timeout($x): set RPC timeout to X seconds |
34
|
|
|
|
35
|
|
|
//// Implementation stuff follows |
36
|
|
|
|
37
|
|
|
function req_to_xml($req, $op) { |
38
|
|
|
if (!isset($req->batch_name)) { |
39
|
|
|
$req->batch_name = "batch_".time(); |
40
|
|
|
} |
41
|
|
|
$x = "<$op> |
42
|
|
|
<authenticator>$req->authenticator</authenticator> |
43
|
|
|
<batch> |
44
|
|
|
<app_name>$req->app_name</app_name> |
45
|
|
|
<batch_name>$req->batch_name</batch_name> |
46
|
|
|
"; |
47
|
|
|
if ((isset($req->output_template_filename)) && ($req->output_template_filename)) { |
48
|
|
|
$x .= " <output_template_filename>$req->output_template_filename</output_template_filename> |
49
|
|
|
"; |
50
|
|
|
} |
51
|
|
|
if ((isset($req->input_template_filename)) && ($req->input_template_filename)) { |
52
|
|
|
$x .= " <input_template_filename>$req->input_template_filename</input_template_filename> |
53
|
|
|
"; |
54
|
|
|
} |
55
|
|
|
if ((isset($req->app_version_num)) && ($req->app_version_num)) { |
56
|
|
|
$x .= " <app_version_num>$req->app_version_num</app_version_num> |
57
|
|
|
"; |
58
|
|
|
} |
59
|
|
|
foreach ($req->jobs as $job) { |
60
|
|
|
$x .= " <job> |
61
|
|
|
"; |
62
|
|
|
if (!empty($job->name)) { |
63
|
|
|
$x .= " <name>$job->name</name> |
64
|
|
|
"; |
65
|
|
|
} |
66
|
|
|
if (!empty($job->rsc_fpops_est)) { |
67
|
|
|
$x .= " <rsc_fpops_est>$job->rsc_fpops_est</rsc_fpops_est> |
68
|
|
|
"; |
69
|
|
|
} |
70
|
|
|
if (!empty($job->command_line)) { |
71
|
|
|
$x .= " <command_line>$job->command_line</command_line> |
72
|
|
|
"; |
73
|
|
|
} |
74
|
|
|
if (!empty($job->target_team)) { |
75
|
|
|
$x .= " <target_team>$job->target_team</target_team> |
76
|
|
|
"; |
77
|
|
|
} elseif (!empty($job->target_user)) { |
78
|
|
|
$x .= " <target_user>$job->target_user</target_user> |
79
|
|
|
"; |
80
|
|
|
} elseif (!empty($job->target_host)) { |
81
|
|
|
$x .= " <target_host>$job->target_host</target_host> |
82
|
|
|
"; |
83
|
|
|
} |
84
|
|
|
foreach ($job->input_files as $file) { |
85
|
|
|
$x .= " <input_file>\n"; |
86
|
|
|
$x .= " <mode>$file->mode</mode>\n"; |
87
|
|
|
if ($file->mode == "remote") { |
88
|
|
|
$x .= " <url>$file->url</url>\n"; |
89
|
|
|
$x .= " <nbytes>$file->nbytes</nbytes>\n"; |
90
|
|
|
$x .= " <md5>$file->md5</md5>\n"; |
91
|
|
|
} else { |
92
|
|
|
$x .= " <source>$file->source</source>\n"; |
93
|
|
|
} |
94
|
|
|
$x .= " </input_file>\n"; |
95
|
|
|
} |
96
|
|
|
if (!empty($job->input_template)) { |
97
|
|
|
$x .= " $job->input_template\n"; |
98
|
|
|
} |
99
|
|
|
if (!empty($job->output_template)) { |
100
|
|
|
$x .= " $job->output_template\n"; |
101
|
|
|
} |
102
|
|
|
$x .= " </job> |
103
|
|
|
"; |
104
|
|
|
} |
105
|
|
|
$x .= " </batch> |
106
|
|
|
</$op> |
107
|
|
|
"; |
108
|
|
|
return $x; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
function validate_request($req) { |
112
|
|
|
if (!is_object($req)) return "req is not an object"; |
113
|
|
|
if (!array_key_exists('project', $req)) return "missing req->project"; |
|
|
|
|
114
|
|
|
if (!array_key_exists('authenticator', $req)) return "missing req->authenticator"; |
115
|
|
|
if (!array_key_exists('app_name', $req)) return "missing req->app_name"; |
116
|
|
|
if (!array_key_exists('jobs', $req)) return "missing req->jobs"; |
117
|
|
|
if (!is_array($req->jobs)) return "req->jobs is not an array"; |
118
|
|
|
foreach ($req->jobs as $job) { |
|
|
|
|
119
|
|
|
// other checks |
120
|
|
|
} |
121
|
|
|
return null; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
$rpc_timeout = 0; |
125
|
|
|
|
126
|
|
|
function do_http_op($req, $xml, $op) { |
127
|
|
|
global $rpc_timeout; |
128
|
|
|
|
129
|
|
|
$ch = curl_init("$req->project/submit_rpc_handler.php"); |
130
|
|
|
curl_setopt($ch, CURLOPT_POST, 1); |
|
|
|
|
131
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
132
|
|
|
if ($rpc_timeout) { |
133
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, $rpc_timeout); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
// see if we need to send any files |
137
|
|
|
// |
138
|
|
|
$nfiles = 0; |
139
|
|
|
$post = array(); |
140
|
|
|
$post["request"] = $xml; |
141
|
|
|
$cwd = getcwd(); |
142
|
|
|
if ($op == "submit_batch") { |
143
|
|
|
foreach ($req->jobs as $job) { |
144
|
|
|
foreach ($job->input_files as $file) { |
145
|
|
|
if ($file->mode == "inline") { |
146
|
|
|
$path = realpath("$cwd/$file->path"); |
147
|
|
|
$post["file$nfiles"] = $path; |
148
|
|
|
$nfiles++; |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
} |
153
|
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $post); |
154
|
|
|
$reply = curl_exec($ch); |
|
|
|
|
155
|
|
|
curl_close($ch); |
|
|
|
|
156
|
|
|
if (!$reply) return array(null, "HTTP error"); |
157
|
|
|
$r = @simplexml_load_string($reply); |
|
|
|
|
158
|
|
|
if (!$r) { |
159
|
|
|
return array(null, "Can't parse reply XML:\n$reply"); |
160
|
|
|
} |
161
|
|
|
$e = (string)$r->error_msg; |
162
|
|
|
if ($e) { |
163
|
|
|
return array(null, "BOINC server: $e"); |
164
|
|
|
} else { |
165
|
|
|
return array($r, null); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
function do_batch_op($req, $op) { |
170
|
|
|
$retval = validate_request($req); |
171
|
|
|
if ($retval) return array(null, $retval); |
172
|
|
|
$xml = req_to_xml($req, $op); |
173
|
|
|
return do_http_op($req, $xml, $op); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
function batch_xml_to_object($batch) { |
177
|
|
|
$b = new StdClass; |
178
|
|
|
$b->id = (int)($batch->id); |
179
|
|
|
$b->create_time = (double)($batch->create_time); |
180
|
|
|
$b->est_completion_time = (double)($batch->est_completion_time); |
181
|
|
|
$b->njobs = (int)($batch->njobs); |
182
|
|
|
$b->fraction_done = (double) $batch->fraction_done; |
183
|
|
|
$b->nerror_jobs = (int)($batch->nerror_jobs); |
184
|
|
|
$b->state = (int)($batch->state); |
185
|
|
|
$b->completion_time = (double)($batch->completion_time); |
186
|
|
|
$b->credit_estimate = (double)($batch->credit_estimate); |
187
|
|
|
$b->credit_canonical = (double)($batch->credit_canonical); |
188
|
|
|
$b->name = (string)($batch->name); |
189
|
|
|
$b->app_name = (string)($batch->app_name); |
190
|
|
|
$b->total_cpu_time = (double)($batch->total_cpu_time); |
191
|
|
|
return $b; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
// if RPC had a fatal error, return the message |
195
|
|
|
// |
196
|
|
|
function get_error($reply, $outer_tag) { |
197
|
|
|
$name = $reply->getName(); |
198
|
|
|
if ($name != $outer_tag) { |
199
|
|
|
return "Bad reply outer tag"; |
200
|
|
|
} |
201
|
|
|
foreach ($reply->error as $error) { |
202
|
|
|
if (isset($error->error_num)) { |
203
|
|
|
return (string)$error->error_msg; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
return null; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
//// API functions follow |
210
|
|
|
|
211
|
|
|
function boinc_set_timeout($x) { |
212
|
|
|
global $rpc_timeout; |
213
|
|
|
$rpc_timeout = $x; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
function boinc_ping($req) { |
217
|
|
|
$req_xml = "<ping> |
218
|
|
|
<authenticator>$req->authenticator</authenticator> |
219
|
|
|
</ping> |
220
|
|
|
"; |
221
|
|
|
list($reply, $errmsg) = do_http_op($req, $req_xml, ""); |
222
|
|
|
if ($errmsg) return array(null, $errmsg); |
223
|
|
|
return array($reply, null); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
function boinc_estimate_batch($req) { |
227
|
|
|
list($reply, $errmsg) = do_batch_op($req, "estimate_batch"); |
228
|
|
|
if ($errmsg) return array(0, $errmsg); |
229
|
|
|
if ($x = get_error($reply, "estimate_batch")) { |
230
|
|
|
return array(null, $x); |
231
|
|
|
} |
232
|
|
|
return array((string)$reply->seconds, null); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
function boinc_submit_batch($req) { |
236
|
|
|
list($reply, $errmsg) = do_batch_op($req, "submit_batch"); |
237
|
|
|
if ($errmsg) return array(0, $errmsg); |
238
|
|
|
if ($x = get_error($reply, "submit_batch")) { |
239
|
|
|
return array(null, $x); |
240
|
|
|
} |
241
|
|
|
return array((int)$reply->batch_id, null); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
function boinc_query_batches($req) { |
245
|
|
|
$req_xml = "<query_batches> |
246
|
|
|
<authenticator>$req->authenticator</authenticator> |
247
|
|
|
"; |
248
|
|
|
if (!empty($req->get_cpu_time)) { |
249
|
|
|
$req_xml .= " <get_cpu_time>1</get_cpu_time>\n"; |
250
|
|
|
} |
251
|
|
|
$req_xml .= "</query_batches>\n"; |
252
|
|
|
list($reply, $errmsg) = do_http_op($req, $req_xml, ""); |
253
|
|
|
if ($errmsg) return array(null, $errmsg); |
254
|
|
|
if ($x = get_error($reply, "query_batches")) { |
255
|
|
|
return array(null, $x); |
256
|
|
|
} |
257
|
|
|
$batches = array(); |
258
|
|
|
foreach ($reply->batch as $batch) { |
259
|
|
|
$b = batch_xml_to_object($batch); |
260
|
|
|
$batches[] = $b; |
261
|
|
|
} |
262
|
|
|
return array($batches, null); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
function boinc_query_batch($req) { |
266
|
|
|
$req_xml = "<query_batch> |
267
|
|
|
<authenticator>$req->authenticator</authenticator> |
268
|
|
|
<batch_id>$req->batch_id</batch_id> |
269
|
|
|
"; |
270
|
|
|
if (!empty($req->get_cpu_time)) { |
271
|
|
|
$req_xml .= " <get_cpu_time>1</get_cpu_time>\n"; |
272
|
|
|
} |
273
|
|
|
if (!empty($req->get_job_details)) { |
274
|
|
|
$req_xml .= " <get_job_details>1</get_job_details>\n"; |
275
|
|
|
} |
276
|
|
|
$req_xml .= "</query_batch>\n"; |
277
|
|
|
list($reply, $errmsg) = do_http_op($req, $req_xml, ""); |
278
|
|
|
if ($errmsg) return array(null, $errmsg); |
279
|
|
|
if ($x = get_error($reply, "query_batch")) { |
280
|
|
|
return array(null, $x); |
281
|
|
|
} |
282
|
|
|
$jobs = array(); |
283
|
|
|
foreach ($reply->job as $job) { |
284
|
|
|
$j = new StdClass; |
285
|
|
|
$j->id = (int)($job->id); |
286
|
|
|
$j->canonical_instance_id = (int)($job->canonical_instance_id); |
287
|
|
|
$jobs[] = $j; |
288
|
|
|
} |
289
|
|
|
$r = batch_xml_to_object($reply); |
290
|
|
|
$r->jobs = $jobs; |
291
|
|
|
return array($r, null); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
function boinc_query_job($req) { |
295
|
|
|
$req_xml = "<query_job> |
296
|
|
|
<authenticator>$req->authenticator</authenticator> |
297
|
|
|
<job_id>$req->job_id</job_id> |
298
|
|
|
</query_job> |
299
|
|
|
"; |
300
|
|
|
list($reply, $errmsg) = do_http_op($req, $req_xml, ""); |
301
|
|
|
if ($errmsg) return array(null, $errmsg); |
302
|
|
|
if ($x = get_error($reply, "query_job")) { |
303
|
|
|
return array(null, $x); |
304
|
|
|
} |
305
|
|
|
$instances = array(); |
306
|
|
|
foreach ($reply->instance as $instance) { |
307
|
|
|
$i = new StdClass; |
308
|
|
|
$i->name = (string)($instance->name); |
309
|
|
|
$i->id = (int)($instance->id); |
310
|
|
|
$i->state = (string)($instance->state); |
311
|
|
|
$i->outfiles = array(); |
312
|
|
|
foreach ($instance->outfile as $outfile) { |
313
|
|
|
$f = new StdClass; |
314
|
|
|
$f->size = (double)$outfile->size; |
315
|
|
|
$i->outfiles[] = $f; |
316
|
|
|
} |
317
|
|
|
$instances[] = $i; |
318
|
|
|
} |
319
|
|
|
$r = new StdClass; |
320
|
|
|
$r->instances = $instances; |
321
|
|
|
return array($r, null); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
function boinc_abort_batch($req) { |
325
|
|
|
$req_xml = "<abort_batch> |
326
|
|
|
<authenticator>$req->authenticator</authenticator> |
327
|
|
|
<batch_id>$req->batch_id</batch_id> |
328
|
|
|
</abort_batch> |
329
|
|
|
"; |
330
|
|
|
list($reply, $errmsg) = do_http_op($req, $req_xml, ""); |
331
|
|
|
if ($errmsg) return $errmsg; |
332
|
|
|
if ($x = get_error($reply, "abort_batch")) { |
333
|
|
|
return array(null, $x); |
334
|
|
|
} |
335
|
|
|
return array(true, null); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
function boinc_get_output_file($req) { |
339
|
|
|
$auth_str = md5($req->authenticator.$req->instance_name); |
340
|
|
|
$name = $req->instance_name; |
341
|
|
|
$file_num = $req->file_num; |
342
|
|
|
return $req->project."/get_output.php?cmd=result_file&result_name=$name&file_num=$file_num&auth_str=$auth_str"; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
function boinc_get_output_files($req) { |
346
|
|
|
$auth_str = md5($req->authenticator.$req->batch_id); |
347
|
|
|
$batch_id = $req->batch_id; |
348
|
|
|
return $req->project."/get_output.php?cmd=batch_files&batch_id=$batch_id&auth_str=$auth_str"; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
function boinc_retire_batch($req) { |
352
|
|
|
$req_xml = "<retire_batch> |
353
|
|
|
<authenticator>$req->authenticator</authenticator> |
354
|
|
|
<batch_id>$req->batch_id</batch_id> |
355
|
|
|
</retire_batch> |
356
|
|
|
"; |
357
|
|
|
list($reply, $errmsg) = do_http_op($req, $req_xml, ""); |
358
|
|
|
if ($errmsg) return $errmsg; |
359
|
|
|
if ($x = get_error($reply, "retire_batch")) { |
360
|
|
|
return array(null, $x); |
361
|
|
|
} |
362
|
|
|
return array(true, null); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
//// example usage follows |
366
|
|
|
if (0) { |
367
|
|
|
$req = new StdClass; |
368
|
|
|
$req->project = "http://isaac.ssl.berkeley.edu/test/"; |
369
|
|
|
$req->authenticator = trim(file_get_contents("test_auth")); |
370
|
|
|
$req->app_name = "uppercase"; |
371
|
|
|
$req->batch_name = "batch_name_12"; |
372
|
|
|
$req->app_version_num = 710; |
373
|
|
|
$req->jobs = array(); |
374
|
|
|
|
375
|
|
|
|
376
|
|
|
$f = new StdClass; |
377
|
|
|
$f->mode = "remote"; |
378
|
|
|
$f->url = "http://isaac.ssl.berkeley.edu/validate_logic.txt"; |
379
|
|
|
$f->md5 = "eec5a142cea5202c9ab2e4575a8aaaa7"; |
380
|
|
|
$f->nbytes = 4250; |
381
|
|
|
|
382
|
|
|
if (0) { |
383
|
|
|
$f = new StdClass; |
384
|
|
|
$f->mode = "local"; |
385
|
|
|
$f->source = "foobar"; |
386
|
|
|
//$job->input_files[] = $f; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
$it = " |
390
|
|
|
<input_template> |
391
|
|
|
<file_info> |
392
|
|
|
</file_info> |
393
|
|
|
<workunit> |
394
|
|
|
<file_ref> |
395
|
|
|
<open_name>in</open_name> |
396
|
|
|
</file_ref> |
397
|
|
|
<target_nresults>1</target_nresults> |
398
|
|
|
<min_quorum>1</min_quorum> |
399
|
|
|
<rsc_fpops_est> 60e9 </rsc_fpops_est> |
400
|
|
|
<rsc_fpops_bound> 60e12 </rsc_fpops_bound> |
401
|
|
|
<rsc_disk_bound>2e6</rsc_disk_bound> |
402
|
|
|
<rsc_memory_bound>1e6</rsc_memory_bound> |
403
|
|
|
<delay_bound>3600</delay_bound> |
404
|
|
|
<credit>1</credit> |
405
|
|
|
</workunit> |
406
|
|
|
</input_template> |
407
|
|
|
"; |
408
|
|
|
$ot = " |
409
|
|
|
<output_template> |
410
|
|
|
<file_info> |
411
|
|
|
<name><OUTFILE_0/></name> |
412
|
|
|
<generated_locally/> |
413
|
|
|
<upload_when_present/> |
414
|
|
|
<max_nbytes>6000000</max_nbytes> |
415
|
|
|
<url><UPLOAD_URL/></url> |
416
|
|
|
</file_info> |
417
|
|
|
<result> |
418
|
|
|
<file_ref> |
419
|
|
|
<file_name><OUTFILE_0/></file_name> |
420
|
|
|
<open_name>out</open_name> |
421
|
|
|
</file_ref> |
422
|
|
|
</result> |
423
|
|
|
</output_template> |
424
|
|
|
"; |
425
|
|
|
for ($i=0; $i<2; $i++) { |
426
|
|
|
$job = new StdClass; |
427
|
|
|
$job->input_files = array(); |
428
|
|
|
$job->input_files[] = $f; |
429
|
|
|
$job->name = $req->batch_name."_$i"; |
430
|
|
|
//$job->rsc_fpops_est = $i*1e9; |
431
|
|
|
$job->command_line = "--t $i"; |
432
|
|
|
$job->input_template = $it; |
433
|
|
|
$job->output_template = $ot; |
434
|
|
|
$req->jobs[] = $job; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
if (0) { |
438
|
|
|
list($e, $errmsg) = boinc_estimate_batch($req); |
439
|
|
|
if ($errmsg) { |
440
|
|
|
echo "Error from server: $errmsg\n"; |
441
|
|
|
} else { |
442
|
|
|
echo "Batch completion estimate: $e seconds\n"; |
443
|
|
|
} |
444
|
|
|
} else { |
445
|
|
|
list($id, $errmsg) = boinc_submit_batch($req); |
446
|
|
|
if ($errmsg) { |
447
|
|
|
echo "Error from server: $errmsg\n"; |
448
|
|
|
} else { |
449
|
|
|
echo "Batch ID: $id\n"; |
450
|
|
|
} |
451
|
|
|
} |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
if (0) { |
455
|
|
|
list($batches, $errmsg) = boinc_query_batches($req); |
456
|
|
|
if ($errmsg) { |
457
|
|
|
echo "Error: $errmsg\n"; |
458
|
|
|
} else { |
459
|
|
|
print_r($batches); |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
if (0) { |
464
|
|
|
$req->batch_id = 20; |
465
|
|
|
list($jobs, $errmsg) = boinc_query_batch($req); |
466
|
|
|
if ($errmsg) { |
467
|
|
|
echo "Error: $errmsg\n"; |
468
|
|
|
} else { |
469
|
|
|
print_r($jobs); |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
|
?> |
473
|
|
|
|