mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-04-29 13:18:53 +00:00

so that they are documented and get displayed by pvesh/pvenode all those fields must exists (since they come from the upid) aside from the exitstatus, so marking that as optional forum user reported that they are missing: https://forum.proxmox.com/threads/ergebnis-eines-tasks-per-api-abfragen.92267/ Signed-off-by: Dominik Csapak <d.csapak@proxmox.com> (cherry picked from commit 9777a9c4ae69b2f799c132bbc4086214d01b6ce8) Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
439 lines
10 KiB
Perl
439 lines
10 KiB
Perl
package PVE::API2::Tasks;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use POSIX;
|
|
use IO::File;
|
|
use File::ReadBackwards;
|
|
use PVE::Tools;
|
|
use PVE::SafeSyslog;
|
|
use PVE::RESTHandler;
|
|
use PVE::ProcFSTools;
|
|
use PVE::RPCEnvironment;
|
|
use PVE::JSONSchema qw(get_standard_option);
|
|
use PVE::Exception qw(raise_param_exc);
|
|
use PVE::AccessControl;
|
|
|
|
use base qw(PVE::RESTHandler);
|
|
|
|
my $convert_token_task = sub {
|
|
my ($task) = @_;
|
|
|
|
if (PVE::AccessControl::pve_verify_tokenid($task->{user}, 1)) {
|
|
($task->{user}, $task->{tokenid}) = PVE::AccessControl::split_tokenid($task->{user});
|
|
}
|
|
};
|
|
|
|
my $check_task_user = sub {
|
|
my ($task, $user) = @_;
|
|
|
|
if ($task->{tokenid}) {
|
|
my $fulltoken = PVE::AccessControl::join_tokenid($task->{user}, $task->{tokenid});
|
|
# token only sees token tasks, user sees user + token tasks
|
|
return $user eq $fulltoken || $user eq $task->{user};
|
|
} else {
|
|
return $user eq $task->{user};
|
|
}
|
|
};
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'node_tasks',
|
|
path => '',
|
|
method => 'GET',
|
|
permissions => {
|
|
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).",
|
|
user => 'all'
|
|
},
|
|
description => "Read task list for one node (finished tasks).",
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
start => {
|
|
type => 'integer',
|
|
minimum => 0,
|
|
default => 0,
|
|
optional => 1,
|
|
description => "List tasks beginning from this offset.",
|
|
},
|
|
limit => {
|
|
type => 'integer',
|
|
minimum => 0,
|
|
default => 50,
|
|
optional => 1,
|
|
description => "Only list this amount of tasks.",
|
|
},
|
|
userfilter => {
|
|
type => 'string',
|
|
optional => 1,
|
|
description => "Only list tasks from this user.",
|
|
},
|
|
typefilter => {
|
|
type => 'string',
|
|
optional => 1,
|
|
description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
|
|
},
|
|
vmid => get_standard_option('pve-vmid', {
|
|
description => "Only list tasks for this VM.",
|
|
optional => 1,
|
|
}),
|
|
errors => {
|
|
type => 'boolean',
|
|
default => 0,
|
|
optional => 1,
|
|
},
|
|
source => {
|
|
type => 'string',
|
|
enum => ['archive', 'active', 'all'],
|
|
default => 'archive',
|
|
optional => 1,
|
|
description => 'List archived, active or all tasks.',
|
|
},
|
|
},
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => "object",
|
|
properties => {
|
|
upid => { type => 'string', title => 'UPID', },
|
|
node => { type => 'string', title => 'Node', },
|
|
pid => { type => 'integer', title => 'PID', },
|
|
pstart => { type => 'integer', },
|
|
starttime => { type => 'integer', title => 'Starttime', },
|
|
type => { type => 'string', title => 'Type', },
|
|
id => { type => 'string', title => 'ID', },
|
|
user => { type => 'string', title => 'User', },
|
|
endtime => { type => 'integer', optional => 1, title => 'Endtime', },
|
|
status => { type => 'string', optional => 1, title => 'Status', },
|
|
},
|
|
},
|
|
links => [ { rel => 'child', href => "{upid}" } ],
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $user = $rpcenv->get_user();
|
|
|
|
my $res = [];
|
|
|
|
my $filename = "/var/log/pve/tasks/index";
|
|
|
|
my $node = $param->{node};
|
|
my $start = $param->{start} // 0;
|
|
my $limit = $param->{limit} // 50;
|
|
my $userfilter = $param->{userfilter};
|
|
my $typefilter = $param->{typefilter};
|
|
my $errors = $param->{errors} // 0;
|
|
my $source = $param->{source} // 'archive';
|
|
|
|
my $count = 0;
|
|
my $line;
|
|
|
|
my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
|
|
|
|
my $filter_task = sub {
|
|
my $task = shift;
|
|
|
|
return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
|
|
return 1 if !($auditor || $check_task_user->($task, $user));
|
|
|
|
return 1 if $typefilter && $task->{type} ne $typefilter;
|
|
|
|
return 1 if $errors && $task->{status} && $task->{status} eq 'OK';
|
|
return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
|
|
|
|
return 1 if $count++ < $start;
|
|
return 1 if $limit <= 0;
|
|
|
|
return 0;
|
|
};
|
|
|
|
my $parse_line = sub {
|
|
if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
|
|
my $upid = $1;
|
|
my $endtime = $3;
|
|
my $status = $5;
|
|
if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
|
|
|
|
$task->{upid} = $upid;
|
|
$task->{endtime} = hex($endtime) if $endtime;
|
|
$task->{status} = $status if $status;
|
|
|
|
$convert_token_task->($task);
|
|
if (!$filter_task->($task)) {
|
|
push @$res, $task;
|
|
$limit--;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if ($source eq 'active' || $source eq 'all') {
|
|
my $recent_tasks = PVE::INotify::read_file('active');
|
|
for my $task (@$recent_tasks) {
|
|
next if $task->{saved}; # archived task, already in index(.1)
|
|
if (!$filter_task->($task)) {
|
|
$task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived
|
|
push @$res, $task;
|
|
$limit--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($source ne 'active') {
|
|
if (my $bw = File::ReadBackwards->new($filename)) {
|
|
while (defined ($line = $bw->readline)) {
|
|
&$parse_line();
|
|
}
|
|
$bw->close();
|
|
}
|
|
if (my $bw = File::ReadBackwards->new("$filename.1")) {
|
|
while (defined ($line = $bw->readline)) {
|
|
&$parse_line();
|
|
}
|
|
$bw->close();
|
|
}
|
|
}
|
|
|
|
$rpcenv->set_result_attrib('total', $count);
|
|
|
|
return $res;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'upid_index',
|
|
path => '{upid}',
|
|
method => 'GET',
|
|
description => '', # index helper
|
|
permissions => { user => 'all' },
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
upid => { type => 'string' },
|
|
}
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => "object",
|
|
properties => {},
|
|
},
|
|
links => [ { rel => 'child', href => "{name}" } ],
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
return [
|
|
{ name => 'log' },
|
|
{ name => 'status' }
|
|
];
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'stop_task',
|
|
path => '{upid}',
|
|
method => 'DELETE',
|
|
description => 'Stop a task.',
|
|
permissions => {
|
|
description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if the task does not belong to him.",
|
|
user => 'all',
|
|
},
|
|
protected => 1,
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
upid => { type => 'string' },
|
|
}
|
|
},
|
|
returns => { type => 'null' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
|
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
|
raise_param_exc({ upid => "no such task" }) if ! -f $filename;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $user = $rpcenv->get_user();
|
|
my $node = $param->{node};
|
|
|
|
$convert_token_task->($task);
|
|
|
|
if (!$check_task_user->($task, $user)) {
|
|
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
|
|
}
|
|
|
|
PVE::RPCEnvironment->check_worker($param->{upid}, 1);
|
|
|
|
return undef;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'read_task_log',
|
|
path => '{upid}/log',
|
|
method => 'GET',
|
|
permissions => {
|
|
description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.",
|
|
user => 'all',
|
|
},
|
|
protected => 1,
|
|
description => "Read task log.",
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
upid => { type => 'string' },
|
|
start => {
|
|
type => 'integer',
|
|
minimum => 0,
|
|
default => 0,
|
|
optional => 1,
|
|
},
|
|
limit => {
|
|
type => 'integer',
|
|
minimum => 0,
|
|
default => 50,
|
|
optional => 1,
|
|
},
|
|
},
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => "object",
|
|
properties => {
|
|
n => {
|
|
description=> "Line number",
|
|
type=> 'integer',
|
|
},
|
|
t => {
|
|
description=> "Line text",
|
|
type => 'string',
|
|
}
|
|
}
|
|
}
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
|
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $user = $rpcenv->get_user();
|
|
my $node = $param->{node};
|
|
my $start = $param->{start} // 0;
|
|
my $limit = $param->{limit} // 50;
|
|
|
|
$convert_token_task->($task);
|
|
|
|
if (!$check_task_user->($task, $user)) {
|
|
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
|
|
}
|
|
|
|
my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
|
|
|
|
$rpcenv->set_result_attrib('total', $count);
|
|
|
|
return $lines;
|
|
}});
|
|
|
|
|
|
my $exit_status_cache = {};
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'read_task_status',
|
|
path => '{upid}/status',
|
|
method => 'GET',
|
|
permissions => {
|
|
description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.",
|
|
user => 'all',
|
|
},
|
|
protected => 1,
|
|
description => "Read task status.",
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
upid => { type => 'string' },
|
|
},
|
|
},
|
|
returns => {
|
|
type => "object",
|
|
properties => {
|
|
pid => {
|
|
type => 'integer'
|
|
},
|
|
status => {
|
|
type => 'string', enum => ['running', 'stopped'],
|
|
},
|
|
type => {
|
|
type => 'string',
|
|
},
|
|
id => {
|
|
type => 'string',
|
|
},
|
|
user => {
|
|
type => 'string',
|
|
},
|
|
exitstatus => {
|
|
type => 'string',
|
|
optional => 1,
|
|
},
|
|
upid => {
|
|
type => 'string',
|
|
},
|
|
starttime => {
|
|
type => 'number',
|
|
},
|
|
node => {
|
|
type => 'string',
|
|
},
|
|
},
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
|
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
|
raise_param_exc({ upid => "no such task" }) if ! -f $filename;
|
|
|
|
my $lines = [];
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $user = $rpcenv->get_user();
|
|
my $node = $param->{node};
|
|
|
|
$convert_token_task->($task);
|
|
|
|
if (!$check_task_user->($task, $user)) {
|
|
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
|
|
}
|
|
|
|
my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
|
|
$task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
|
|
'running' : 'stopped';
|
|
|
|
$task->{upid} = $param->{upid}; # include upid
|
|
|
|
if ($task->{status} eq 'stopped') {
|
|
if (!defined($exit_status_cache->{$task->{upid}})) {
|
|
$exit_status_cache->{$task->{upid}} =
|
|
PVE::Tools::upid_read_status($task->{upid});
|
|
}
|
|
$task->{exitstatus} = $exit_status_cache->{$task->{upid}};
|
|
}
|
|
|
|
return $task;
|
|
}});
|
|
|
|
1;
|