]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Tasks.pm
056b698bee6d3a8d9616b8ba930dcb28bbda85ea
[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 },
86 source => {
87 type => 'string',
88 enum => ['archive', 'active', 'all'],
89 default => 'archive',
90 optional => 1,
91 description => 'List archived, active or all tasks.',
92 },
93 since => {
94 type => 'integer',
95 description => "Only list tasks since this UNIX epoch.",
96 optional => 1,
97 },
98 until => {
99 type => 'integer',
100 description => "Only list tasks until this UNIX epoch.",
101 optional => 1,
102 },
103 statusfilter => {
104 type => 'string',
105 format => 'pve-task-status-type-list',
106 optional => 1,
107 description => 'List of Task States that should be returned.',
108 },
109 },
110 },
111 returns => {
112 type => 'array',
113 items => {
114 type => "object",
115 properties => {
116 upid => { type => 'string', title => 'UPID', },
117 node => { type => 'string', title => 'Node', },
118 pid => { type => 'integer', title => 'PID', },
119 pstart => { type => 'integer', },
120 starttime => { type => 'integer', title => 'Starttime', },
121 type => { type => 'string', title => 'Type', },
122 id => { type => 'string', title => 'ID', },
123 user => { type => 'string', title => 'User', },
124 endtime => { type => 'integer', optional => 1, title => 'Endtime', },
125 status => { type => 'string', optional => 1, title => 'Status', },
126 },
127 },
128 links => [ { rel => 'child', href => "{upid}" } ],
129 },
130 code => sub {
131 my ($param) = @_;
132
133 my $rpcenv = PVE::RPCEnvironment::get();
134 my $user = $rpcenv->get_user();
135
136 my $res = [];
137
138 my $filename = "/var/log/pve/tasks/index";
139
140 my $node = $param->{node};
141 my $start = $param->{start} // 0;
142 my $limit = $param->{limit} // 50;
143 my $userfilter = $param->{userfilter};
144 my $typefilter = $param->{typefilter};
145 my $errors = $param->{errors} // 0;
146 my $source = $param->{source} // 'archive';
147 my $since = $param->{since};
148 my $until = $param->{until};
149 my $statusfilter = {
150 ok => 1,
151 warning => 1,
152 error => 1,
153 unknown => 1,
154 };
155
156 if (defined($param->{statusfilter}) && !$errors) {
157 $statusfilter = {
158 ok => 0,
159 warning => 0,
160 error => 0,
161 unknown => 0,
162 };
163 for my $filter (PVE::Tools::split_list($param->{statusfilter})) {
164 $statusfilter->{lc($filter)} = 1 ;
165 }
166 } elsif ($errors) {
167 $statusfilter->{ok} = 0;
168 }
169
170 my $count = 0;
171 my $line;
172
173 my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
174
175 my $filter_task = sub {
176 my $task = shift;
177
178 return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
179 return 1 if !($auditor || $check_task_user->($task, $user));
180
181 return 1 if $typefilter && $task->{type} ne $typefilter;
182
183 return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
184
185 return 1 if defined($since) && $task->{starttime} < $since;
186 return 1 if defined($until) && $task->{starttime} > $until;
187
188 my $type = PVE::Tools::upid_normalize_status_type($task->{status});
189 return 1 if !$statusfilter->{$type};
190
191 return 1 if $count++ < $start;
192 return 1 if $limit <= 0;
193
194 return 0;
195 };
196
197 my $parse_line = sub {
198 if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
199 my $upid = $1;
200 my $endtime = $3;
201 my $status = $5;
202 if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
203
204 $task->{upid} = $upid;
205 $task->{endtime} = hex($endtime) if $endtime;
206 $task->{status} = $status if $status;
207
208 $convert_token_task->($task);
209 if (!$filter_task->($task)) {
210 push @$res, $task;
211 $limit--;
212 }
213 }
214 }
215 };
216
217 if ($source eq 'active' || $source eq 'all') {
218 my $recent_tasks = PVE::INotify::read_file('active');
219 for my $task (@$recent_tasks) {
220 next if $task->{saved}; # archived task, already in index(.1)
221 if (!$filter_task->($task)) {
222 $task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived
223 push @$res, $task;
224 $limit--;
225 }
226 }
227 }
228
229 if ($source ne 'active') {
230 if (my $bw = File::ReadBackwards->new($filename)) {
231 while (defined ($line = $bw->readline)) {
232 &$parse_line();
233 }
234 $bw->close();
235 }
236 if (my $bw = File::ReadBackwards->new("$filename.1")) {
237 while (defined ($line = $bw->readline)) {
238 &$parse_line();
239 }
240 $bw->close();
241 }
242 }
243
244 $rpcenv->set_result_attrib('total', $count);
245
246 return $res;
247 }});
248
249 __PACKAGE__->register_method({
250 name => 'upid_index',
251 path => '{upid}',
252 method => 'GET',
253 description => '', # index helper
254 permissions => { user => 'all' },
255 parameters => {
256 additionalProperties => 0,
257 properties => {
258 node => get_standard_option('pve-node'),
259 upid => { type => 'string' },
260 }
261 },
262 returns => {
263 type => 'array',
264 items => {
265 type => "object",
266 properties => {},
267 },
268 links => [ { rel => 'child', href => "{name}" } ],
269 },
270 code => sub {
271 my ($param) = @_;
272
273 return [
274 { name => 'log' },
275 { name => 'status' }
276 ];
277 }});
278
279 __PACKAGE__->register_method({
280 name => 'stop_task',
281 path => '{upid}',
282 method => 'DELETE',
283 description => 'Stop a task.',
284 permissions => {
285 description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if the task does not belong to him.",
286 user => 'all',
287 },
288 protected => 1,
289 proxyto => 'node',
290 parameters => {
291 additionalProperties => 0,
292 properties => {
293 node => get_standard_option('pve-node'),
294 upid => { type => 'string' },
295 }
296 },
297 returns => { type => 'null' },
298 code => sub {
299 my ($param) = @_;
300
301 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
302 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
303 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
304
305 my $rpcenv = PVE::RPCEnvironment::get();
306 my $user = $rpcenv->get_user();
307 my $node = $param->{node};
308
309 $convert_token_task->($task);
310
311 if (!$check_task_user->($task, $user)) {
312 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
313 }
314
315 PVE::RPCEnvironment->check_worker($param->{upid}, 1);
316
317 return undef;
318 }});
319
320 __PACKAGE__->register_method({
321 name => 'read_task_log',
322 path => '{upid}/log',
323 method => 'GET',
324 permissions => {
325 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.",
326 user => 'all',
327 },
328 protected => 1,
329 description => "Read task log.",
330 proxyto => 'node',
331 parameters => {
332 additionalProperties => 0,
333 properties => {
334 node => get_standard_option('pve-node'),
335 upid => { type => 'string' },
336 start => {
337 type => 'integer',
338 minimum => 0,
339 default => 0,
340 optional => 1,
341 },
342 limit => {
343 type => 'integer',
344 minimum => 0,
345 default => 50,
346 optional => 1,
347 },
348 },
349 },
350 returns => {
351 type => 'array',
352 items => {
353 type => "object",
354 properties => {
355 n => {
356 description=> "Line number",
357 type=> 'integer',
358 },
359 t => {
360 description=> "Line text",
361 type => 'string',
362 }
363 }
364 }
365 },
366 code => sub {
367 my ($param) = @_;
368
369 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
370 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
371
372 my $rpcenv = PVE::RPCEnvironment::get();
373 my $user = $rpcenv->get_user();
374 my $node = $param->{node};
375 my $start = $param->{start} // 0;
376 my $limit = $param->{limit} // 50;
377
378 $convert_token_task->($task);
379
380 if (!$check_task_user->($task, $user)) {
381 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
382 }
383
384 my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
385
386 $rpcenv->set_result_attrib('total', $count);
387
388 return $lines;
389 }});
390
391
392 my $exit_status_cache = {};
393
394 __PACKAGE__->register_method({
395 name => 'read_task_status',
396 path => '{upid}/status',
397 method => 'GET',
398 permissions => {
399 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.",
400 user => 'all',
401 },
402 protected => 1,
403 description => "Read task status.",
404 proxyto => 'node',
405 parameters => {
406 additionalProperties => 0,
407 properties => {
408 node => get_standard_option('pve-node'),
409 upid => { type => 'string' },
410 },
411 },
412 returns => {
413 type => "object",
414 properties => {
415 pid => {
416 type => 'integer'
417 },
418 status => {
419 type => 'string', enum => ['running', 'stopped'],
420 },
421 type => {
422 type => 'string',
423 },
424 id => {
425 type => 'string',
426 },
427 user => {
428 type => 'string',
429 },
430 exitstatus => {
431 type => 'string',
432 optional => 1,
433 },
434 upid => {
435 type => 'string',
436 },
437 starttime => {
438 type => 'number',
439 },
440 node => {
441 type => 'string',
442 },
443 },
444 },
445 code => sub {
446 my ($param) = @_;
447
448 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
449 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
450 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
451
452 my $lines = [];
453
454 my $rpcenv = PVE::RPCEnvironment::get();
455 my $user = $rpcenv->get_user();
456 my $node = $param->{node};
457
458 $convert_token_task->($task);
459
460 if (!$check_task_user->($task, $user)) {
461 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
462 }
463
464 my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
465 $task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
466 'running' : 'stopped';
467
468 $task->{upid} = $param->{upid}; # include upid
469
470 if ($task->{status} eq 'stopped') {
471 if (!defined($exit_status_cache->{$task->{upid}})) {
472 $exit_status_cache->{$task->{upid}} =
473 PVE::Tools::upid_read_status($task->{upid});
474 }
475 $task->{exitstatus} = $exit_status_cache->{$task->{upid}};
476 }
477
478 return $task;
479 }});
480
481 1;