]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Tasks.pm
api: task download: name clashing params explicit
[pve-manager.git] / PVE / API2 / Tasks.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Tasks;
2
3use strict;
4use warnings;
5use POSIX;
6use IO::File;
7use File::ReadBackwards;
8use PVE::Tools;
9use PVE::SafeSyslog;
10use PVE::RESTHandler;
11use PVE::ProcFSTools;
12use PVE::RPCEnvironment;
13use PVE::JSONSchema qw(get_standard_option);
5e44c7e1 14use PVE::Exception qw(raise_param_exc);
aff192e6
DM
15use PVE::AccessControl;
16
17use base qw(PVE::RESTHandler);
18
a901f94a
FG
19my $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
8f8073cb
FG
27my $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
aff192e6 39__PACKAGE__->register_method({
20663182
DC
40 name => 'node_tasks',
41 path => '',
aff192e6 42 method => 'GET',
20663182 43 permissions => {
00cc94d3 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).",
20663182 45 user => 'all'
00cc94d3 46 },
aff192e6
DM
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,
7419faa4 56 default => 0,
aff192e6 57 optional => 1,
7419faa4 58 description => "List tasks beginning from this offset.",
aff192e6
DM
59 },
60 limit => {
61 type => 'integer',
62 minimum => 0,
7419faa4 63 default => 50,
aff192e6 64 optional => 1,
7419faa4 65 description => "Only list this amount of tasks.",
aff192e6
DM
66 },
67 userfilter => {
68 type => 'string',
69 optional => 1,
7419faa4 70 description => "Only list tasks from this user.",
aff192e6 71 },
8dacbee3
FG
72 typefilter => {
73 type => 'string',
74 optional => 1,
75 description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
76 },
5614bec0
DM
77 vmid => get_standard_option('pve-vmid', {
78 description => "Only list tasks for this VM.",
7419faa4 79 optional => 1,
5614bec0 80 }),
aff192e6
DM
81 errors => {
82 type => 'boolean',
7419faa4 83 default => 0,
aff192e6 84 optional => 1,
e95aae29 85 description => 'Only list tasks with a status of ERROR.',
aff192e6 86 },
32388c41
FG
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 },
97e749ad
DC
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 },
acf8a271
DC
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 },
aff192e6
DM
110 },
111 },
112 returns => {
113 type => 'array',
114 items => {
115 type => "object",
116 properties => {
7419faa4 117 upid => { type => 'string', title => 'UPID', },
f6faf09c
FG
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', },
7419faa4 125 endtime => { type => 'integer', optional => 1, title => 'Endtime', },
f6faf09c 126 status => { type => 'string', optional => 1, title => 'Status', },
aff192e6
DM
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};
f0b87b4c
DC
142 my $start = $param->{start} // 0;
143 my $limit = $param->{limit} // 50;
aff192e6 144 my $userfilter = $param->{userfilter};
8dacbee3 145 my $typefilter = $param->{typefilter};
f0b87b4c 146 my $errors = $param->{errors} // 0;
32388c41 147 my $source = $param->{source} // 'archive';
97e749ad
DC
148 my $since = $param->{since};
149 my $until = $param->{until};
acf8a271
DC
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 }
aff192e6
DM
170
171 my $count = 0;
172 my $line;
173
e4d554ba 174 my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
aff192e6 175
32388c41
FG
176 my $filter_task = sub {
177 my $task = shift;
178
179 return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
8f8073cb 180 return 1 if !($auditor || $check_task_user->($task, $user));
32388c41 181
8dacbee3
FG
182 return 1 if $typefilter && $task->{type} ne $typefilter;
183
32388c41
FG
184 return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
185
97e749ad
DC
186 return 1 if defined($since) && $task->{starttime} < $since;
187 return 1 if defined($until) && $task->{starttime} > $until;
188
acf8a271
DC
189 my $type = PVE::Tools::upid_normalize_status_type($task->{status});
190 return 1 if !$statusfilter->{$type};
191
32388c41
FG
192 return 1 if $count++ < $start;
193 return 1 if $limit <= 0;
194
195 return 0;
196 };
197
aff192e6
DM
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))) {
aff192e6
DM
204
205 $task->{upid} = $upid;
206 $task->{endtime} = hex($endtime) if $endtime;
207 $task->{status} = $status if $status;
32388c41 208
a901f94a 209 $convert_token_task->($task);
32388c41
FG
210 if (!$filter_task->($task)) {
211 push @$res, $task;
212 $limit--;
213 }
aff192e6
DM
214 }
215 }
216 };
217
32388c41
FG
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 }
aff192e6 227 }
aff192e6 228 }
32388c41
FG
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();
aff192e6 242 }
aff192e6
DM
243 }
244
e09058af 245 $rpcenv->set_result_attrib('total', $count);
aff192e6
DM
246
247 return $res;
248 }});
249
250__PACKAGE__->register_method({
20663182
DC
251 name => 'upid_index',
252 path => '{upid}',
aff192e6
DM
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({
20663182
DC
281 name => 'stop_task',
282 path => '{upid}',
aff192e6
DM
283 method => 'DELETE',
284 description => 'Stop a task.',
20663182 285 permissions => {
e95aae29 286 description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if they aren't the owner of the task.",
20663182 287 user => 'all',
00cc94d3 288 },
aff192e6
DM
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
a901f94a
FG
310 $convert_token_task->($task);
311
8f8073cb 312 if (!$check_task_user->($task, $user)) {
00cc94d3 313 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
e4d554ba 314 }
aff192e6 315
f70fc9ac 316 PVE::RPCEnvironment->check_worker($param->{upid}, 1);
aff192e6
DM
317
318 return undef;
319 }});
320
321__PACKAGE__->register_method({
20663182
DC
322 name => 'read_task_log',
323 path => '{upid}/log',
aff192e6 324 method => 'GET',
20663182 325 permissions => {
e95aae29 326 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they aren't the owner of the task.",
00cc94d3
DM
327 user => 'all',
328 },
aff192e6
DM
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'),
e95aae29
DW
336 upid => {
337 type => 'string',
338 description => "The task's unique ID.",
339 },
aff192e6
DM
340 start => {
341 type => 'integer',
342 minimum => 0,
7419faa4 343 default => 0,
aff192e6 344 optional => 1,
aa73246b 345 description => "Start at this line when reading the tasklog",
aff192e6
DM
346 },
347 limit => {
348 type => 'integer',
349 minimum => 0,
7419faa4 350 default => 50,
aff192e6 351 optional => 1,
aa73246b 352 description => "The amount of lines to read from the tasklog.",
aff192e6 353 },
aa73246b
DT
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 }
aff192e6
DM
359 },
360 },
361 returns => {
362 type => 'array',
20663182 363 items => {
aff192e6
DM
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
aff192e6
DM
383 my $rpcenv = PVE::RPCEnvironment::get();
384 my $user = $rpcenv->get_user();
385 my $node = $param->{node};
386
a901f94a
FG
387 $convert_token_task->($task);
388
8f8073cb 389 if (!$check_task_user->($task, $user)) {
e4d554ba
DM
390 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
391 }
aff192e6 392
62790325 393 if ($param->{download}) {
aa73246b 394 if (defined($param->{start}) || defined($param->{limit})) {
2ebba467 395 die "'download' cannot be used together with 'start' or 'limit' parameters\n";
aa73246b
DT
396 }
397
398 my $fh;
399 my $use_compression = ( -s $filename ) > 1024;
db354940 400
aa73246b
DT
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 }
aff192e6
DM
428 }});
429
47a5865a
DM
430
431my $exit_status_cache = {};
432
aff192e6 433__PACKAGE__->register_method({
20663182
DC
434 name => 'read_task_status',
435 path => '{upid}/status',
aff192e6 436 method => 'GET',
20663182 437 permissions => {
e95aae29 438 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
00cc94d3
DM
439 user => 'all',
440 },
aff192e6
DM
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'),
e95aae29
DW
448 upid => {
449 type => 'string',
450 description => "The task's unique ID.",
451 },
aff192e6
DM
452 },
453 },
454 returns => {
455 type => "object",
456 properties => {
20663182 457 pid => {
aff192e6
DM
458 type => 'integer'
459 },
20663182
DC
460 status => {
461 type => 'string', enum => ['running', 'stopped'],
aff192e6 462 },
7195dcd3
DC
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 },
aff192e6
DM
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
a901f94a
FG
500 $convert_token_task->($task);
501
8f8073cb 502 if (!$check_task_user->($task, $user)) {
e4d554ba
DM
503 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
504 }
aff192e6
DM
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
47a5865a
DM
512 if ($task->{status} eq 'stopped') {
513 if (!defined($exit_status_cache->{$task->{upid}})) {
20663182 514 $exit_status_cache->{$task->{upid}} =
47a5865a
DM
515 PVE::Tools::upid_read_status($task->{upid});
516 }
517 $task->{exitstatus} = $exit_status_cache->{$task->{upid}};
518 }
519
aff192e6
DM
520 return $task;
521 }});
b0f820ab
DM
522
5231;