]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/API2/Tasks.pm
bump version to 5.4-15
[pve-manager.git] / PVE / API2 / Tasks.pm
index 1b83f2cef76c7ed2574b1084c032cf28b8b426cc..73e398eb5117bb44486b70b83ae9d9b3626d49ca 100644 (file)
@@ -11,15 +11,19 @@ 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);
 
 __PACKAGE__->register_method({
-    name => 'node_tasks', 
-    path => '', 
+    name => 'node_tasks',
+    path => '',
     method => 'GET',
-    permissions => { user => 'all' },
+    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 => {
@@ -29,21 +33,43 @@ __PACKAGE__->register_method({
            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 => {
@@ -51,7 +77,16 @@ __PACKAGE__->register_method({
        items => {
            type => "object",
            properties => {
-               upid => { type => 'string' },
+               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}" } ],
@@ -67,15 +102,34 @@ __PACKAGE__->register_method({
        my $filename = "/var/log/pve/tasks/index";
 
        my $node = $param->{node};
-       my $start = $param->{start} || 0;
-       my $limit = $param->{limit} || 50;
+       my $start = $param->{start} // 0;
+       my $limit = $param->{limit} // 50;
        my $userfilter = $param->{userfilter};
-       my $errors = $param->{errors};
+       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' ]);
+       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 || $user eq $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.*))?)?$/) {
@@ -83,44 +137,54 @@ __PACKAGE__->register_method({
                my $endtime = $3;
                my $status = $5;
                if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
-                   return if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
-                   next if !($auditor || $user eq $task->{user});
-
-                   return if $errors && $status && $status eq 'OK';
-
-                   return if $count++ < $start;
-                   return if $limit <= 0;
 
                    $task->{upid} = $upid;
                    $task->{endtime} = hex($endtime) if $endtime;
                    $task->{status} = $status if $status;
-                   push @$res, $task;
-                   $limit--;
+
+                   if (!$filter_task->($task)) {
+                       push @$res, $task;
+                       $limit--;
+                   }
                }
            }
        };
 
-       if (my $bw = File::ReadBackwards->new($filename)) {     
-           while (defined ($line = $bw->readline)) {
-               &$parse_line();
+       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--;
+               }
            }
-           $bw->close();
        }
-       if (my $bw = File::ReadBackwards->new("$filename.1")) { 
-           while (defined ($line = $bw->readline)) {
-               &$parse_line();
+
+       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();
            }
-           $bw->close();
        }
 
-       $rpcenv->set_result_count($count);
+       $rpcenv->set_result_attrib('total', $count);
 
        return $res;
     }});
 
 __PACKAGE__->register_method({
-    name => 'upid_index', 
-    path => '{upid}', 
+    name => 'upid_index',
+    path => '{upid}',
     method => 'GET',
     description => '', # index helper
     permissions => { user => 'all' },
@@ -149,11 +213,14 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'stop_task', 
-    path => '{upid}', 
+    name => 'stop_task',
+    path => '{upid}',
     method => 'DELETE',
     description => 'Stop a task.',
-    permissions => { user => 'all' },
+    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 => {
@@ -175,20 +242,23 @@ __PACKAGE__->register_method({
        my $user = $rpcenv->get_user();
        my $node = $param->{node};
 
-       my $sysadmin = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Console' ]);
-       die "Permission check failed\n"
-           if !($sysadmin || $user eq $task->{user});
+       if ($user ne $task->{user}) {
+           $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
+       }
 
-       PVE::RPCEnvironment::check_worker($param->{upid}, 1);
+       PVE::RPCEnvironment->check_worker($param->{upid}, 1);
 
        return undef;
     }});
 
 __PACKAGE__->register_method({
-    name => 'read_task_log', 
-    path => '{upid}/log', 
+    name => 'read_task_log',
+    path => '{upid}/log',
     method => 'GET',
-    permissions => { user => 'all' },
+    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',
@@ -200,18 +270,20 @@ __PACKAGE__->register_method({
            start => {
                type => 'integer',
                minimum => 0,
+               default => 0,
                optional => 1,
            },
            limit => {
                type => 'integer',
                minimum => 0,
+               default => 50,
                optional => 1,
            },
        },
     },
     returns => {
        type => 'array',
-       items => { 
+       items => {
            type => "object",
            properties => {
                n => {
@@ -231,50 +303,34 @@ __PACKAGE__->register_method({
        my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
        raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
 
-       my $lines = [];
-
        my $rpcenv = PVE::RPCEnvironment::get();
        my $user = $rpcenv->get_user();
        my $node = $param->{node};
+       my $start = $param->{start} // 0;
+       my $limit = $param->{limit} // 50;
 
-       my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
-       die "Permission check failed\n"
-           if !($auditor || $user eq $task->{user});
-
-       my $fh = IO::File->new($filename, "r");
-       raise_param_exc({ upid => "no such task - unable to open file - $!" }) if !$fh;
-
-       my $start = $param->{start} || 0;
-       my $limit = $param->{limit} || 50;
-       my $count = 0;
-       my $line;
-       while (defined ($line = <$fh>)) {
-           next if $count++ < $start;
-           next if $limit <= 0;
-           chomp $line;
-           push @$lines, { n => $count, t => $line};
-           $limit--;
+       if ($user ne $task->{user})  {
+           $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
        }
 
-       close($fh);
+       my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
 
-       # HACK: ExtJS store.guaranteeRange() does not like empty array
-       # so we add a line
-       if (!$count) {
-           $count++;
-           push @$lines, { n => $count, t => "no content"};
-       }
+       $rpcenv->set_result_attrib('total', $count);
 
-       $rpcenv->set_result_count($count);
-           
        return $lines;
     }});
 
+
+my $exit_status_cache = {};
+
 __PACKAGE__->register_method({
-    name => 'read_task_status', 
-    path => '{upid}/status', 
+    name => 'read_task_status',
+    path => '{upid}/status',
     method => 'GET',
-    permissions => { user => 'all' },
+    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',
@@ -288,11 +344,11 @@ __PACKAGE__->register_method({
     returns => {
        type => "object",
        properties => {
-           pid => { 
+           pid => {
                type => 'integer'
            },
-           status => { 
-               type => 'string', enum => ['running', 'stopped'], 
+           status => {
+               type => 'string', enum => ['running', 'stopped'],
            },
        },
     },
@@ -309,9 +365,9 @@ __PACKAGE__->register_method({
        my $user = $rpcenv->get_user();
        my $node = $param->{node};
 
-       my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
-       die "Permission check failed\n"
-           if !($auditor || $user eq $task->{user});
+       if ($user ne $task->{user}) {
+           $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
+       }
 
        my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
        $task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
@@ -319,5 +375,15 @@ __PACKAGE__->register_method({
 
        $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;