]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Tasks.pm
api: task download: avoid useless intermediate variable
[pve-manager.git] / PVE / API2 / Tasks.pm
1 package PVE::API2::Tasks;
2
3 use strict;
4 use warnings;
5 use POSIX;
6 use IO::File;
7 use File::ReadBackwards;
8 use PVE::Tools;
9 use PVE::SafeSyslog;
10 use PVE::RESTHandler;
11 use PVE::ProcFSTools;
12 use PVE::RPCEnvironment;
13 use PVE::JSONSchema qw(get_standard_option);
14 use PVE::Exception qw(raise_param_exc);
15 use PVE::AccessControl;
16
17 use base qw(PVE::RESTHandler);
18
19 my $convert_token_task = sub {
20 my ($task) = @_;
21
22 if (PVE::AccessControl::pve_verify_tokenid($task->{user}, 1)) {
23 ($task->{user}, $task->{tokenid}) = PVE::AccessControl::split_tokenid($task->{user});
24 }
25 };
26
27 my $check_task_user = sub {
28 my ($task, $user) = @_;
29
30 if ($task->{tokenid}) {
31 my $fulltoken = PVE::AccessControl::join_tokenid($task->{user}, $task->{tokenid});
32 # token only sees token tasks, user sees user + token tasks
33 return $user eq $fulltoken || $user eq $task->{user};
34 } else {
35 return $user eq $task->{user};
36 }
37 };
38
39 __PACKAGE__->register_method({
40 name => 'node_tasks',
41 path => '',
42 method => 'GET',
43 permissions => {
44 description => "List task associated with the current user, or all task the user has 'Sys.Audit' permissions on /nodes/<node> (the <node> the task runs on).",
45 user => 'all'
46 },
47 description => "Read task list for one node (finished tasks).",
48 proxyto => 'node',
49 parameters => {
50 additionalProperties => 0,
51 properties => {
52 node => get_standard_option('pve-node'),
53 start => {
54 type => 'integer',
55 minimum => 0,
56 default => 0,
57 optional => 1,
58 description => "List tasks beginning from this offset.",
59 },
60 limit => {
61 type => 'integer',
62 minimum => 0,
63 default => 50,
64 optional => 1,
65 description => "Only list this amount of tasks.",
66 },
67 userfilter => {
68 type => 'string',
69 optional => 1,
70 description => "Only list tasks from this user.",
71 },
72 typefilter => {
73 type => 'string',
74 optional => 1,
75 description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
76 },
77 vmid => get_standard_option('pve-vmid', {
78 description => "Only list tasks for this VM.",
79 optional => 1,
80 }),
81 errors => {
82 type => 'boolean',
83 default => 0,
84 optional => 1,
85 description => 'Only list tasks with a status of ERROR.',
86 },
87 source => {
88 type => 'string',
89 enum => ['archive', 'active', 'all'],
90 default => 'archive',
91 optional => 1,
92 description => 'List archived, active or all tasks.',
93 },
94 since => {
95 type => 'integer',
96 description => "Only list tasks since this UNIX epoch.",
97 optional => 1,
98 },
99 until => {
100 type => 'integer',
101 description => "Only list tasks until this UNIX epoch.",
102 optional => 1,
103 },
104 statusfilter => {
105 type => 'string',
106 format => 'pve-task-status-type-list',
107 optional => 1,
108 description => 'List of Task States that should be returned.',
109 },
110 },
111 },
112 returns => {
113 type => 'array',
114 items => {
115 type => "object",
116 properties => {
117 upid => { type => 'string', title => 'UPID', },
118 node => { type => 'string', title => 'Node', },
119 pid => { type => 'integer', title => 'PID', },
120 pstart => { type => 'integer', },
121 starttime => { type => 'integer', title => 'Starttime', },
122 type => { type => 'string', title => 'Type', },
123 id => { type => 'string', title => 'ID', },
124 user => { type => 'string', title => 'User', },
125 endtime => { type => 'integer', optional => 1, title => 'Endtime', },
126 status => { type => 'string', optional => 1, title => 'Status', },
127 },
128 },
129 links => [ { rel => 'child', href => "{upid}" } ],
130 },
131 code => sub {
132 my ($param) = @_;
133
134 my $rpcenv = PVE::RPCEnvironment::get();
135 my $user = $rpcenv->get_user();
136
137 my $res = [];
138
139 my $filename = "/var/log/pve/tasks/index";
140
141 my $node = $param->{node};
142 my $start = $param->{start} // 0;
143 my $limit = $param->{limit} // 50;
144 my $userfilter = $param->{userfilter};
145 my $typefilter = $param->{typefilter};
146 my $errors = $param->{errors} // 0;
147 my $source = $param->{source} // 'archive';
148 my $since = $param->{since};
149 my $until = $param->{until};
150 my $statusfilter = {
151 ok => 1,
152 warning => 1,
153 error => 1,
154 unknown => 1,
155 };
156
157 if (defined($param->{statusfilter}) && !$errors) {
158 $statusfilter = {
159 ok => 0,
160 warning => 0,
161 error => 0,
162 unknown => 0,
163 };
164 for my $filter (PVE::Tools::split_list($param->{statusfilter})) {
165 $statusfilter->{lc($filter)} = 1 ;
166 }
167 } elsif ($errors) {
168 $statusfilter->{ok} = 0;
169 }
170
171 my $count = 0;
172 my $line;
173
174 my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
175
176 my $filter_task = sub {
177 my $task = shift;
178
179 return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
180 return 1 if !($auditor || $check_task_user->($task, $user));
181
182 return 1 if $typefilter && $task->{type} ne $typefilter;
183
184 return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
185
186 return 1 if defined($since) && $task->{starttime} < $since;
187 return 1 if defined($until) && $task->{starttime} > $until;
188
189 my $type = PVE::Tools::upid_normalize_status_type($task->{status});
190 return 1 if !$statusfilter->{$type};
191
192 return 1 if $count++ < $start;
193 return 1 if $limit <= 0;
194
195 return 0;
196 };
197
198 my $parse_line = sub {
199 if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
200 my $upid = $1;
201 my $endtime = $3;
202 my $status = $5;
203 if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
204
205 $task->{upid} = $upid;
206 $task->{endtime} = hex($endtime) if $endtime;
207 $task->{status} = $status if $status;
208
209 $convert_token_task->($task);
210 if (!$filter_task->($task)) {
211 push @$res, $task;
212 $limit--;
213 }
214 }
215 }
216 };
217
218 if ($source eq 'active' || $source eq 'all') {
219 my $recent_tasks = PVE::INotify::read_file('active');
220 for my $task (@$recent_tasks) {
221 next if $task->{saved}; # archived task, already in index(.1)
222 if (!$filter_task->($task)) {
223 $task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived
224 push @$res, $task;
225 $limit--;
226 }
227 }
228 }
229
230 if ($source ne 'active') {
231 if (my $bw = File::ReadBackwards->new($filename)) {
232 while (defined ($line = $bw->readline)) {
233 &$parse_line();
234 }
235 $bw->close();
236 }
237 if (my $bw = File::ReadBackwards->new("$filename.1")) {
238 while (defined ($line = $bw->readline)) {
239 &$parse_line();
240 }
241 $bw->close();
242 }
243 }
244
245 $rpcenv->set_result_attrib('total', $count);
246
247 return $res;
248 }});
249
250 __PACKAGE__->register_method({
251 name => 'upid_index',
252 path => '{upid}',
253 method => 'GET',
254 description => '', # index helper
255 permissions => { user => 'all' },
256 parameters => {
257 additionalProperties => 0,
258 properties => {
259 node => get_standard_option('pve-node'),
260 upid => { type => 'string' },
261 }
262 },
263 returns => {
264 type => 'array',
265 items => {
266 type => "object",
267 properties => {},
268 },
269 links => [ { rel => 'child', href => "{name}" } ],
270 },
271 code => sub {
272 my ($param) = @_;
273
274 return [
275 { name => 'log' },
276 { name => 'status' }
277 ];
278 }});
279
280 __PACKAGE__->register_method({
281 name => 'stop_task',
282 path => '{upid}',
283 method => 'DELETE',
284 description => 'Stop a task.',
285 permissions => {
286 description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if they aren't the owner of the task.",
287 user => 'all',
288 },
289 protected => 1,
290 proxyto => 'node',
291 parameters => {
292 additionalProperties => 0,
293 properties => {
294 node => get_standard_option('pve-node'),
295 upid => { type => 'string' },
296 }
297 },
298 returns => { type => 'null' },
299 code => sub {
300 my ($param) = @_;
301
302 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
303 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
304 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
305
306 my $rpcenv = PVE::RPCEnvironment::get();
307 my $user = $rpcenv->get_user();
308 my $node = $param->{node};
309
310 $convert_token_task->($task);
311
312 if (!$check_task_user->($task, $user)) {
313 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
314 }
315
316 PVE::RPCEnvironment->check_worker($param->{upid}, 1);
317
318 return undef;
319 }});
320
321 __PACKAGE__->register_method({
322 name => 'read_task_log',
323 path => '{upid}/log',
324 method => 'GET',
325 permissions => {
326 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they aren't the owner of the task.",
327 user => 'all',
328 },
329 protected => 1,
330 description => "Read task log.",
331 proxyto => 'node',
332 parameters => {
333 additionalProperties => 0,
334 properties => {
335 node => get_standard_option('pve-node'),
336 upid => {
337 type => 'string',
338 description => "The task's unique ID.",
339 },
340 start => {
341 type => 'integer',
342 minimum => 0,
343 default => 0,
344 optional => 1,
345 description => "Start at this line when reading the tasklog",
346 },
347 limit => {
348 type => 'integer',
349 minimum => 0,
350 default => 50,
351 optional => 1,
352 description => "The amount of lines to read from the tasklog.",
353 },
354 download => {
355 type => 'boolean',
356 optional => 1,
357 description => "Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters",
358 }
359 },
360 },
361 returns => {
362 type => 'array',
363 items => {
364 type => "object",
365 properties => {
366 n => {
367 description=> "Line number",
368 type=> 'integer',
369 },
370 t => {
371 description=> "Line text",
372 type => 'string',
373 }
374 }
375 }
376 },
377 code => sub {
378 my ($param) = @_;
379
380 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
381 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
382
383 my $rpcenv = PVE::RPCEnvironment::get();
384 my $user = $rpcenv->get_user();
385 my $node = $param->{node};
386
387 $convert_token_task->($task);
388
389 if (!$check_task_user->($task, $user)) {
390 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
391 }
392
393 if ($param->{download}) {
394 if (defined($param->{start}) || defined($param->{limit})) {
395 die "Parameter 'download' can't be used with other parameters\n";
396 }
397
398 my $fh;
399 my $use_compression = ( -s $filename ) > 1024;
400
401 # 1024 is a practical cutoff for the size distribution of our log files.
402 if ($use_compression) {
403 open($fh, "-|", "/usr/bin/gzip", "-c", "$filename")
404 or die "Could not create compressed file stream for file '$filename' - $!\n";
405 } else {
406 open($fh, '<', $filename) or die "Could not open file '$filename' - $!\n";
407 }
408
409 return {
410 download => {
411 fh => $fh,
412 stream => 1,
413 'content-encoding' => $use_compression ? 'gzip' : undef,
414 'content-type' => "text/plain",
415 'content-disposition' => "attachment; filename=\"".$param->{upid}."\"",
416 },
417 },
418 } else {
419 my $start = $param->{start} // 0;
420 my $limit = $param->{limit} // 50;
421
422 my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
423
424 $rpcenv->set_result_attrib('total', $count);
425
426 return $lines;
427 }
428 }});
429
430
431 my $exit_status_cache = {};
432
433 __PACKAGE__->register_method({
434 name => 'read_task_status',
435 path => '{upid}/status',
436 method => 'GET',
437 permissions => {
438 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
439 user => 'all',
440 },
441 protected => 1,
442 description => "Read task status.",
443 proxyto => 'node',
444 parameters => {
445 additionalProperties => 0,
446 properties => {
447 node => get_standard_option('pve-node'),
448 upid => {
449 type => 'string',
450 description => "The task's unique ID.",
451 },
452 },
453 },
454 returns => {
455 type => "object",
456 properties => {
457 pid => {
458 type => 'integer'
459 },
460 status => {
461 type => 'string', enum => ['running', 'stopped'],
462 },
463 type => {
464 type => 'string',
465 },
466 id => {
467 type => 'string',
468 },
469 user => {
470 type => 'string',
471 },
472 exitstatus => {
473 type => 'string',
474 optional => 1,
475 },
476 upid => {
477 type => 'string',
478 },
479 starttime => {
480 type => 'number',
481 },
482 node => {
483 type => 'string',
484 },
485 },
486 },
487 code => sub {
488 my ($param) = @_;
489
490 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
491 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
492 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
493
494 my $lines = [];
495
496 my $rpcenv = PVE::RPCEnvironment::get();
497 my $user = $rpcenv->get_user();
498 my $node = $param->{node};
499
500 $convert_token_task->($task);
501
502 if (!$check_task_user->($task, $user)) {
503 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
504 }
505
506 my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
507 $task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
508 'running' : 'stopped';
509
510 $task->{upid} = $param->{upid}; # include upid
511
512 if ($task->{status} eq 'stopped') {
513 if (!defined($exit_status_cache->{$task->{upid}})) {
514 $exit_status_cache->{$task->{upid}} =
515 PVE::Tools::upid_read_status($task->{upid});
516 }
517 $task->{exitstatus} = $exit_status_cache->{$task->{upid}};
518 }
519
520 return $task;
521 }});
522
523 1;