]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
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::AccessControl; | |
15 | ||
16 | use base qw(PVE::RESTHandler); | |
17 | ||
18 | __PACKAGE__->register_method({ | |
19 | name => 'node_tasks', | |
20 | path => '', | |
21 | method => 'GET', | |
00cc94d3 DM |
22 | permissions => { |
23 | 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).", | |
24 | user => 'all' | |
25 | }, | |
aff192e6 DM |
26 | description => "Read task list for one node (finished tasks).", |
27 | proxyto => 'node', | |
28 | parameters => { | |
29 | additionalProperties => 0, | |
30 | properties => { | |
31 | node => get_standard_option('pve-node'), | |
32 | start => { | |
33 | type => 'integer', | |
34 | minimum => 0, | |
35 | optional => 1, | |
36 | }, | |
37 | limit => { | |
38 | type => 'integer', | |
39 | minimum => 0, | |
40 | optional => 1, | |
41 | }, | |
42 | userfilter => { | |
43 | type => 'string', | |
44 | optional => 1, | |
45 | }, | |
5614bec0 DM |
46 | vmid => get_standard_option('pve-vmid', { |
47 | description => "Only list tasks for this VM.", | |
48 | optional => 1 | |
49 | }), | |
aff192e6 DM |
50 | errors => { |
51 | type => 'boolean', | |
52 | optional => 1, | |
53 | }, | |
54 | }, | |
55 | }, | |
56 | returns => { | |
57 | type => 'array', | |
58 | items => { | |
59 | type => "object", | |
60 | properties => { | |
61 | upid => { type => 'string' }, | |
62 | }, | |
63 | }, | |
64 | links => [ { rel => 'child', href => "{upid}" } ], | |
65 | }, | |
66 | code => sub { | |
67 | my ($param) = @_; | |
68 | ||
69 | my $rpcenv = PVE::RPCEnvironment::get(); | |
70 | my $user = $rpcenv->get_user(); | |
71 | ||
72 | my $res = []; | |
73 | ||
74 | my $filename = "/var/log/pve/tasks/index"; | |
75 | ||
76 | my $node = $param->{node}; | |
77 | my $start = $param->{start} || 0; | |
78 | my $limit = $param->{limit} || 50; | |
79 | my $userfilter = $param->{userfilter}; | |
80 | my $errors = $param->{errors}; | |
81 | ||
82 | my $count = 0; | |
83 | my $line; | |
84 | ||
e4d554ba | 85 | my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1); |
aff192e6 DM |
86 | |
87 | my $parse_line = sub { | |
88 | if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) { | |
89 | my $upid = $1; | |
90 | my $endtime = $3; | |
91 | my $status = $5; | |
92 | if ((my $task = PVE::Tools::upid_decode($upid, 1))) { | |
93 | return if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i; | |
92fd515b | 94 | return if !($auditor || $user eq $task->{user}); |
aff192e6 DM |
95 | |
96 | return if $errors && $status && $status eq 'OK'; | |
97 | ||
5614bec0 DM |
98 | return if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid}); |
99 | ||
aff192e6 DM |
100 | return if $count++ < $start; |
101 | return if $limit <= 0; | |
102 | ||
103 | $task->{upid} = $upid; | |
104 | $task->{endtime} = hex($endtime) if $endtime; | |
105 | $task->{status} = $status if $status; | |
106 | push @$res, $task; | |
107 | $limit--; | |
108 | } | |
109 | } | |
110 | }; | |
111 | ||
112 | if (my $bw = File::ReadBackwards->new($filename)) { | |
113 | while (defined ($line = $bw->readline)) { | |
114 | &$parse_line(); | |
115 | } | |
116 | $bw->close(); | |
117 | } | |
118 | if (my $bw = File::ReadBackwards->new("$filename.1")) { | |
119 | while (defined ($line = $bw->readline)) { | |
120 | &$parse_line(); | |
121 | } | |
122 | $bw->close(); | |
123 | } | |
124 | ||
e09058af | 125 | $rpcenv->set_result_attrib('total', $count); |
aff192e6 DM |
126 | |
127 | return $res; | |
128 | }}); | |
129 | ||
130 | __PACKAGE__->register_method({ | |
131 | name => 'upid_index', | |
132 | path => '{upid}', | |
133 | method => 'GET', | |
134 | description => '', # index helper | |
135 | permissions => { user => 'all' }, | |
136 | parameters => { | |
137 | additionalProperties => 0, | |
138 | properties => { | |
139 | node => get_standard_option('pve-node'), | |
140 | upid => { type => 'string' }, | |
141 | } | |
142 | }, | |
143 | returns => { | |
144 | type => 'array', | |
145 | items => { | |
146 | type => "object", | |
147 | properties => {}, | |
148 | }, | |
149 | links => [ { rel => 'child', href => "{name}" } ], | |
150 | }, | |
151 | code => sub { | |
152 | my ($param) = @_; | |
153 | ||
154 | return [ | |
155 | { name => 'log' }, | |
156 | { name => 'status' } | |
157 | ]; | |
158 | }}); | |
159 | ||
160 | __PACKAGE__->register_method({ | |
161 | name => 'stop_task', | |
162 | path => '{upid}', | |
163 | method => 'DELETE', | |
164 | description => 'Stop a task.', | |
00cc94d3 DM |
165 | permissions => { |
166 | description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if the task does not belong to him.", | |
167 | user => 'all', | |
168 | }, | |
aff192e6 DM |
169 | protected => 1, |
170 | proxyto => 'node', | |
171 | parameters => { | |
172 | additionalProperties => 0, | |
173 | properties => { | |
174 | node => get_standard_option('pve-node'), | |
175 | upid => { type => 'string' }, | |
176 | } | |
177 | }, | |
178 | returns => { type => 'null' }, | |
179 | code => sub { | |
180 | my ($param) = @_; | |
181 | ||
182 | my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); | |
183 | raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; | |
184 | raise_param_exc({ upid => "no such task" }) if ! -f $filename; | |
185 | ||
186 | my $rpcenv = PVE::RPCEnvironment::get(); | |
187 | my $user = $rpcenv->get_user(); | |
188 | my $node = $param->{node}; | |
189 | ||
e4d554ba | 190 | if ($user ne $task->{user}) { |
00cc94d3 | 191 | $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]); |
e4d554ba | 192 | } |
aff192e6 | 193 | |
c5693297 | 194 | PVE::RPCEnvironment::check_worker($param->{upid}, 1); |
aff192e6 DM |
195 | |
196 | return undef; | |
197 | }}); | |
198 | ||
199 | __PACKAGE__->register_method({ | |
200 | name => 'read_task_log', | |
201 | path => '{upid}/log', | |
202 | method => 'GET', | |
00cc94d3 DM |
203 | permissions => { |
204 | description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.", | |
205 | user => 'all', | |
206 | }, | |
aff192e6 DM |
207 | protected => 1, |
208 | description => "Read task log.", | |
209 | proxyto => 'node', | |
210 | parameters => { | |
211 | additionalProperties => 0, | |
212 | properties => { | |
213 | node => get_standard_option('pve-node'), | |
214 | upid => { type => 'string' }, | |
215 | start => { | |
216 | type => 'integer', | |
217 | minimum => 0, | |
218 | optional => 1, | |
219 | }, | |
220 | limit => { | |
221 | type => 'integer', | |
222 | minimum => 0, | |
223 | optional => 1, | |
224 | }, | |
225 | }, | |
226 | }, | |
227 | returns => { | |
228 | type => 'array', | |
229 | items => { | |
230 | type => "object", | |
231 | properties => { | |
232 | n => { | |
233 | description=> "Line number", | |
234 | type=> 'integer', | |
235 | }, | |
236 | t => { | |
237 | description=> "Line text", | |
238 | type => 'string', | |
239 | } | |
240 | } | |
241 | } | |
242 | }, | |
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 | ||
249 | my $lines = []; | |
250 | ||
251 | my $rpcenv = PVE::RPCEnvironment::get(); | |
252 | my $user = $rpcenv->get_user(); | |
253 | my $node = $param->{node}; | |
254 | ||
e4d554ba DM |
255 | if ($user ne $task->{user}) { |
256 | $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]); | |
257 | } | |
aff192e6 DM |
258 | |
259 | my $fh = IO::File->new($filename, "r"); | |
260 | raise_param_exc({ upid => "no such task - unable to open file - $!" }) if !$fh; | |
261 | ||
262 | my $start = $param->{start} || 0; | |
263 | my $limit = $param->{limit} || 50; | |
264 | my $count = 0; | |
265 | my $line; | |
266 | while (defined ($line = <$fh>)) { | |
267 | next if $count++ < $start; | |
268 | next if $limit <= 0; | |
269 | chomp $line; | |
270 | push @$lines, { n => $count, t => $line}; | |
271 | $limit--; | |
272 | } | |
273 | ||
274 | close($fh); | |
275 | ||
276 | # HACK: ExtJS store.guaranteeRange() does not like empty array | |
277 | # so we add a line | |
278 | if (!$count) { | |
279 | $count++; | |
280 | push @$lines, { n => $count, t => "no content"}; | |
281 | } | |
282 | ||
e09058af | 283 | $rpcenv->set_result_attrib('total', $count); |
aff192e6 DM |
284 | |
285 | return $lines; | |
286 | }}); | |
287 | ||
47a5865a DM |
288 | |
289 | my $exit_status_cache = {}; | |
290 | ||
aff192e6 DM |
291 | __PACKAGE__->register_method({ |
292 | name => 'read_task_status', | |
293 | path => '{upid}/status', | |
294 | method => 'GET', | |
00cc94d3 DM |
295 | permissions => { |
296 | description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if the task does not belong to him.", | |
297 | user => 'all', | |
298 | }, | |
aff192e6 DM |
299 | protected => 1, |
300 | description => "Read task status.", | |
301 | proxyto => 'node', | |
302 | parameters => { | |
303 | additionalProperties => 0, | |
304 | properties => { | |
305 | node => get_standard_option('pve-node'), | |
306 | upid => { type => 'string' }, | |
307 | }, | |
308 | }, | |
309 | returns => { | |
310 | type => "object", | |
311 | properties => { | |
312 | pid => { | |
313 | type => 'integer' | |
314 | }, | |
315 | status => { | |
316 | type => 'string', enum => ['running', 'stopped'], | |
317 | }, | |
318 | }, | |
319 | }, | |
320 | code => sub { | |
321 | my ($param) = @_; | |
322 | ||
323 | my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); | |
324 | raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; | |
325 | raise_param_exc({ upid => "no such task" }) if ! -f $filename; | |
326 | ||
327 | my $lines = []; | |
328 | ||
329 | my $rpcenv = PVE::RPCEnvironment::get(); | |
330 | my $user = $rpcenv->get_user(); | |
331 | my $node = $param->{node}; | |
332 | ||
e4d554ba DM |
333 | if ($user ne $task->{user}) { |
334 | $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]); | |
335 | } | |
aff192e6 DM |
336 | |
337 | my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid}); | |
338 | $task->{status} = ($pstart && ($pstart == $task->{pstart})) ? | |
339 | 'running' : 'stopped'; | |
340 | ||
341 | $task->{upid} = $param->{upid}; # include upid | |
342 | ||
47a5865a DM |
343 | if ($task->{status} eq 'stopped') { |
344 | if (!defined($exit_status_cache->{$task->{upid}})) { | |
345 | $exit_status_cache->{$task->{upid}} = | |
346 | PVE::Tools::upid_read_status($task->{upid}); | |
347 | } | |
348 | $task->{exitstatus} = $exit_status_cache->{$task->{upid}}; | |
349 | } | |
350 | ||
aff192e6 DM |
351 | return $task; |
352 | }}); |