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