Passed
Pull Request — master (#6594)
by David
15:27 queued 05:34
created

create_jobs()   B

Complexity

Conditions 10
Paths 144

Size

Total Lines 56
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
eloc 39
c 1
b 0
f 1
nc 144
nop 11
dl 0
loc 56
rs 7.2999

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
// This file is part of BOINC.
3
// https://boinc.berkeley.edu
4
// Copyright (C) 2024 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
// web interface for submitting BUDA jobs
20
21
require_once('../inc/util.inc');
22
require_once('../inc/submit_util.inc');
23
require_once('../inc/sandbox.inc');
24
require_once('../inc/buda.inc');
25
require_once('../inc/kw_prefs.inc');
26
27
display_errors();
28
29
function submit_form($user) {
30
    $sbitems_zip = sandbox_select_items($user, '/.zip$/');
31
    if (!$sbitems_zip) {
32
        error_page("No .zip files in your sandbox.");
33
    }
34
    $app = get_str('app');
35
    if (!is_valid_filename($app)) die('bad arg');
36
37
    $desc = "<br><small>
38
        A zip file with one directory per job.
39
        Each directory contains the input file(s) for that job
40
        and an optional file <code>cmdline</code>
41
        containing command-line arguments.
42
        <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission>Details</a></small>.
43
    ";
44
    page_head("BUDA: Submit jobs to $app");
45
    form_start('buda_submit.php');
46
    form_input_hidden('action', 'submit');
47
    form_input_hidden('app', $app);
48
    form_select("Batch zip file $desc", 'batch_file', $sbitems_zip);
49
    form_input_text(
50
        'Command-line arguments<br><small>Passed to all jobs in the batch</small>',
51
        'cmdline'
52
    );
53
    form_input_text(
54
        'Max job runtime (days) on a typical (4.3 GFLOPS) computer.
55
            <br><small>
56
            The runtime limit will be scaled for faster/slower computers.
57
            <br>
58
            Jobs that reach this limit will be aborted.
59
            </small>'
60
        ,
61
        'max_runtime_days', 1
62
    );
63
    form_input_text(
64
        'Expected job runtime (days) on a typical (4.3 GFLOPS) computer.
65
            <br><small>
66
            This determines how many jobs are sent to each host,
67
            and how "fraction done" is computed.
68
            </small>
69
        ',
70
        'exp_runtime_days', .5
71
    );
72
    form_checkbox(
73
        "Enable debugging output <br><small>Write Docker commands and output to stderr. Not recommended for long-running jobs.</small>.",
74
        'wrapper_verbose'
75
    );
76
    form_submit('OK');
77
    form_end();
78
    page_tail();
79
}
80
81
// unzip batch file into a temp dir; return dir name
82
//
83
function unzip_batch_file($user, $batch_file) {
84
    @mkdir("../../buda_batches");
85
    for ($i=0; $i<1000; $i++) {
86
        $batch_dir = "../../buda_batches/$i";
87
        $batch_dir_name = $i;
88
        $ret = @mkdir($batch_dir);
89
        if ($ret) break;
90
    }
91
    if (!$ret) error_page("can't create batch dir");
92
    $sb_dir = sandbox_dir($user);
93
    if (!file_exists("$sb_dir/$batch_file")) {
94
        error_page("no batch file $batch_file");
95
    }
96
    system("cd $batch_dir; unzip $sb_dir/$batch_file > /dev/null", $ret);
97
    if ($ret) {
98
        error_page("unzip error: $ret");
99
    }
100
    return $batch_dir_name;
101
}
102
103
// Scan a batch dir.
104
// Check its validity:
105
// - optional dir 'shared_input_files' has shared input files
106
// - other dirs (job dirs) can have only remaining infiles and possibly cmdline
107
//
108
// Return a structure describing its contents, and the md5/size of files
109
//
110
function parse_batch_dir($batch_dir, $app_desc) {
111
    $input_files = $app_desc->input_file_names;
112
    sort($input_files);
113
    $shared_files = [];
114
    $shared_file_infos = [];
115
    if (is_dir("$batch_dir/shared_input_files")) {
116
        foreach (scandir("$batch_dir/shared_input_files") as $fname) {
117
            if ($fname[0] == '.') continue;
118
            if (!in_array($fname, $input_files)) {
119
                error_page("$fname is not an input file name");
120
            }
121
            $shared_files[] = $fname;
122
            $shared_file_infos[] = get_file_info("$batch_dir/shared_input_files/$fname");
123
        }
124
    }
125
    $unshared_files = array_diff($input_files, $shared_files);
126
    sort($unshared_files);
127
    $jobs = [];
128
    foreach (scandir($batch_dir) as $fname) {
129
        if ($fname[0] == '.') continue;
130
        if ($fname == 'shared_input_files') continue;
131
        if (!is_dir("$batch_dir/$fname")) {
132
            error_page("$batch_dir/$fname is not a directory");
133
        }
134
        $job_files = [];
135
        $cmdline = '';
136
        foreach(scandir("$batch_dir/$fname") as $f2) {
137
            if ($f2[0] == '.') continue;
138
            if ($f2 == 'cmdline') {
139
                $cmdline = trim(file_get_contents("$batch_dir/$fname/cmdline"));
140
                continue;
141
            }
142
            if (!in_array($f2, $unshared_files)) {
143
                error_page("$fname/$f2 is not an input file name");
144
            }
145
            $job_files[] = $f2;
146
        }
147
        if (array_values($job_files) != array_values($unshared_files)) {
148
            error_page("$fname doesn't have all input files");
149
        }
150
151
        if (!$cmdline && !$job_files) {
152
            error_page("job $f2 has no cmdline and no input files");
153
        }
154
155
        $file_infos = [];
156
        foreach ($unshared_files as $f2) {
157
            $file_infos[] = get_file_info("$batch_dir/$fname/$f2");
158
        }
159
160
        $job = new StdClass;
161
        $job->dir = $fname;
162
        $job->cmdline = $cmdline;
163
        $job->file_infos = $file_infos;
164
        $jobs[] = $job;
165
    }
166
    $batch_desc = new StdClass;
167
    $batch_desc->shared_files = $shared_files;
168
    $batch_desc->shared_file_infos = $shared_file_infos;
169
    $batch_desc->unshared_files = $unshared_files;
170
    $batch_desc->jobs = $jobs;
171
    return $batch_desc;
172
}
173
174
function create_batch($user, $njobs, $app) {
175
    global $buda_boinc_app;
176
    $now = time();
177
    $batch_name = sprintf('buda_%d_%d', $user->id, $now);
178
    $description = "$app";
179
    $batch_id = BoincBatch::insert(sprintf(
180
        "(user_id, create_time, logical_start_time, logical_end_time, est_completion_time, njobs, fraction_done, nerror_jobs, state, completion_time, credit_estimate, credit_canonical, credit_total, name, app_id, project_state, description, expire_time) values (%d, %d, 0, 0, 0, %d, 0, 0, %d, 0, 0, 0, 0, '%s', %d, 0, '%s', 0)",
181
        $user->id, $now, $njobs, BATCH_STATE_INIT, $batch_name, $buda_boinc_app->id,
182
        $description
183
    ));
184
    return BoincBatch::lookup_id($batch_id);
185
}
186
187
function stage_input_files($batch_dir, $batch_desc, $batch_id) {
188
    $n = count($batch_desc->shared_files);
189
    $batch_desc->shared_files_phys_names = [];
190
    for ($i=0; $i<$n; $i++) {
191
        $path = sprintf('%s/%s', $batch_dir, $batch_desc->shared_files[$i]);
192
        [$md5, $size] = $batch_desc->shared_file_infos[$i];
193
        $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
194
        stage_file_aux($path, $md5, $size, $phys_name);
195
        $batch_desc->shared_files_phys_names[] = $phys_name;
196
    }
197
    foreach ($batch_desc->jobs as $job) {
198
        $n = count($batch_desc->unshared_files);
199
        $job->phys_names = [];
200
        for ($i=0; $i<$n; $i++) {
201
            $path = sprintf('%s/%s/%s',
202
                $batch_dir, $job->dir, $batch_desc->unshared_files[$i]
203
            );
204
            [$md5, $size] = $job->file_infos[$i];
205
            $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
206
            stage_file_aux($path, $md5, $size, $phys_name);
207
            $job->phys_names[] = $phys_name;
208
        }
209
    }
210
}
211
212
// run bin/create_work to create the jobs.
213
// Use --stdin, where each job is described by a line
214
//
215
function create_jobs(
216
    $user, $app, $app_desc, $batch_desc, $batch_id, $batch_dir_name,
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
217
    $wrapper_verbose, $cmdline, $max_fpops, $exp_fpops,
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
218
    $keywords
219
) {
220
    global $buda_boinc_app;
221
222
    // make per-job lines to pass as stdin
223
    //
224
    $job_cmds = '';
225
    foreach ($batch_desc->jobs as $job) {
226
        $job_cmd = sprintf('--wu_name batch_%d__job_%s', $batch_id, $job->dir);
227
        if ($job->cmdline) {
228
            $job_cmd .= sprintf(' --command_line "%s"', $job->cmdline);
229
        }
230
        foreach ($batch_desc->shared_files_phys_names as $x) {
231
            $job_cmd .= " $x";
232
        }
233
        foreach ($job->phys_names as $x) {
234
            $job_cmd .= " $x";
235
        }
236
        $job_cmds .= "$job_cmd\n";
237
    }
238
    $wrapper_cmdline = sprintf('"%s %s"',
239
        $wrapper_verbose?'--verbose':'',
240
        $cmdline
241
    );
242
    $cmd = sprintf(
243
        'cd ../..; bin/create_work --appname %s --sub_appname "%s" --batch %d --stdin --command_line %s --wu_template %s --result_template %s --rsc_fpops_bound %f --rsc_fpops_est %f',
244
        $buda_boinc_app->name,
245
        $app_desc->long_name,
246
        $batch_id,
247
        $wrapper_cmdline,
248
        "buda_apps/$app/template_in",
249
        "buda_apps/$app/template_out",
250
        $max_fpops, $exp_fpops
251
    );
252
    if ($keywords) {
253
        $cmd .= " --keywords '$keywords'";
254
    }
255
    if ($user->seti_id) {
256
        $cmd .= " --target_user $user->id ";
257
    }
258
    $cmd .= sprintf(' > %s 2<&1', "buda_batches/errfile");
259
260
    $h = popen($cmd, "w");
261
    if (!$h) error_page('create_work launch failed');
262
    fwrite($h, $job_cmds);
263
    $ret = pclose($h);
264
    if ($ret) {
265
        echo "<pre>create_work failed.\n";
266
        echo "command: $cmd\n\n";
267
        echo "job lines:\n$job_cmds\n\n";
268
        echo "error file:\n";
269
        readfile("../../buda_batches/errfile");
270
        exit;
271
    }
272
}
273
274
function handle_submit($user) {
275
    global $buda_root;
276
277
    $app = get_str('app');
278
    if (!is_valid_filename($app)) die('bad arg');
279
    $batch_file = get_str('batch_file');
280
    if (!is_valid_filename($batch_file)) die('bad arg');
281
    $wrapper_verbose = get_str('wrapper_verbose', true);
282
    $cmdline = get_str('cmdline');
283
284
    $max_runtime_days = get_str('max_runtime_days');
285
    if (!is_numeric($max_runtime_days)) error_page('bad runtime limit');
286
    $max_runtime_days = (double)$max_runtime_days;
287
    if ($max_runtime_days <= 0) error_page('bad runtime limit');
288
    if ($max_runtime_days > 100) error_page('bad runtime limit');
289
    $max_fpops = $max_runtime_days * 4.3e9 * 86400;
290
291
    $exp_runtime_days = get_str('exp_runtime_days');
292
    if (!is_numeric($exp_runtime_days)) error_page('bad expected runtime');
293
    $exp_runtime_days = (double)$exp_runtime_days;
294
    if ($exp_runtime_days <= 0) error_page('bad expected runtime');
295
    if ($exp_runtime_days > 100) error_page('bad expected runtime');
296
    if ($exp_runtime_days > $max_runtime_days) {
297
        error_page('exp must be < max runtime');
298
    }
299
    $exp_fpops = $exp_runtime_days * 4.3e9 * 86400;
300
301
    $app_desc = get_buda_app_desc($app);
302
303
    // unzip batch file into temp dir
304
    $batch_dir_name = unzip_batch_file($user, $batch_file);
305
    $batch_dir = "../../buda_batches/$batch_dir_name";
306
307
    // scan batch dir; validate and return struct
308
    $batch_desc = parse_batch_dir($batch_dir, $app_desc);
309
310
    if (!$batch_desc->jobs) {
311
        system("rm -rf $batch_dir");
312
        page_head("No jobs created");
313
        echo "
314
            Your batch file (.zip) did not specify any jobs.
315
            See <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission#batch-files>Instructions for creating batch files</a>.
316
        ";
317
        page_tail();
318
        return;
319
    }
320
    if (count($batch_desc->jobs > 10 && $user->seti_id) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
321
        system("rm -rf $batch_dir");
0 ignored issues
show
Coding Style introduced by
PHP syntax error: syntax error, unexpected ';'
Loading history...
Bug introduced by
A parse error occurred: Syntax error, unexpected ';' on line 321 at column 35
Loading history...
322
        error_page(
323
            "Batches with > 10 jobs are not allowed if 'use only my computers' is set"
324
        );
325
    }
326
327
    $batch = create_batch($user, count($batch_desc->jobs), $app);
328
329
    // stage input files and record the physical names
330
    //
331
    stage_input_files($batch_dir, $batch_desc, $batch->id);
332
333
    // get job keywords: user keywords plus BUDA app keywords
334
    //
335
    [$yes, $no] = read_kw_prefs($user);
336
    $keywords = array_merge($yes, $app_desc->sci_kw);
337
    $keywords = array_unique($keywords);
338
    $keywords = implode(' ', $keywords);
339
340
    create_jobs(
341
        $user, $app, $app_desc, $batch_desc, $batch->id, $batch_dir_name,
342
        $wrapper_verbose, $cmdline, $max_fpops, $exp_fpops, $keywords
343
    );
344
345
    // mark batch as in progress
346
    //
347
    $batch->update(sprintf('state=%d', BATCH_STATE_IN_PROGRESS));
348
349
    // clean up batch dir
350
    //
351
    system("rm -rf $batch_dir");
352
353
    header("Location: submit.php?action=query_batch&batch_id=$batch->id");
354
}
355
356
function show_list() {
357
    page_head('BUDA job submission');
358
    $apps = get_buda_apps();
359
    echo 'Select app:<p><br>';
360
    foreach ($apps as $app) {
361
        $desc = get_buda_app_desc($app);
362
        echo sprintf('<p><a href=buda_submit.php?action=form&app=%s>%s</a>',
363
            $app, $desc->long_name
364
        );
365
    }
366
    page_tail();
367
}
368
369
$user = get_logged_in_user();
370
$buda_boinc_app = BoincApp::lookup("name='buda'");
371
if (!$buda_boinc_app) error_page('no buda app');
372
if (!has_submit_access($user, $buda_boinc_app->id)) {
373
    error_page('no access');
374
}
375
$action = get_str('action', true);
376
if ($action == 'submit') {
377
    handle_submit($user);
378
} else if ($action == 'form') {
379
    submit_form($user);
380
} else {
381
    show_list();
382
}
383
384
?>
385