]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Tasks.pm
ui: dc/BackupEdit: use correct validation
[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,
e95aae29 345 description => "The line number to start printing at.",
aff192e6
DM
346 },
347 limit => {
348 type => 'integer',
349 minimum => 0,
7419faa4 350 default => 50,
aff192e6 351 optional => 1,
e95aae29 352 description => "The maximum amount of lines that should be printed.",
aff192e6
DM
353 },
354 },
355 },
356 returns => {
357 type => 'array',
20663182 358 items => {
aff192e6
DM
359 type => "object",
360 properties => {
361 n => {
362 description=> "Line number",
363 type=> 'integer',
364 },
365 t => {
366 description=> "Line text",
367 type => 'string',
368 }
369 }
370 }
371 },
372 code => sub {
373 my ($param) = @_;
374
375 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
376 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
377
aff192e6
DM
378 my $rpcenv = PVE::RPCEnvironment::get();
379 my $user = $rpcenv->get_user();
380 my $node = $param->{node};
db354940
DC
381 my $start = $param->{start} // 0;
382 my $limit = $param->{limit} // 50;
aff192e6 383
a901f94a
FG
384 $convert_token_task->($task);
385
8f8073cb 386 if (!$check_task_user->($task, $user)) {
e4d554ba
DM
387 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
388 }
aff192e6 389
db354940 390 my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
aff192e6 391
e09058af 392 $rpcenv->set_result_attrib('total', $count);
db354940 393
aff192e6
DM
394 return $lines;
395 }});
396
47a5865a
DM
397
398my $exit_status_cache = {};
399
aff192e6 400__PACKAGE__->register_method({
20663182
DC
401 name => 'read_task_status',
402 path => '{upid}/status',
aff192e6 403 method => 'GET',
20663182 404 permissions => {
e95aae29 405 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
00cc94d3
DM
406 user => 'all',
407 },
aff192e6
DM
408 protected => 1,
409 description => "Read task status.",
410 proxyto => 'node',
411 parameters => {
412 additionalProperties => 0,
413 properties => {
414 node => get_standard_option('pve-node'),
e95aae29
DW
415 upid => {
416 type => 'string',
417 description => "The task's unique ID.",
418 },
aff192e6
DM
419 },
420 },
421 returns => {
422 type => "object",
423 properties => {
20663182 424 pid => {
aff192e6
DM
425 type => 'integer'
426 },
20663182
DC
427 status => {
428 type => 'string', enum => ['running', 'stopped'],
aff192e6 429 },
7195dcd3
DC
430 type => {
431 type => 'string',
432 },
433 id => {
434 type => 'string',
435 },
436 user => {
437 type => 'string',
438 },
439 exitstatus => {
440 type => 'string',
441 optional => 1,
442 },
443 upid => {
444 type => 'string',
445 },
446 starttime => {
447 type => 'number',
448 },
449 node => {
450 type => 'string',
451 },
aff192e6
DM
452 },
453 },
454 code => sub {
455 my ($param) = @_;
456
457 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
458 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
459 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
460
461 my $lines = [];
462
463 my $rpcenv = PVE::RPCEnvironment::get();
464 my $user = $rpcenv->get_user();
465 my $node = $param->{node};
466
a901f94a
FG
467 $convert_token_task->($task);
468
8f8073cb 469 if (!$check_task_user->($task, $user)) {
e4d554ba
DM
470 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
471 }
aff192e6
DM
472
473 my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
474 $task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
475 'running' : 'stopped';
476
477 $task->{upid} = $param->{upid}; # include upid
478
47a5865a
DM
479 if ($task->{status} eq 'stopped') {
480 if (!defined($exit_status_cache->{$task->{upid}})) {
20663182 481 $exit_status_cache->{$task->{upid}} =
47a5865a
DM
482 PVE::Tools::upid_read_status($task->{upid});
483 }
484 $task->{exitstatus} = $exit_status_cache->{$task->{upid}};
485 }
486
aff192e6
DM
487 return $task;
488 }});
b0f820ab
DM
489
4901;