1 package PVE
::API2
::Tasks
;
7 use File
::ReadBackwards
;
12 use PVE
::RPCEnvironment
;
13 use PVE
::JSONSchema
qw(get_standard_option);
14 use PVE
::Exception
qw(raise_param_exc);
15 use PVE
::AccessControl
;
17 use base
qw(PVE::RESTHandler);
19 my $convert_token_task = sub {
22 if (PVE
::AccessControl
::pve_verify_tokenid
($task->{user
}, 1)) {
23 ($task->{user
}, $task->{tokenid
}) = PVE
::AccessControl
::split_tokenid
($task->{user
});
27 my $check_task_user = sub {
28 my ($task, $user) = @_;
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
};
35 return $user eq $task->{user
};
39 __PACKAGE__-
>register_method({
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).",
47 description
=> "Read task list for one node (finished tasks).",
50 additionalProperties
=> 0,
52 node
=> get_standard_option
('pve-node'),
58 description
=> "List tasks beginning from this offset.",
65 description
=> "Only list this amount of tasks.",
70 description
=> "Only list tasks from this user.",
75 description
=> 'Only list tasks of this type (e.g., vzstart, vzdump).',
77 vmid
=> get_standard_option
('pve-vmid', {
78 description
=> "Only list tasks for this VM.",
85 description
=> 'Only list tasks with a status of ERROR.',
89 enum
=> ['archive', 'active', 'all'],
92 description
=> 'List archived, active or all tasks.',
96 description
=> "Only list tasks since this UNIX epoch.",
101 description
=> "Only list tasks until this UNIX epoch.",
106 format
=> 'pve-task-status-type-list',
108 description
=> 'List of Task States that should be returned.',
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', },
129 links
=> [ { rel
=> 'child', href
=> "{upid}" } ],
134 my $rpcenv = PVE
::RPCEnvironment
::get
();
135 my $user = $rpcenv->get_user();
139 my $filename = "/var/log/pve/tasks/index";
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};
157 if (defined($param->{statusfilter
}) && !$errors) {
164 for my $filter (PVE
::Tools
::split_list
($param->{statusfilter
})) {
165 $statusfilter->{lc($filter)} = 1 ;
168 $statusfilter->{ok
} = 0;
174 my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
176 my $filter_task = sub {
179 return 1 if $userfilter && $task->{user
} !~ m/\Q$userfilter\E/i;
180 return 1 if !($auditor || $check_task_user->($task, $user));
182 return 1 if $typefilter && $task->{type
} ne $typefilter;
184 return 1 if $param->{vmid
} && (!$task->{id
} || $task->{id
} ne $param->{vmid
});
186 return 1 if defined($since) && $task->{starttime
} < $since;
187 return 1 if defined($until) && $task->{starttime
} > $until;
189 my $type = PVE
::Tools
::upid_normalize_status_type
($task->{status
});
190 return 1 if !$statusfilter->{$type};
192 return 1 if $count++ < $start;
193 return 1 if $limit <= 0;
198 my $parse_line = sub {
199 if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
203 if ((my $task = PVE
::Tools
::upid_decode
($upid, 1))) {
205 $task->{upid
} = $upid;
206 $task->{endtime
} = hex($endtime) if $endtime;
207 $task->{status
} = $status if $status;
209 $convert_token_task->($task);
210 if (!$filter_task->($task)) {
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
230 if ($source ne 'active') {
231 if (my $bw = File
::ReadBackwards-
>new($filename)) {
232 while (defined ($line = $bw->readline)) {
237 if (my $bw = File
::ReadBackwards-
>new("$filename.1")) {
238 while (defined ($line = $bw->readline)) {
245 $rpcenv->set_result_attrib('total', $count);
250 __PACKAGE__-
>register_method({
251 name
=> 'upid_index',
254 description
=> '', # index helper
255 permissions
=> { user
=> 'all' },
257 additionalProperties
=> 0,
259 node
=> get_standard_option
('pve-node'),
260 upid
=> { type
=> 'string' },
269 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
280 __PACKAGE__-
>register_method({
284 description
=> 'Stop a task.',
286 description
=> "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if they aren't the owner of the task.",
292 additionalProperties
=> 0,
294 node
=> get_standard_option
('pve-node'),
295 upid
=> { type
=> 'string' },
298 returns
=> { type
=> 'null' },
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;
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
307 my $user = $rpcenv->get_user();
308 my $node = $param->{node
};
310 $convert_token_task->($task);
312 if (!$check_task_user->($task, $user)) {
313 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
316 PVE
::RPCEnvironment-
>check_worker($param->{upid
}, 1);
321 __PACKAGE__-
>register_method({
322 name
=> 'read_task_log',
323 path
=> '{upid}/log',
326 description
=> "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they aren't the owner of the task.",
330 description
=> "Read task log.",
333 additionalProperties
=> 0,
335 node
=> get_standard_option
('pve-node'),
338 description
=> "The task's unique ID.",
345 description
=> "Start at this line when reading the tasklog",
352 description
=> "The amount of lines to read from the tasklog.",
357 description
=> "Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters",
367 description
=> "Line number",
371 description
=> "Line text",
380 my ($task, $filename) = PVE
::Tools
::upid_decode
($param->{upid
}, 1);
381 raise_param_exc
({ upid
=> "unable to parse worker upid" }) if !$task;
383 my $rpcenv = PVE
::RPCEnvironment
::get
();
384 my $user = $rpcenv->get_user();
385 my $node = $param->{node
};
387 $convert_token_task->($task);
389 if (!$check_task_user->($task, $user)) {
390 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
393 if ($param->{download
}) {
394 if (defined($param->{start
}) || defined($param->{limit
})) {
395 die "'download' cannot be used together with 'start' or 'limit' parameters\n";
399 my $use_compression = ( -s
$filename ) > 1024;
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";
406 open($fh, '<', $filename) or die "Could not open file '$filename' - $!\n";
413 'content-encoding' => $use_compression ?
'gzip' : undef,
414 'content-type' => "text/plain",
415 'content-disposition' => "attachment; filename=\"".$param->{upid
}."\"",
419 my $start = $param->{start
} // 0;
420 my $limit = $param->{limit
} // 50;
422 my ($count, $lines) = PVE
::Tools
::dump_logfile
($filename, $start, $limit);
424 $rpcenv->set_result_attrib('total', $count);
431 my $exit_status_cache = {};
433 __PACKAGE__-
>register_method({
434 name
=> 'read_task_status',
435 path
=> '{upid}/status',
438 description
=> "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
442 description
=> "Read task status.",
445 additionalProperties
=> 0,
447 node
=> get_standard_option
('pve-node'),
450 description
=> "The task's unique ID.",
461 type
=> 'string', enum
=> ['running', 'stopped'],
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;
496 my $rpcenv = PVE
::RPCEnvironment
::get
();
497 my $user = $rpcenv->get_user();
498 my $node = $param->{node
};
500 $convert_token_task->($task);
502 if (!$check_task_user->($task, $user)) {
503 $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
506 my $pstart = PVE
::ProcFSTools
::read_proc_starttime
($task->{pid
});
507 $task->{status
} = ($pstart && ($pstart == $task->{pstart
})) ?
508 'running' : 'stopped';
510 $task->{upid
} = $param->{upid
}; # include upid
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
});
517 $task->{exitstatus
} = $exit_status_cache->{$task->{upid
}};