]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu/Agent.pm
schema: fix description of migrate_downtime parameter
[qemu-server.git] / PVE / API2 / Qemu / Agent.pm
CommitLineData
b8158701
DC
1package PVE::API2::Qemu::Agent;
2
3use strict;
4use warnings;
5
6use PVE::RESTHandler;
7use PVE::JSONSchema qw(get_standard_option);
8use PVE::QemuServer;
ccd16638 9use PVE::QemuServer::Agent qw(agent_available agent_cmd check_agent_error);
0a13e08e 10use PVE::QemuServer::Monitor qw(mon_cmd);
b428fb63
DC
11use MIME::Base64 qw(encode_base64 decode_base64);
12use JSON;
b8158701
DC
13
14use base qw(PVE::RESTHandler);
15
bb14060a
DC
16# max size for file-read over the api
17my $MAX_READ_SIZE = 16 * 1024 * 1024; # 16 MiB
18
ea2bceaf
DC
19# list of commands
20# will generate one api endpoint per command
21# needs a 'method' property and optionally a 'perms' property (default VM.Monitor)
22my $guest_agent_commands = {
23 'ping' => {
24 method => 'POST',
25 },
26 'get-time' => {
e6bd703b 27 method => 'GET',
ea2bceaf
DC
28 },
29 'info' => {
e6bd703b 30 method => 'GET',
ea2bceaf
DC
31 },
32 'fsfreeze-status' => {
33 method => 'POST',
34 },
35 'fsfreeze-freeze' => {
36 method => 'POST',
37 },
38 'fsfreeze-thaw' => {
39 method => 'POST',
40 },
41 'fstrim' => {
42 method => 'POST',
43 },
44 'network-get-interfaces' => {
e6bd703b 45 method => 'GET',
ea2bceaf
DC
46 },
47 'get-vcpus' => {
e6bd703b 48 method => 'GET',
ea2bceaf
DC
49 },
50 'get-fsinfo' => {
e6bd703b 51 method => 'GET',
ea2bceaf
DC
52 },
53 'get-memory-blocks' => {
e6bd703b 54 method => 'GET',
ea2bceaf
DC
55 },
56 'get-memory-block-info' => {
e6bd703b 57 method => 'GET',
ea2bceaf
DC
58 },
59 'suspend-hybrid' => {
60 method => 'POST',
61 },
62 'suspend-ram' => {
63 method => 'POST',
64 },
65 'suspend-disk' => {
66 method => 'POST',
67 },
68 'shutdown' => {
69 method => 'POST',
70 },
5667cc55
DC
71 # added since qemu 2.9
72 'get-host-name' => {
73 method => 'GET',
74 },
75 'get-osinfo' => {
76 method => 'GET',
77 },
78 'get-users' => {
79 method => 'GET',
80 },
81 'get-timezone' => {
82 method => 'GET',
83 },
ea2bceaf
DC
84};
85
ad1f73b9
DC
86__PACKAGE__->register_method({
87 name => 'index',
88 path => '',
89 proxyto => 'node',
90 method => 'GET',
7bd9abd2 91 description => "QEMU Guest Agent command index.",
ad1f73b9
DC
92 permissions => {
93 user => 'all',
94 },
95 parameters => {
96 additionalProperties => 1,
97 properties => {
98 node => get_standard_option('pve-node'),
99 vmid => get_standard_option('pve-vmid', {
100 completion => \&PVE::QemuServer::complete_vmid_running }),
101 },
102 },
103 returns => {
104 type => 'array',
105 items => {
106 type => "object",
107 properties => {},
108 },
109 links => [ { rel => 'child', href => '{name}' } ],
7bd9abd2 110 description => "Returns the list of QEMU Guest Agent commands",
ad1f73b9
DC
111 },
112 code => sub {
113 my ($param) = @_;
114
115 my $result = [];
116
b428fb63
DC
117 my $cmds = [keys %$guest_agent_commands];
118 push @$cmds, qw(
8efdf418
DC
119 exec
120 exec-status
bb14060a 121 file-read
735821a4 122 file-write
b428fb63
DC
123 set-user-password
124 );
125
126 for my $cmd ( sort @$cmds) {
ad1f73b9
DC
127 push @$result, { name => $cmd };
128 }
129
130 return $result;
131 }});
132
ea2bceaf
DC
133sub register_command {
134 my ($class, $command, $method, $perm) = @_;
135
136 die "no method given\n" if !$method;
137 die "no command given\n" if !defined($command);
138
139 my $permission;
140
141 if (ref($perm) eq 'HASH') {
142 $permission = $perm;
143 } else {
144 $perm //= 'VM.Monitor';
145 $permission = { check => [ 'perm', '/vms/{vmid}', [ $perm ]]};
146 }
147
148 my $parameters = {
b8158701
DC
149 additionalProperties => 0,
150 properties => {
151 node => get_standard_option('pve-node'),
152 vmid => get_standard_option('pve-vmid', {
c0acd873
TL
153 completion => \&PVE::QemuServer::complete_vmid_running,
154 }),
b8158701
DC
155 command => {
156 type => 'string',
157 description => "The QGA command.",
ea2bceaf 158 enum => [ sort keys %$guest_agent_commands ],
b8158701
DC
159 },
160 },
ea2bceaf
DC
161 };
162
7bd9abd2 163 my $description = "Execute QEMU Guest Agent commands.";
ea2bceaf
DC
164 my $name = 'agent';
165
166 if ($command ne '') {
167 $description = "Execute $command.";
168 $name = $command;
169 delete $parameters->{properties}->{command};
170 }
171
172 __PACKAGE__->register_method({
173 name => $name,
174 path => $command,
175 method => $method,
176 protected => 1,
177 proxyto => 'node',
178 description => $description,
179 permissions => $permission,
180 parameters => $parameters,
181 returns => {
182 type => 'object',
183 description => "Returns an object with a single `result` property.",
184 },
185 code => sub {
186 my ($param) = @_;
187
188 my $vmid = $param->{vmid};
b8158701 189
ea2bceaf 190 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
b8158701 191
3824765e 192 agent_available($vmid, $conf);
b8158701 193
ea2bceaf 194 my $cmd = $param->{command} // $command;
0a13e08e 195 my $res = mon_cmd($vmid, "guest-$cmd");
b8158701 196
ea2bceaf
DC
197 return { result => $res };
198 }});
199}
b8158701 200
ea2bceaf
DC
201# old {vmid}/agent POST endpoint, here for compatibility
202__PACKAGE__->register_command('', 'POST');
b8158701 203
ea2bceaf
DC
204for my $cmd (sort keys %$guest_agent_commands) {
205 my $props = $guest_agent_commands->{$cmd};
206 __PACKAGE__->register_command($cmd, $props->{method}, $props->{perms});
207}
b8158701 208
b428fb63
DC
209# commands with parameters are complicated and we want to register them manually
210__PACKAGE__->register_method({
211 name => 'set-user-password',
212 path => 'set-user-password',
213 method => 'POST',
214 protected => 1,
215 proxyto => 'node',
216 description => "Sets the password for the given user to the given password",
217 permissions => { check => [ 'perm', '/vms/{vmid}', [ 'VM.Monitor' ]]},
218 parameters => {
219 additionalProperties => 0,
220 properties => {
221 node => get_standard_option('pve-node'),
222 vmid => get_standard_option('pve-vmid', {
223 completion => \&PVE::QemuServer::complete_vmid_running }),
224 username => {
225 type => 'string',
226 description => 'The user to set the password for.'
227 },
228 password => {
229 type => 'string',
230 description => 'The new password.',
231 minLength => 5,
ba728fb5 232 maxLength => 1024,
b428fb63
DC
233 },
234 crypted => {
235 type => 'boolean',
236 description => 'set to 1 if the password has already been passed through crypt()',
237 optional => 1,
238 default => 0,
239 },
240 },
241 },
242 returns => {
243 type => 'object',
244 description => "Returns an object with a single `result` property.",
245 },
246 code => sub {
247 my ($param) = @_;
248
249 my $vmid = $param->{vmid};
250
251 my $crypted = $param->{crypted} // 0;
252 my $args = {
253 username => $param->{username},
254 password => encode_base64($param->{password}),
255 crypted => $crypted ? JSON::true : JSON::false,
256 };
50ecb1ba 257 my $res = agent_cmd($vmid, "set-user-password", $args, 'cannot set user password');
b428fb63
DC
258
259 return { result => $res };
260 }});
261
8efdf418
DC
262__PACKAGE__->register_method({
263 name => 'exec',
264 path => 'exec',
265 method => 'POST',
266 protected => 1,
267 proxyto => 'node',
268 description => "Executes the given command in the vm via the guest-agent and returns an object with the pid.",
269 permissions => { check => [ 'perm', '/vms/{vmid}', [ 'VM.Monitor' ]]},
270 parameters => {
271 additionalProperties => 0,
272 properties => {
273 node => get_standard_option('pve-node'),
274 vmid => get_standard_option('pve-vmid', {
275 completion => \&PVE::QemuServer::complete_vmid_running }),
276 command => {
1572afe8
DC
277 type => 'array',
278 description => 'The command as a list of program + arguments.',
279 items => {
280 format => 'string',
281 description => 'A single part of the program + arguments.',
282 }
d8f61794
SR
283 },
284 'input-data' => {
285 type => 'string',
109a0950 286 maxLength => 64 * 1024,
d8f61794
SR
287 description => "Data to pass as 'input-data' to the guest. Usually treated as STDIN to 'command'.",
288 optional => 1,
289 },
8efdf418
DC
290 },
291 },
292 returns => {
293 type => 'object',
294 properties => {
295 pid => {
296 type => 'integer',
297 description => "The PID of the process started by the guest-agent.",
298 },
299 },
300 },
301 code => sub {
302 my ($param) = @_;
303
304 my $vmid = $param->{vmid};
1572afe8 305 my $cmd = $param->{command};
8efdf418 306
d8f61794 307 my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $param->{'input-data'}, $cmd);
8efdf418
DC
308 return $res;
309 }});
310
311__PACKAGE__->register_method({
312 name => 'exec-status',
313 path => 'exec-status',
314 method => 'GET',
315 protected => 1,
316 proxyto => 'node',
317 description => "Gets the status of the given pid started by the guest-agent",
318 permissions => { check => [ 'perm', '/vms/{vmid}', [ 'VM.Monitor' ]]},
319 parameters => {
320 additionalProperties => 0,
321 properties => {
322 node => get_standard_option('pve-node'),
323 vmid => get_standard_option('pve-vmid', {
324 completion => \&PVE::QemuServer::complete_vmid_running }),
325 pid => {
326 type => 'integer',
327 description => 'The PID to query'
328 },
329 },
330 },
331 returns => {
332 type => 'object',
333 properties => {
334 exited => {
335 type => 'boolean',
336 description => 'Tells if the given command has exited yet.',
337 },
338 exitcode => {
339 type => 'integer',
340 optional => 1,
341 description => 'process exit code if it was normally terminated.',
342 },
343 signal=> {
344 type => 'integer',
345 optional => 1,
346 description => 'signal number or exception code if the process was abnormally terminated.',
347 },
348 'out-data' => {
349 type => 'string',
350 optional => 1,
351 description => 'stdout of the process',
352 },
353 'err-data' => {
354 type => 'string',
355 optional => 1,
356 description => 'stderr of the process',
357 },
358 'out-truncated' => {
359 type => 'boolean',
360 optional => 1,
361 description => 'true if stdout was not fully captured',
362 },
363 'err-truncated' => {
364 type => 'boolean',
365 optional => 1,
366 description => 'true if stderr was not fully captured',
367 },
368 },
369 },
370 code => sub {
371 my ($param) = @_;
372
373 my $vmid = $param->{vmid};
374 my $pid = int($param->{pid});
375
376 my $res = PVE::QemuServer::Agent::qemu_exec_status($vmid, $pid);
377
378 return $res;
379 }});
380
bb14060a
DC
381__PACKAGE__->register_method({
382 name => 'file-read',
383 path => 'file-read',
384 method => 'GET',
385 protected => 1,
386 proxyto => 'node',
387 description => "Reads the given file via guest agent. Is limited to $MAX_READ_SIZE bytes.",
388 permissions => { check => [ 'perm', '/vms/{vmid}', [ 'VM.Monitor' ]]},
389 parameters => {
390 additionalProperties => 0,
391 properties => {
392 node => get_standard_option('pve-node'),
393 vmid => get_standard_option('pve-vmid', {
394 completion => \&PVE::QemuServer::complete_vmid_running }),
395 file => {
396 type => 'string',
397 description => 'The path to the file'
398 },
399 },
400 },
401 returns => {
402 type => 'object',
403 description => "Returns an object with a `content` property.",
404 properties => {
405 content => {
406 type => 'string',
407 description => "The content of the file, maximum $MAX_READ_SIZE",
408 },
409 truncated => {
410 type => 'boolean',
411 optional => 1,
412 description => "If set to 1, the output is truncated and not complete"
413 }
414 },
415 },
416 code => sub {
417 my ($param) = @_;
418
419 my $vmid = $param->{vmid};
420
421 my $qgafh = agent_cmd($vmid, "file-open", { path => $param->{file} }, "can't open file");
422
423 my $bytes_left = $MAX_READ_SIZE;
424 my $eof = 0;
425 my $read_size = 1024*1024;
426 my $content = "";
427
428 while ($bytes_left > 0 && !$eof) {
0a13e08e 429 my $read = mon_cmd($vmid, "guest-file-read", handle => $qgafh, count => int($read_size));
bb14060a
DC
430 check_agent_error($read, "can't read from file");
431
432 $content .= decode_base64($read->{'buf-b64'});
433 $bytes_left -= $read->{count};
434 $eof = $read->{eof} // 0;
435 }
436
0a13e08e 437 my $res = mon_cmd($vmid, "guest-file-close", handle => $qgafh);
bb14060a
DC
438 check_agent_error($res, "can't close file", 1);
439
440 my $result = {
441 content => $content,
442 'bytes-read' => ($MAX_READ_SIZE-$bytes_left),
443 };
444
445 if (!$eof) {
446 warn "agent file-read: reached maximum read size: $MAX_READ_SIZE bytes. output might be truncated.\n";
447 $result->{truncated} = 1;
448 }
449
450 return $result;
451 }});
452
735821a4
DC
453__PACKAGE__->register_method({
454 name => 'file-write',
455 path => 'file-write',
456 method => 'POST',
457 protected => 1,
458 proxyto => 'node',
459 description => "Writes the given file via guest agent.",
460 permissions => { check => [ 'perm', '/vms/{vmid}', [ 'VM.Monitor' ]]},
461 parameters => {
462 additionalProperties => 0,
463 properties => {
464 node => get_standard_option('pve-node'),
465 vmid => get_standard_option('pve-vmid', {
466 completion => \&PVE::QemuServer::complete_vmid_running }),
467 file => {
468 type => 'string',
469 description => 'The path to the file.'
470 },
471 content => {
472 type => 'string',
2b607ad1 473 maxLength => 60 * 1024, # 60k, smaller than our 64k POST limit
735821a4 474 description => "The content to write into the file."
0cb15193
DC
475 },
476 encode => {
477 type => 'boolean',
2b607ad1
TL
478 description => "If set, the content will be encoded as base64 (required by QEMU)."
479 ."Otherwise the content needs to be encoded beforehand - defaults to true.",
0cb15193
DC
480 optional => 1,
481 default => 1,
482 },
735821a4
DC
483 },
484 },
485 returns => { type => 'null' },
486 code => sub {
487 my ($param) = @_;
488
489 my $vmid = $param->{vmid};
2b607ad1 490
4d47a9f5 491 my $buf = ($param->{encode} // 1) ? encode_base64($param->{content}) : $param->{content};
735821a4
DC
492
493 my $qgafh = agent_cmd($vmid, "file-open", { path => $param->{file}, mode => 'wb' }, "can't open file");
494 my $write = agent_cmd($vmid, "file-write", { handle => $qgafh, 'buf-b64' => $buf }, "can't write to file");
495 my $res = agent_cmd($vmid, "file-close", { handle => $qgafh }, "can't close file");
496
d1c1af4b 497 return;
735821a4
DC
498 }});
499
b8158701 5001;