. // example of a web interface to remote job submission // // Although the architecture is intended to support remote portals, // this example must run on the BOINC project server. // In particular: // - It assumes cookie-based authentication, // as is done by the BOINC code. // You'll need to adapt/extend this considerably to run this // on a server other than the BOINC project server. // - It uses some functions from BOINC (page_head() etc.). // When you adapt this to your own purposes, // you can strip out this stuff if the web site doesn't use BOINC require_once("../inc/submit.inc"); require_once("../inc/common_defs.inc"); require_once("../inc/submit_db.inc"); require_once("../inc/submit_util.inc"); // needed for access control stuff require_once("../inc/util.inc"); require_once("../project/project.inc"); error_reporting(E_ALL); ini_set('display_errors', true); ini_set('display_startup_errors', true); // hardwired app name for now define('APP_NAME', 'remote_test'); $project = $master_url; // from project.inc $user = get_logged_in_user(); $auth = $user->authenticator; // the job submission "home page": // show the user's in-progress and completed batches, // and a button for creating a new batch // function handle_main() { global $project, $auth; $req = new StdClass; $req->project = $project; $req->authenticator = $auth; list($batches, $errmsg) = boinc_query_batches($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Job submission and control"); echo " This is an example of a web interface for remote submission of BOINC jobs. It lets you

To use this, you must be logged in as a user with permission to submit jobs.

"; show_button("submit_example.php?action=create_form", "Create new batch"); $first = true; foreach ($batches as $batch) { if ($batch->state != BATCH_STATE_IN_PROGRESS) continue; if ($first) { $first = false; echo "

Batches in progress

\n"; start_table(); table_header("name", "ID", "app", "# jobs", "progress", "submitted"); } $pct_done = (int)($batch->fraction_done*100); table_row( "id>$batch->name", "id>$batch->id", $batch->app_name, $batch->njobs, "$pct_done%", local_time_str($batch->create_time) ); } if ($first) { echo "

You have no in-progress batches.\n"; } else { end_table(); } $first = true; foreach ($batches as $batch) { if ($batch->state != BATCH_STATE_COMPLETE) continue; if ($first) { $first = false; echo "

Completed batches

\n"; start_table(); table_header("name", "ID", "# jobs", "submitted"); } table_row( "id>$batch->name", "id>$batch->id", $batch->njobs, local_time_str($batch->create_time) ); } if ($first) { echo "

You have no completed batches.\n"; } else { end_table(); } $first = true; foreach ($batches as $batch) { if ($batch->state != BATCH_STATE_ABORTED) continue; if ($first) { $first = false; echo "

Aborted batches

\n"; start_table(); table_header("name", "ID", "# jobs", "submitted"); } table_row( "id>$batch->name", "id>$batch->id", $batch->njobs, local_time_str($batch->create_time) ); } if (!$first) { end_table(); } echo "

Return to job control page\n"; page_tail(); } // return an array of the apps this user is allowed to submit jobs for // function eligible_apps() { global $user; $apps = BoincApp::enum("deprecated = 0"); $user_submit = BoincUserSubmit::lookup_userid($user->id); if (!$user_submit) return null; $a = array(); foreach($apps as $app) { if ($user_submit->submit_all) { $a[] = $app; } else { if (BoincUserSubmitApp::lookup("user_id=$user->id and app_id=$app->id")) { $a[] = $app; } } } return $a; } // return HTML for a popup menu of apps // function app_select($apps) { $x = "\n"; return $x; } // show a form for creating a batch. // function handle_create_form() { global $project, $auth; $apps = eligible_apps(); if (!$apps) error_page("You are not allowed to submit jobs"); page_head("Create batch"); echo " This form lets you specify a batch of jobs, and either submit it or get and estimate of its completion time.

The job runs an application that

"; start_table(); row2("Batch name", ""); // row2("Application", app_select($apps)); row2("Input file URL", ""); row2("Parameter low value (0..60)", ""); row2("Parameter high value (0..60)", ""); row2("Parameter increment (>= 1)", ""); row2("", "" ); row2("", "" ); end_table(); echo "
\n"; echo "

Return to job control page\n"; page_tail(); } // build a request object for boinc_*_batch() from form variables // function form_to_request() { global $project, $auth; $input_url = get_str('input_url'); if (!$input_url) error_page("missing input URL"); $param_lo = (double)get_str('param_lo'); if ($param_lo<0 || $param_lo>60) error_page("param lo must be in 0..60"); $param_hi = (double)get_str('param_hi'); if ($param_hi<0 || $param_hi>60 || $param_hi <= $param_lo) { error_page("param hi must be in 0..60 and > param lo"); } $param_inc = (double)get_str('param_inc'); if ($param_inc < 1) error_page("param inc must be >= 1"); $req = new StdClass; $req->project = $project; $req->authenticator = $auth; $req->app_name = APP_NAME; $req->batch_name = get_str('batch_name'); $req->jobs = array(); $f = new StdClass; $f->source = $input_url; $f->mode = 'semilocal'; for ($x=$param_lo; $x<$param_hi; $x += $param_inc) { $job = new StdClass; $job->rsc_fpops_est = $x*1e9; $job->command_line = "--t $x"; $job->input_files = array($f); $req->jobs[] = $job; } return $req; } // create and submit a batch // function handle_create_action() { global $project, $auth; $get_estimate = get_str('get_estimate', true); if ($get_estimate) { $req = form_to_request($project, $auth); list($e, $errmsg) = boinc_estimate_batch($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Batch estimate"); echo sprintf("Estimate: %.0f seconds", $e); echo "

Return to job control page\n"; page_tail(); } else { $req = form_to_request($project, $auth); list($id, $errmsg) = boinc_submit_batch($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Batch submitted"); echo "Batch created, ID: $id\n"; echo "

Return to job control page\n"; page_tail(); } } // show the details of an existing batch // function handle_query_batch() { global $project, $auth; $req = (object)array( 'project' => $project, 'authenticator' => $auth, 'batch_id' => get_int('batch_id'), ); list($batch, $errmsg) = boinc_query_batch($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Batch $req->batch_id"); start_table(); row2("name", $batch->name); row2("application", $batch->app_name); row2("state", batch_state_string($batch->state)); row2("# jobs", $batch->njobs); row2("# error jobs", $batch->nerror_jobs); row2("progress", sprintf("%.0f%%", $batch->fraction_done*100)); if ($batch->completion_time) { row2("completed", local_time_str($batch->completion_time)); } row2("GFLOP/hours, estimated", number_format(credit_to_gflop_hours($batch->credit_estimate), 2)); row2("GFLOP/hours, actual", number_format(credit_to_gflop_hours($batch->credit_canonical), 2)); end_table(); $url = boinc_get_output_files($req); show_button($url, "Get zipped output files"); switch ($batch->state) { case BATCH_STATE_IN_PROGRESS: echo "
"; show_button( "submit_example.php?action=abort_batch_confirm&batch_id=$req->batch_id", "Abort batch" ); break; case BATCH_STATE_COMPLETE: case BATCH_STATE_ABORTED: echo "
"; show_button( "submit_example.php?action=retire_batch_confirm&batch_id=$req->batch_id", "Retire batch" ); break; } echo "

Jobs

\n"; start_table(); table_header( "Job ID

click for details or to get output files

", "status", "Canonical instance

click to see result page on BOINC server

" ); foreach($batch->jobs as $job) { $id = (int)$job->id; $resultid = (int)$job->canonical_instance_id; if ($resultid) { $x = "$resultid"; $y = "completed"; } else { $x = "---"; $y = "in progress"; } echo " $id $y $x "; } end_table(); echo "

Return to job control page\n"; page_tail(); } // show the details of a job, including links to see the output files // function handle_query_job() { global $project, $auth; $req = new StdClass; $req->project = $project; $req->authenticator = $auth; $req->job_id = get_int('job_id'); list($reply, $errmsg) = boinc_query_job($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Job $req->job_id"); echo "job_id>View workunit page on BOINC server\n"; echo "

Instances

\n"; start_table(); table_header( "Instance ID

click for result page on BOINC server

", "State", "Output files" ); foreach($reply->instances as $inst) { echo " id>$inst->id $inst->state "; $i = 0; foreach ($inst->outfiles as $outfile) { $req->instance_name = $inst->name; $req->file_num = $i; $url = boinc_get_output_file($req); echo "$outfile->size bytes"; $i++; } echo "\n"; } end_table(); echo "

Return to job control page\n"; page_tail(); } function handle_abort_batch_confirm() { $batch_id = get_int('batch_id'); page_head("Confirm abort batch"); echo " Aborting a batch will cancel all unstarted jobs. Are you sure you want to do this?

"; show_button( "submit_example.php?action=abort_batch&batch_id=$batch_id", "Yes - abort batch" ); echo "

Return to job control page\n"; page_tail(); } function handle_abort_batch() { global $project, $auth; $req->project = $project; $req->authenticator = $auth; $req->batch_id = get_int('batch_id'); $errmsg = boinc_abort_batch($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Batch aborted"); echo "

Return to job control page\n"; page_tail(); } function handle_retire_batch_confirm() { $batch_id = get_int('batch_id'); page_head("Confirm retire batch"); echo " Retiring a batch will remove all of its output files. Are you sure you want to do this?

"; show_button( "submit_example.php?action=retire_batch&batch_id=$batch_id", "Yes - retire batch" ); echo "

Return to job control page\n"; page_tail(); } function handle_retire_batch() { global $project, $auth; $req->project = $project; $req->authenticator = $auth; $req->batch_id = get_int('batch_id'); $errmsg = boinc_retire_batch($req); if ($errmsg) error_page(htmlentities($errmsg)); page_head("Batch retired"); echo "

Return to job control page\n"; page_tail(); } $action = get_str('action', true); switch ($action) { case '': handle_main(); break; case 'abort_batch': handle_abort_batch(); break; case 'abort_batch_confirm': handle_abort_batch_confirm(); break; case 'create_action': handle_create_action(); break; case 'create_form': handle_create_form(); break; case 'query_batch': handle_query_batch(); break; case 'query_job': handle_query_job(); break; case 'retire_batch': handle_retire_batch(); break; case 'retire_batch_confirm': handle_retire_batch_confirm(); break; default: error_page('no such action'); } ?>