. // Handler for remote job submission. // See http://boinc.berkeley.edu/trac/wiki/RemoteJobs require_once("../inc/boinc_db.inc"); require_once("../inc/submit_db.inc"); require_once("../inc/xml.inc"); require_once("../inc/dir_hier.inc"); require_once("../inc/result.inc"); require_once("../inc/submit_util.inc"); error_reporting(E_ALL); ini_set('display_errors', true); ini_set('display_startup_errors', true); function get_wu($name) { $name = BoincDb::escape_string($name); $wu = BoincWorkunit::lookup("name='$name'"); if (!$wu) xml_error(-1, "BOINC server: no job named $name was found"); return $wu; } function get_app($name) { $name = BoincDb::escape_string($name); $app = BoincApp::lookup("name='$name'"); if (!$app) xml_error(-1, "BOINC server: no app named $name was found"); return $app; } // estimate FLOP count for a batch. // If estimates aren't included in the job descriptions, // use what's in the input template // function batch_flop_count($r, $template) { $x = 0; $t = (double)$template->workunit->rsc_fpops_est; foreach($r->batch->job as $job) { $y = (double)$job->rsc_fpops_est; if ($y) { $x += $y; } else { $x += $t; } } return $x; } // estimate project FLOPS based on recent average credit // function project_flops() { $x = BoincUser::sum("expavg_credit"); if ($x == 0) $x = 200; $y = 1e9*$x/200; return $y; } function est_elapsed_time($r, $template) { // crude estimate: batch FLOPs / project FLOPS // return batch_flop_count($r, $template) / project_flops(); } function read_input_template($app, $r) { if ((isset($r->batch)) && (isset($r->batch->workunit_template_file)) && ($r->batch->workunit_template_file)) { $path = "../../templates/".$r->batch->workunit_template_file; } else { $path = "../../templates/$app->name"."_in"; } return simplexml_load_file($path); } function check_max_jobs_in_progress($r, $user_submit) { if (!$user_submit->max_jobs_in_progress) return; $query = "select count(*) as total from DBNAME.result, DBNAME.batch where batch.user_id=$userid and result.batch = batch.id and result.server_state<".RESULT_SERVER_STATE_OVER; $db = BoincDb::get(); $n = $db->get_int($query); if ($n === false) return; if ($n + count($r->batch->job) > $user_submit->max_jobs_in_progress) { xml_error(-1, "BOINC server: limit on jobs in progress exceeded"); } } function estimate_batch($r) { $app = get_app((string)($r->batch->app_name)); list($user, $user_submit) = authenticate_user($r, $app); $template = read_input_template($app, $r); $e = est_elapsed_time($r, $template); echo "\n$e\n\n"; } function validate_batch($jobs, $template) { $i = 0; $n = count($template->file_info); foreach($jobs as $job) { $m = count($job->input_files); if ($n != $m) { xml_error(-1, "BOINC server: wrong # of input files for job $i: need $n, got $m"); } $i++; } } $fanout = parse_config(get_config(), ""); // stage a file, and return the physical name // function stage_file($file) { global $fanout; $download_dir = parse_config(get_config(), ""); switch ($file->mode) { case "semilocal": case "local": // read the file (from disk or network) to get MD5. // Copy to download hier, using a physical name based on MD5 // $md5 = md5_file($file->source); if (!$md5) { xml_error(-1, "BOINC server: Can't get MD5 of file $file->source"); } $name = "jf_$md5"; $path = dir_hier_path($name, $download_dir, $fanout); if (file_exists($path)) return $name; if (!copy($file->source, $path)) { xml_error(-1, "BOINC server: can't copy file from $file->source to $path"); } return $name; case "local_staged": return $file->source; case "inline": $md5 = md5($file->source); if (!$md5) { xml_error(-1, "BOINC server: Can't get MD5 of inline data"); } $name = "jf_$md5"; $path = dir_hier_path($name, $download_dir, $fanout); if (file_exists($path)) return $name; if (!file_put_contents($path, $file->source)) { xml_error(-1, "BOINC server: can't write to file $path"); } return $name; } xml_error(-1, "BOINC server: unsupported file mode: $file->mode"); } // stage all the files // function stage_files(&$jobs, $template) { foreach($jobs as $job) { foreach ($job->input_files as $file) { if ($file->mode != "remote") { $file->name = stage_file($file); } } } } function submit_jobs($jobs, $template, $app, $batch_id, $priority, $result_template_file = null, $workunit_template_file = null) { $x = ""; foreach($jobs as $job) { if ($job->name) { $x .= " --wu_name $job->name"; } if ($job->command_line) { $x .= " --command_line \"$job->command_line\""; } if ($job->target_team) { $x .= " --target_team $job->target_team"; } elseif ($job->target_user) { $x .= " --target_user $job->target_user"; } elseif ($job->target_host) { $x .= " --target_host $job->target_host"; } foreach ($job->input_files as $file) { if ($file->mode == "remote") { $x .= " --remote_file $file->url $file->nbytes $file->md5"; } else { $x .= " $file->name"; } } $x .= "\n"; } $cmd = "cd ../..; ./bin/create_work --appname $app->name --batch $batch_id --rsc_fpops_est $job->rsc_fpops_est --priority $priority --stdin"; if ($result_template_file) { $cmd .= " --result_template templates/$result_template_file"; } if ($workunit_template_file) { $cmd .= " --wu_template templates/$workunit_template_file"; } $cmd .= " --stdin"; $h = popen($cmd, "w"); if ($h === false) { xml_error(-1, "BOINC server: can't run create_work"); } fwrite($h, $x); $ret = pclose($h); if ($ret) { xml_error(-1, "BOINC server: create_work failed"); } } function xml_get_jobs($r) { $jobs = array(); foreach($r->batch->job as $j) { $job = new StdClass; $job->input_files = array(); $job->command_line = (string)$j->command_line; $job->target_team = (int)$j->target_team; $job->target_user = (int)$j->target_user; $job->target_host = (int)$j->target_host; $job->name = (string)$j->name; $job->rsc_fpops_est = (double)$j->rsc_fpops_est; foreach ($j->input_file as $f) { $file = new StdClass; $file->mode = (string)$f->mode; if ($file->mode == "remote") { $file->url = (string)$f->url; $file->nbytes = (double)$f->nbytes; $file->md5 = (string)$f->md5; } else { $file->source = (string)$f->source; } $job->input_files[] = $file; } $jobs[] = $job; } return $jobs; } function submit_batch($r) { $app = get_app((string)($r->batch->app_name)); list($user, $user_submit) = authenticate_user($r, $app); $template = read_input_template($app, $r); $jobs = xml_get_jobs($r); validate_batch($jobs, $template); stage_files($jobs, $template); $njobs = count($jobs); $now = time(); $batch_id = (int)($r->batch->batch_id); if ($batch_id) { $batch = BoincBatch::lookup_id($batch_id); if (!$batch) { xml_error(-1, "BOINC server: no batch $batch_id"); } if ($batch->user_id != $user->id) { xml_error(-1, "BOINC server: not owner of batch"); } if ($batch->state != BATCH_STATE_INIT) { xml_error(-1, "BOINC server: batch not in init state"); } } // - compute batch FLOP count // - run adjust_user_priorities to increment user_submit.logical_start_time // - use that for batch logical end time and job priority // $total_flops = 0; foreach($jobs as $job) { if ($job->rsc_fpops_est) { $total_flops += $job->rsc_fpops_est; } else { $x = (double) $template->workunit->rsc_fpops_est; if ($x) { $total_flops += $x; } else { xml_error(-1, "BOINC server: no rsc_fpops_est given"); } } } $cmd = "cd ../../bin; ./adjust_user_priority --user $user->id --flops $total_flops --app $app->name"; $x = exec($cmd); if (!is_numeric($x) || (double)$x == 0) { xml_error(-1, "BOINC server: $cmd returned $x"); } $let = (double)$x; if ($batch_id) { $njobs = count($jobs); $ret = $batch->update("njobs=$njobs, logical_end_time=$let"); if (!$ret) xml_error(-1, "BOINC server: batch->update() failed"); } else { $batch_name = (string)($r->batch->batch_name); $batch_id = BoincBatch::insert( "(user_id, create_time, njobs, name, app_id, logical_end_time, state) values ($user->id, $now, $njobs, '$batch_name', $app->id, $let, ".BATCH_STATE_INIT.")" ); if (!$batch_id) { xml_error(-1, "BOINC server: Can't create batch: ".BoincDb::error()); } $batch = BoincBatch::lookup_id($batch_id); } if ($r->batch->result_template_file) { $result_template_file = $r->batch->result_template_file; } else { $result_template_file = null; } if ($r->batch->workunit_template_file) { $workunit_template_file = $r->batch->workunit_template_file; } else { $workunit_template_file = null; } submit_jobs($jobs, $template, $app, $batch_id, $let, $result_template_file, $workunit_template_file); // set state to IN_PROGRESS only after creating jobs; // otherwise we might flag batch as COMPLETED // $ret = $batch->update("state= ".BATCH_STATE_IN_PROGRESS); if (!$ret) xml_error(-1, "BOINC server: batch->update() failed"); echo "$batch_id\n"; } function create_batch($r) { $app = get_app((string)($r->batch->app_name)); list($user, $user_submit) = authenticate_user($r, $app); $now = time(); $batch_name = (string)($r->batch->batch_name); $expire_time = (double)($r->expire_time); $batch_id = BoincBatch::insert( "(user_id, create_time, name, app_id, state, expire_time) values ($user->id, $now, '$batch_name', $app->id, ".BATCH_STATE_INIT.", $expire_time)" ); if (!$batch_id) { xml_error(-1, "BOINC server: Can't create batch: ".BoincDb::error()); } echo "$batch_id\n"; } function print_batch_params($batch) { $app = BoincApp::lookup_id($batch->app_id); if (!$app) $app->name = "none"; echo " $batch->id $batch->create_time $batch->expire_time $batch->est_completion_time $batch->njobs $batch->fraction_done $batch->nerror_jobs $batch->state $batch->completion_time $batch->credit_estimate $batch->credit_canonical $batch->name $app->name "; } function query_batches($r) { list($user, $user_submit) = authenticate_user($r, null); $batches = BoincBatch::enum("user_id = $user->id"); echo "\n"; foreach ($batches as $batch) { if ($batch->state < BATCH_STATE_COMPLETE) { $wus = BoincWorkunit::enum("batch = $batch->id"); $batch = get_batch_params($batch, $wus); } echo " \n"; print_batch_params($batch); echo " \n"; } echo "\n"; } function n_outfiles($wu) { $path = "../../$wu->result_template_file"; $r = simplexml_load_file($path); return count($r->file_info); } // return a batch specified by the command, using either ID or name // function get_batch($r) { if (!empty($r->batch_id)) { $batch_id = (int)($r->batch_id); $batch = BoincBatch::lookup_id($batch_id); } else if (!empty($r->batch_name)) { $batch_name = (string)($r->batch_name); $batch_name = BoincDb::escape_string($batch_name); $batch = BoincBatch::lookup_name($batch_name); } else { xml_error(-1, "BOINC server: batch not specified"); } if (!$batch) xml_error(-1, "BOINC server: no such batch"); return $batch; } function query_batch($r) { list($user, $user_submit) = authenticate_user($r, null); $batch = get_batch($r); if ($batch->user_id != $user->id) { xml_error(-1, "BOINC server: not owner of batch"); } $wus = BoincWorkunit::enum("batch = $batch->id"); $batch = get_batch_params($batch, $wus); echo "\n"; print_batch_params($batch); $n_outfiles = n_outfiles($wus[0]); foreach ($wus as $wu) { echo " $wu->id $wu->name $wu->canonical_resultid $n_outfiles "; } echo "\n"; } // variant for Condor, which doesn't care about job instances // and refers to batches by name // function query_batch2($r) { list($user, $user_submit) = authenticate_user($r, null); $batch_names = $r->batch_name; $batches = array(); foreach ($batch_names as $b) { $batch_name = (string)$b; $batch_name = BoincDb::escape_string($batch_name); $batch = BoincBatch::lookup_name($batch_name); if (!$batch) { xml_error(-1, "no batch named $batch_name"); } if ($batch->user_id != $user->id) { xml_error(-1, "not owner of $batch_name"); } $batches[] = $batch; } $min_mod_time = (double)$r->min_mod_time; if ($min_mod_time) { $mod_time_clause = "and mod_time > FROM_UNIXTIME($min_mod_time)"; } else { $mod_time_clause = ""; } $t = dtime(); echo "$t\n"; echo "\n"; foreach ($batches as $batch) { $wus = BoincWorkunit::enum("batch = $batch->id $mod_time_clause"); echo " ".count($wus)."\n"; foreach ($wus as $wu) { if ($wu->canonical_resultid) { $status = "DONE"; } else if ($wu->error_mask) { $status = "ERROR"; } else { $status = "IN_PROGRESS"; } echo " $wu->name $status "; } } echo "\n"; } function query_job($r) { list($user, $user_submit) = authenticate_user($r, null); $job_id = (int)($r->job_id); $wu = BoincWorkunit::lookup_id($job_id); if (!$wu) xml_error(-1, "no such job"); $batch = BoincBatch::lookup_id($wu->batch); if ($batch->user_id != $user->id) { xml_error(-1, "not owner"); } echo "\n"; $results = BoincResult::enum("workunitid=$job_id"); foreach ($results as $result) { echo " $result->name $result->id ".state_string($result)." "; if ($result->server_state == 5) { // over? $paths = get_outfile_paths($result); foreach($paths as $path) { if (is_file($path)) { $size = filesize($path); echo " $size "; } } } echo "\n"; } echo "\n"; } // the following for Condor. // If the job has a canonical instance, return info about it. // Otherwise find an instance that completed // (possibly crashed) and return its info. // function query_completed_job($r) { list($user, $user_submit) = authenticate_user($r, null); $job_name = (string)($r->job_name); $job_name = BoincDb::escape_string($job_name); $wu = BoincWorkunit::lookup("name='$job_name'"); if (!$wu) xml_error(-1, "no such job"); $batch = BoincBatch::lookup_id($wu->batch); if ($batch->user_id != $user->id) { xml_error(-1, "not owner"); } echo "\n"; echo " $wu->error_mask\n"; if ($wu->canonical_resultid) { $result = BoincResult::lookup_id($wu->canonical_resultid); echo " $wu->canonical_resultid\n"; } else { $results = BoincResult::enum("workunitid=$job_id"); foreach ($results as $r) { switch($r->outcome) { case 1: case 3: case 6: $result = $r; break; } } if ($result) { echo " $result->id\n"; } } if ($result) { echo " $result->exit_status\n"; echo " $result->elapsed_time\n"; echo " $result->cpu_time\n"; echo " stderr_out); echo " ]]>\n"; } echo "\n"; } function handle_abort_batch($r) { list($user, $user_submit) = authenticate_user($r, null); $batch = get_batch($r); if ($batch->user_id != $user->id) { xml_error(-1, "not owner"); } abort_batch($batch); echo "1"; } // handle the abort of jobs possibly belonging to different batches // function handle_abort_jobs($r) { list($user, $user_submit) = authenticate_user($r, null); $batch = null; foreach ($r->job_name as $job_name) { $job_name = BoincDb::escape_string($job_name); $wu = BoincWorkunit::lookup("name='$job_name'"); if (!$wu) { xml_error(-1, "No job $job_name"); } if (!$wu->batch) { xml_error(-1, "Job $job_name is not part of a batch"); } if (!$batch || $wu->batch != $batch->id) { $batch = BoincBatch::lookup_id($wu->batch); } if (!$batch || $batch->user_id != $user->id) { xml_error(-1, "not owner"); } echo "\n"; abort_workunit($wu); } echo "1"; } function handle_retire_batch($r) { list($user, $user_submit) = authenticate_user($r, null); $batch = get_batch($r); if ($batch->user_id != $user->id) { xml_error(-1, "not owner"); } retire_batch($batch); echo "1"; } function handle_set_expire_time($r) { list($user, $user_submit) = authenticate_user($r, null); $batch = get_batch($r); if ($batch->user_id != $user->id) { xml_error(-1, "not owner"); } $expire_time = (double)($r->expire_time); if ($batch->update("expire_time=$expire_time")) { echo "1"; } else { xml_error(-1, "update failed"); } } function get_templates($r) { $app_name = (string)($r->app_name); if ($app_name) { $app = get_app($app_name); } else { $job_name = (string)($r->job_name); $wu = get_wu($job_name); $app = BoincApp::lookup_id($wu->appid); } list($user, $user_submit) = authenticate_user($r, $app); $in = file_get_contents("../../templates/".$app->name."_in"); $out = file_get_contents("../../templates/".$app->name."_out"); if ($in === false || $out === false) { xml_error(-1, "template file missing"); } echo "\n$in\n$out\n\n"; } function ping($r) { BoincDb::get(); // errors out if DB down or web disabled echo "1"; } if (0) { $r = simplexml_load_string(" x 54 "); query_batch($r); exit; } if (0) { $r = simplexml_load_string(" x 312173 "); query_job($r); exit; } if (0) { $r = simplexml_load_string(" x remote_test Aug 6 batch 4 19000000000 --t 19 remote http://google.com/ "); estimate_batch($r); exit; } if (0) { require_once("submit_test.inc"); } xml_header(); $r = simplexml_load_string($_POST['request']); if (!$r) { xml_error(-1, "can't parse request message"); } switch ($r->getName()) { case 'abort_batch': handle_abort_batch($r); break; case 'abort_jobs': handle_abort_jobs($r); break; case 'create_batch': create_batch($r); break; case 'estimate_batch': estimate_batch($r); break; case 'get_templates': get_templates($r); break; case 'ping': ping($r); break; case 'query_batch': query_batch($r); break; case 'query_batch2': query_batch2($r); break; case 'query_batches': query_batches($r); break; case 'query_job': query_job($r); break; case 'query_completed_job': query_completed_job($r); break; case 'retire_batch': handle_retire_batch($r); break; case 'set_expire_time': handle_set_expire_time($r); break; case 'submit_batch': submit_batch($r); break; default: xml_error(-1, "bad command: ".$r->getName()); } ?>