Completed
Push — master ( 2c494c...954598 )
by Vitalii
49s queued 15s
created

create_jobs()   B

Complexity

Conditions 10
Paths 144

Size

Total Lines 63
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
eloc 42
c 1
b 0
f 1
nc 144
nop 12
dl 0
loc 63
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');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
36
    $variant = get_str('variant');
37
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
38
39
    $desc = "<br><small>
40
        A zip file with one directory per job.
41
        Each directory contains the input file(s) for that job
42
        and an optional file <code>cmdline</code>
43
        containing command-line arguments.
44
        <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission>Details</a></small>.
45
    ";
46
    page_head("BUDA: Submit jobs to $app ($variant)");
47
    form_start('buda_submit.php');
48
    form_input_hidden('action', 'submit');
49
    form_input_hidden('app', $app);
50
    form_input_hidden('variant', $variant);
51
    form_select("Batch zip file $desc", 'batch_file', $sbitems_zip);
52
    form_input_text(
53
        'Command-line arguments<br><small>Passed to all jobs in the batch</small>',
54
        'cmdline'
55
    );
56
    form_input_text(
57
        'Max job runtime (days) on a typical (4.3 GFLOPS) computer.
58
            <br><small>
59
            The runtime limit will be scaled for faster/slower computers.
60
            <br>
61
            Jobs that reach this limit will be aborted.
62
            </small>'
63
        ,
64
        'max_runtime_days', 1
65
    );
66
    form_input_text(
67
        'Expected job runtime (days) on a typical (4.3 GFLOPS) computer.
68
            <br><small>
69
            This determines how many jobs are sent to each host,
70
            and how "fraction done" is computed.
71
            </small>
72
        ',
73
        'exp_runtime_days', .5
74
    );
75
    form_checkbox(
76
        "Enable debugging output <br><small>Write Docker commands and output to stderr. Not recommended for long-running jobs.</small>.",
77
        'wrapper_verbose'
78
    );
79
    form_submit('OK');
80
    form_end();
81
    page_tail();
82
}
83
84
// unzip batch file into a temp dir; return dir name
85
//
86
function unzip_batch_file($user, $batch_file) {
87
    @mkdir("../../buda_batches");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

87
    /** @scrutinizer ignore-unhandled */ @mkdir("../../buda_batches");

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
88
    for ($i=0; $i<1000; $i++) {
89
        $batch_dir = "../../buda_batches/$i";
90
        $batch_dir_name = $i;
91
        $ret = @mkdir($batch_dir);
92
        if ($ret) break;
93
    }
94
    if (!$ret) error_page("can't create batch dir");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.
Loading history...
95
    $sb_dir = sandbox_dir($user);
96
    if (!file_exists("$sb_dir/$batch_file")) {
97
        error_page("no batch file $batch_file");
98
    }
99
    system("cd $batch_dir; unzip $sb_dir/$batch_file > /dev/null", $ret);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $batch_dir does not seem to be defined for all execution paths leading up to this point.
Loading history...
100
    if ($ret) {
101
        error_page("unzip error: $ret");
102
    }
103
    return $batch_dir_name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $batch_dir_name does not seem to be defined for all execution paths leading up to this point.
Loading history...
104
}
105
106
// Scan a batch dir.
107
// Check its validity:
108
// - optional dir 'shared_input_files' has shared input files
109
// - other dirs (job dirs) can have only remaining infiles and possibly cmdline
110
//
111
// Return a structure describing its contents, and the md5/size of files
112
//
113
function parse_batch_dir($batch_dir, $variant_desc) {
114
    $input_files = $variant_desc->input_file_names;
115
    sort($input_files);
116
    $shared_files = [];
117
    $shared_file_infos = [];
118
    if (is_dir("$batch_dir/shared_input_files")) {
119
        foreach (scandir("$batch_dir/shared_input_files") as $fname) {
120
            if ($fname[0] == '.') continue;
121
            if (!in_array($fname, $input_files)) {
122
                error_page("$fname is not an input file name");
123
            }
124
            $shared_files[] = $fname;
125
            $shared_file_infos[] = get_file_info("$batch_dir/shared_input_files/$fname");
126
        }
127
    }
128
    $unshared_files = array_diff($input_files, $shared_files);
129
    sort($unshared_files);
130
    $jobs = [];
131
    foreach (scandir($batch_dir) as $fname) {
132
        if ($fname[0] == '.') continue;
133
        if ($fname == 'shared_input_files') continue;
134
        if (!is_dir("$batch_dir/$fname")) {
135
            error_page("$batch_dir/$fname is not a directory");
136
        }
137
        $job_files = [];
138
        $cmdline = '';
139
        foreach(scandir("$batch_dir/$fname") as $f2) {
140
            if ($f2[0] == '.') continue;
141
            if ($f2 == 'cmdline') {
142
                $cmdline = trim(file_get_contents("$batch_dir/$fname/cmdline"));
143
                continue;
144
            }
145
            if (!in_array($f2, $unshared_files)) {
146
                error_page("$fname/$f2 is not an input file name");
147
            }
148
            $job_files[] = $f2;
149
        }
150
        if (array_values($job_files) != array_values($unshared_files)) {
151
            error_page("$fname doesn't have all input files");
152
        }
153
154
        if (!$cmdline && !$job_files) {
155
            error_page("job $f2 has no cmdline and no input files");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $f2 does not seem to be defined for all execution paths leading up to this point.
Loading history...
156
        }
157
158
        $file_infos = [];
159
        foreach ($unshared_files as $f2) {
160
            $file_infos[] = get_file_info("$batch_dir/$fname/$f2");
161
        }
162
163
        $job = new StdClass;
164
        $job->dir = $fname;
165
        $job->cmdline = $cmdline;
166
        $job->file_infos = $file_infos;
167
        $jobs[] = $job;
168
    }
169
    $batch_desc = new StdClass;
170
    $batch_desc->shared_files = $shared_files;
171
    $batch_desc->shared_file_infos = $shared_file_infos;
172
    $batch_desc->unshared_files = $unshared_files;
173
    $batch_desc->jobs = $jobs;
174
    return $batch_desc;
175
}
176
177
function create_batch($user, $njobs, $app, $variant) {
178
    global $buda_boinc_app;
179
    $now = time();
180
    $batch_name = sprintf('buda_%d_%d', $user->id, $now);
181
    $description = "$app ($variant)";
182
    $batch_id = BoincBatch::insert(sprintf(
183
        "(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)",
184
        $user->id, $now, $njobs, BATCH_STATE_INIT, $batch_name, $buda_boinc_app->id,
185
        $description
186
    ));
187
    return BoincBatch::lookup_id($batch_id);
188
}
189
190
function stage_input_files($batch_dir, $batch_desc, $batch_id) {
191
    $n = count($batch_desc->shared_files);
192
    $batch_desc->shared_files_phys_names = [];
193
    for ($i=0; $i<$n; $i++) {
194
        $path = sprintf('%s/%s', $batch_dir, $batch_desc->shared_files[$i]);
195
        [$md5, $size] = $batch_desc->shared_file_infos[$i];
196
        $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
197
        stage_file_aux($path, $md5, $size, $phys_name);
198
        $batch_desc->shared_files_phys_names[] = $phys_name;
199
    }
200
    foreach ($batch_desc->jobs as $job) {
201
        $n = count($batch_desc->unshared_files);
202
        $job->phys_names = [];
203
        for ($i=0; $i<$n; $i++) {
204
            $path = sprintf('%s/%s/%s',
205
                $batch_dir, $job->dir, $batch_desc->unshared_files[$i]
206
            );
207
            [$md5, $size] = $job->file_infos[$i];
208
            $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
209
            stage_file_aux($path, $md5, $size, $phys_name);
210
            $job->phys_names[] = $phys_name;
211
        }
212
    }
213
}
214
215
// run bin/create_work to create the jobs.
216
// Use --stdin, where each job is described by a line
217
//
218
function create_jobs(
219
    $app, $app_desc, $variant, $variant_desc,
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
220
    $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...
221
    $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...
222
    $keywords
223
) {
224
    global $buda_boinc_app;
225
226
    // get list of physical names of app files
227
    //
228
    $app_file_names = $variant_desc->dockerfile_phys;
229
    foreach ($variant_desc->app_files_phys as $pname) {
230
        $app_file_names .= " $pname";
231
    }
232
233
    // make per-job lines to pass as stdin
234
    //
235
    $job_cmds = '';
236
    foreach ($batch_desc->jobs as $job) {
237
        $job_cmd = sprintf('--wu_name batch_%d__job_%s', $batch_id, $job->dir);
238
        if ($job->cmdline) {
239
            $job_cmd .= sprintf(' --command_line "%s"', $job->cmdline);
240
        }
241
        $job_cmd .= " $app_file_names";
242
        foreach ($batch_desc->shared_files_phys_names as $x) {
243
            $job_cmd .= " $x";
244
        }
245
        foreach ($job->phys_names as $x) {
246
            $job_cmd .= " $x";
247
        }
248
        $job_cmds .= "$job_cmd\n";
249
    }
250
    $wrapper_cmdline = sprintf('"--dockerfile %s %s %s"',
251
        $variant_desc->dockerfile,
252
        $wrapper_verbose?'--verbose':'',
253
        $cmdline
254
    );
255
    $cmd = sprintf(
256
        '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',
257
        $buda_boinc_app->name,
258
        $app_desc->long_name,
259
        $batch_id,
260
        $wrapper_cmdline,
261
        "buda_apps/$app/$variant/template_in",
262
        "buda_apps/$app/$variant/template_out",
263
        $max_fpops, $exp_fpops
264
    );
265
    if ($keywords) {
266
        $cmd .= " --keywords '$keywords'";
267
    }
268
    $cmd .= sprintf(' > %s 2<&1', "buda_batches/errfile");
269
270
    $h = popen($cmd, "w");
271
    if (!$h) error_page('create_work launch failed');
0 ignored issues
show
introduced by
$h is of type resource, thus it always evaluated to false.
Loading history...
272
    fwrite($h, $job_cmds);
273
    $ret = pclose($h);
274
    if ($ret) {
275
        echo "<pre>create_work failed.\n";
276
        echo "command: $cmd\n\n";
277
        echo "job lines:\n$job_cmds\n\n";
278
        echo "error file:\n";
279
        readfile("../../buda_batches/errfile");
280
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
281
    }
282
}
283
284
function handle_submit($user) {
285
    global $buda_root;
286
287
    $app = get_str('app');
288
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
289
    $variant = get_str('variant');
290
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
291
    $batch_file = get_str('batch_file');
292
    if (!is_valid_filename($batch_file)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
293
    $wrapper_verbose = get_str('wrapper_verbose', true);
294
    $cmdline = get_str('cmdline');
295
296
    $max_runtime_days = get_str('max_runtime_days');
297
    if (!is_numeric($max_runtime_days)) error_page('bad runtime limit');
298
    $max_runtime_days = (double)$max_runtime_days;
299
    if ($max_runtime_days <= 0) error_page('bad runtime limit');
300
    if ($max_runtime_days > 100) error_page('bad runtime limit');
301
    $max_fpops = $max_runtime_days * 4.3e9 * 86400;
302
303
    $exp_runtime_days = get_str('exp_runtime_days');
304
    if (!is_numeric($exp_runtime_days)) error_page('bad expected runtime');
305
    $exp_runtime_days = (double)$exp_runtime_days;
306
    if ($exp_runtime_days <= 0) error_page('bad expected runtime');
307
    if ($exp_runtime_days > 100) error_page('bad expected runtime');
308
    if ($exp_runtime_days > $max_runtime_days) {
309
        error_page('exp must be < max runtime');
310
    }
311
    $exp_fpops = $exp_runtime_days * 4.3e9 * 86400;
312
313
    $app_desc = get_buda_desc($app);
314
315
    $variant_dir = "$buda_root/$app/$variant";
316
    $variant_desc = json_decode(
317
        file_get_contents("$variant_dir/variant.json")
318
    );
319
320
    // unzip batch file into temp dir
321
    $batch_dir_name = unzip_batch_file($user, $batch_file);
322
    $batch_dir = "../../buda_batches/$batch_dir_name";
323
324
    // scan batch dir; validate and return struct
325
    $batch_desc = parse_batch_dir($batch_dir, $variant_desc);
326
327
    if (!$batch_desc->jobs) {
328
        page_head("No jobs created");
329
        echo "
330
            Your batch file (.zip) did not specify any jobs.
331
            See <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission#batch-files>Instructions for creating batch files</a>.
332
        ";
333
        page_tail();
334
        return;
335
    }
336
337
    $batch = create_batch(
338
        $user, count($batch_desc->jobs), $app, $variant
339
    );
340
341
    // stage input files and record the physical names
342
    //
343
    stage_input_files($batch_dir, $batch_desc, $batch->id);
344
345
    // get job keywords: user keywords plus BUDA app keywords
346
    //
347
    [$yes, $no] = read_kw_prefs($user);
348
    $keywords = array_merge($yes, $app_desc->sci_kw, $app_desc->loc_kw);
349
    $keywords = array_unique($keywords);
350
    $keywords = implode(' ', $keywords);
351
352
    create_jobs(
353
        $app, $app_desc, $variant, $variant_desc,
354
        $batch_desc, $batch->id, $batch_dir_name,
355
        $wrapper_verbose, $cmdline, $max_fpops, $exp_fpops, $keywords
356
    );
357
358
    // mark batch as in progress
359
    //
360
    $batch->update(sprintf('state=%d', BATCH_STATE_IN_PROGRESS));
361
362
    // clean up batch dir
363
    //
364
    //system("rm -rf $batch_dir");
365
366
    header("Location: submit.php?action=query_batch&batch_id=$batch->id");
367
}
368
369
function show_list() {
370
    page_head('BUDA job submission');
371
    $apps = get_buda_apps();
372
    echo 'Select app and variant:<p><br>';
373
    foreach ($apps as $app) {
374
        $desc = get_buda_desc($app);
375
        $vars = get_buda_variants($app);
376
        echo "$desc->long_name
377
            <ul>
378
        ";
379
        foreach ($vars as $var) {
380
            echo sprintf('<li><a href=buda_submit.php?action=form&app=%s&variant=%s>%s</a>',
381
                $app, $var, $var
382
            );
383
        }
384
        echo "</ul>\n";
385
    }
386
    page_tail();
387
}
388
389
$user = get_logged_in_user();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $user is correct as get_logged_in_user() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
390
$buda_boinc_app = BoincApp::lookup("name='buda'");
391
if (!$buda_boinc_app) error_page('no buda app');
392
if (!has_submit_access($user, $buda_boinc_app->id)) {
393
    error_page('no access');
394
}
395
$action = get_str('action', true);
396
if ($action == 'submit') {
397
    handle_submit($user);
398
} else if ($action == 'form') {
399
    submit_form($user);
400
} else {
401
    show_list();
0 ignored issues
show
Bug introduced by
The call to show_list() has too few arguments starting with models. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

401
    /** @scrutinizer ignore-call */ 
402
    show_list();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
402
}
403
404
?>
405