]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Tasks.pm
api/tasks: attribute token tasks to user
[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 __PACKAGE__->register_method({
28 name => 'node_tasks',
29 path => '',
30 method => 'GET',
31 permissions => {
32 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).",
33 user => 'all'
34 },
35 description => "Read task list for one node (finished tasks).",
36 proxyto => 'node',
37 parameters => {
38 additionalProperties => 0,
39 properties => {
40 node => get_standard_option('pve-node'),
41 start => {
42 type => 'integer',
43 minimum => 0,
44 default => 0,
45 optional => 1,
46 description => "List tasks beginning from this offset.",
47 },
48 limit => {
49 type => 'integer',
50 minimum => 0,
51 default => 50,
52 optional => 1,
53 description => "Only list this amount of tasks.",
54 },
55 userfilter => {
56 type => 'string',
57 optional => 1,
58 description => "Only list tasks from this user.",
59 },
60 typefilter => {
61 type => 'string',
62 optional => 1,
63 description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
64 },
65 vmid => get_standard_option('pve-vmid', {
66 description => "Only list tasks for this VM.",
67 optional => 1,
68 }),
69 errors => {
70 type => 'boolean',
71 default => 0,
72 optional => 1,
73 },
74 source => {
75 type => 'string',
76 enum => ['archive', 'active', 'all'],
77 default => 'archive',
78 optional => 1,
79 description => 'List archived, active or all tasks.',
80 },
81 },
82 },
83 returns => {
84 type => 'array',
85 items => {
86 type => "object",
87 properties => {
88 upid => { type => 'string', title => 'UPID', },
89 node => { type => 'string', title => 'Node', },
90 pid => { type => 'integer', title => 'PID', },
91 pstart => { type => 'integer', },
92 starttime => { type => 'integer', title => 'Starttime', },
93 type => { type => 'string', title => 'Type', },
94 id => { type => 'string', title => 'ID', },
95 user => { type => 'string', title => 'User', },
96 endtime => { type => 'integer', optional => 1, title => 'Endtime', },
97 status => { type => 'string', optional => 1, title => 'Status', },
98 },
99 },
100 links => [ { rel => 'child', href => "{upid}" } ],
101 },
102 code => sub {
103 my ($param) = @_;
104
105 my $rpcenv = PVE::RPCEnvironment::get();
106 my $user = $rpcenv->get_user();
107
108 my $res = [];
109
110 my $filename = "/var/log/pve/tasks/index";
111
112 my $node = $param->{node};
113 my $start = $param->{start} // 0;
114 my $limit = $param->{limit} // 50;
115 my $userfilter = $param->{userfilter};
116 my $typefilter = $param->{typefilter};
117 my $errors = $param->{errors} // 0;
118 my $source = $param->{source} // 'archive';
119
120 my $count = 0;
121 my $line;
122
123 my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
124
125 my $filter_task = sub {
126 my $task = shift;
127
128 return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
129 return 1 if !($auditor || $user eq $task->{user});
130
131 return 1 if $typefilter && $task->{type} ne $typefilter;
132
133 return 1 if $errors && $task->{status} && $task->{status} eq 'OK';
134 return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
135
136 return 1 if $count++ < $start;
137 return 1 if $limit <= 0;
138
139 return 0;
140 };
141
142 my $parse_line = sub {
143 if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
144 my $upid = $1;
145 my $endtime = $3;
146 my $status = $5;
147 if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
148
149 $task->{upid} = $upid;
150 $task->{endtime} = hex($endtime) if $endtime;
151 $task->{status} = $status if $status;
152
153 $convert_token_task->($task);
154 if (!$filter_task->($task)) {
155 push @$res, $task;
156 $limit--;
157 }
158 }
159 }
160 };
161
162 if ($source eq 'active' || $source eq 'all') {
163 my $recent_tasks = PVE::INotify::read_file('active');
164 for my $task (@$recent_tasks) {
165 next if $task->{saved}; # archived task, already in index(.1)
166 if (!$filter_task->($task)) {
167 $task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived
168 push @$res, $task;
169 $limit--;
170 }
171 }
172 }
173
174 if ($source ne 'active') {
175 if (my $bw = File::ReadBackwards->new($filename)) {
176 while (defined ($line = $bw->readline)) {
177 &$parse_line();
178 }
179 $bw->close();
180 }
181 if (my $bw = File::ReadBackwards->new("$filename.1")) {
182 while (defined ($line = $bw->readline)) {
183 &$parse_line();
184 }
185 $bw->close();
186 }
187 }
188
189 $rpcenv->set_result_attrib('total', $count);
190
191 return $res;
192 }});
193
194 __PACKAGE__->register_method({
195 name => 'upid_index',
196 path => '{upid}',
197 method => 'GET',
198 description => '', # index helper
199 permissions => { user => 'all' },
200 parameters => {
201 additionalProperties => 0,
202 properties => {
203 node => get_standard_option('pve-node'),
204 upid => { type => 'string' },
205 }
206 },
207 returns => {
208 type => 'array',
209 items => {
210 type => "object",
211 properties => {},
212 },
213 links => [ { rel => 'child', href => "{name}" } ],
214 },
215 code => sub {
216 my ($param) = @_;
217
218 return [
219 { name => 'log' },
220 { name => 'status' }
221 ];
222 }});
223
224 __PACKAGE__->register_method({
225 name => 'stop_task',
226 path => '{upid}',
227 method => 'DELETE',
228 description => 'Stop a task.',
229 permissions => {
230 description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if the task does not belong to him.",
231 user => 'all',
232 },
233 protected => 1,
234 proxyto => 'node',
235 parameters => {
236 additionalProperties => 0,
237 properties => {
238 node => get_standard_option('pve-node'),
239 upid => { type => 'string' },
240 }
241 },
242 returns => { type => 'null' },
243 code => sub {
244 my ($param) = @_;
245
246 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
247 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
248 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
249
250 my $rpcenv = PVE::RPCEnvironment::get();
251 my $user = $rpcenv->get_user();
252 my $node = $param->{node};
253
254 $convert_token_task->($task);
255
256 if ($user ne $task->{user}) {
257 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
258 }
259
260 PVE::RPCEnvironment->check_worker($param->{upid}, 1);
261
262 return undef;
263 }});
264
265 __PACKAGE__->register_method({
266 name => 'read_task_log',
267 path => '{upid}/log',
268 method => 'GET',
269 permissions => {
270 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.",
271 user => 'all',
272 },
273 protected => 1,
274 description => "Read task log.",
275 proxyto => 'node',
276 parameters => {
277 additionalProperties => 0,
278 properties => {
279 node => get_standard_option('pve-node'),
280 upid => { type => 'string' },
281 start => {
282 type => 'integer',
283 minimum => 0,
284 default => 0,
285 optional => 1,
286 },
287 limit => {
288 type => 'integer',
289 minimum => 0,
290 default => 50,
291 optional => 1,
292 },
293 },
294 },
295 returns => {
296 type => 'array',
297 items => {
298 type => "object",
299 properties => {
300 n => {
301 description=> "Line number",
302 type=> 'integer',
303 },
304 t => {
305 description=> "Line text",
306 type => 'string',
307 }
308 }
309 }
310 },
311 code => sub {
312 my ($param) = @_;
313
314 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
315 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
316
317 my $rpcenv = PVE::RPCEnvironment::get();
318 my $user = $rpcenv->get_user();
319 my $node = $param->{node};
320 my $start = $param->{start} // 0;
321 my $limit = $param->{limit} // 50;
322
323 $convert_token_task->($task);
324
325 if ($user ne $task->{user}) {
326 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
327 }
328
329 my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
330
331 $rpcenv->set_result_attrib('total', $count);
332
333 return $lines;
334 }});
335
336
337 my $exit_status_cache = {};
338
339 __PACKAGE__->register_method({
340 name => 'read_task_status',
341 path => '{upid}/status',
342 method => 'GET',
343 permissions => {
344 description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.",
345 user => 'all',
346 },
347 protected => 1,
348 description => "Read task status.",
349 proxyto => 'node',
350 parameters => {
351 additionalProperties => 0,
352 properties => {
353 node => get_standard_option('pve-node'),
354 upid => { type => 'string' },
355 },
356 },
357 returns => {
358 type => "object",
359 properties => {
360 pid => {
361 type => 'integer'
362 },
363 status => {
364 type => 'string', enum => ['running', 'stopped'],
365 },
366 },
367 },
368 code => sub {
369 my ($param) = @_;
370
371 my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
372 raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
373 raise_param_exc({ upid => "no such task" }) if ! -f $filename;
374
375 my $lines = [];
376
377 my $rpcenv = PVE::RPCEnvironment::get();
378 my $user = $rpcenv->get_user();
379 my $node = $param->{node};
380
381 $convert_token_task->($task);
382
383 if ($user ne $task->{user}) {
384 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
385 }
386
387 my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
388 $task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
389 'running' : 'stopped';
390
391 $task->{upid} = $param->{upid}; # include upid
392
393 if ($task->{status} eq 'stopped') {
394 if (!defined($exit_status_cache->{$task->{upid}})) {
395 $exit_status_cache->{$task->{upid}} =
396 PVE::Tools::upid_read_status($task->{upid});
397 }
398 $task->{exitstatus} = $exit_status_cache->{$task->{upid}};
399 }
400
401 return $task;
402 }});
403
404 1;