]> git.proxmox.com Git - qemu-server.git/blob - PVE/CLI/qm.pm
whitespace fixup
[qemu-server.git] / PVE / CLI / qm.pm
1 package PVE::CLI::qm;
2
3 use strict;
4 use warnings;
5
6 # Note: disable '+' prefix for Getopt::Long (for resize command)
7 use Getopt::Long qw(:config no_getopt_compat);
8
9 use Fcntl ':flock';
10 use File::Path;
11 use IO::Socket::UNIX;
12 use IO::Select;
13
14 use PVE::Tools qw(extract_param);
15 use PVE::Cluster;
16 use PVE::SafeSyslog;
17 use PVE::INotify;
18 use PVE::RPCEnvironment;
19 use PVE::QemuServer;
20 use PVE::QemuServer::ImportDisk;
21 use PVE::QemuServer::OVF;
22 use PVE::API2::Qemu;
23 use JSON;
24 use PVE::JSONSchema qw(get_standard_option);
25 use Term::ReadLine;
26 use Data::Dumper;
27
28 use PVE::CLIHandler;
29
30 use base qw(PVE::CLIHandler);
31
32 my $upid_exit = sub {
33 my $upid = shift;
34 my $status = PVE::Tools::upid_read_status($upid);
35 exit($status eq 'OK' ? 0 : -1);
36 };
37
38 my $nodename = PVE::INotify::nodename();
39
40 sub setup_environment {
41 PVE::RPCEnvironment->setup_default_cli_env();
42 }
43
44 sub run_vnc_proxy {
45 my ($path) = @_;
46
47 my $c;
48 while ( ++$c < 10 && !-e $path ) { sleep(1); }
49
50 my $s = IO::Socket::UNIX->new(Peer => $path, Timeout => 120);
51
52 die "unable to connect to socket '$path' - $!" if !$s;
53
54 my $select = new IO::Select;
55
56 $select->add(\*STDIN);
57 $select->add($s);
58
59 my $timeout = 60*15; # 15 minutes
60
61 my @handles;
62 while ($select->count &&
63 scalar(@handles = $select->can_read ($timeout))) {
64 foreach my $h (@handles) {
65 my $buf;
66 my $n = $h->sysread($buf, 4096);
67
68 if ($h == \*STDIN) {
69 if ($n) {
70 syswrite($s, $buf);
71 } else {
72 exit(0);
73 }
74 } elsif ($h == $s) {
75 if ($n) {
76 syswrite(\*STDOUT, $buf);
77 } else {
78 exit(0);
79 }
80 }
81 }
82 }
83 exit(0);
84 }
85
86 sub print_recursive_hash {
87 my ($prefix, $hash, $key) = @_;
88
89 if (ref($hash) eq 'HASH') {
90 if (defined($key)) {
91 print "$prefix$key:\n";
92 }
93 foreach my $itemkey (keys %$hash) {
94 print_recursive_hash("\t$prefix", $hash->{$itemkey}, $itemkey);
95 }
96 } elsif (ref($hash) eq 'ARRAY') {
97 if (defined($key)) {
98 print "$prefix$key:\n";
99 }
100 foreach my $item (@$hash) {
101 print_recursive_hash("\t$prefix", $item);
102 }
103 } elsif (!ref($hash) && defined($hash)) {
104 if (defined($key)) {
105 print "$prefix$key: $hash\n";
106 } else {
107 print "$prefix$hash\n";
108 }
109 }
110 }
111
112 __PACKAGE__->register_method ({
113 name => 'showcmd',
114 path => 'showcmd',
115 method => 'GET',
116 description => "Show command line which is used to start the VM (debug info).",
117 parameters => {
118 additionalProperties => 0,
119 properties => {
120 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
121 },
122 },
123 returns => { type => 'null'},
124 code => sub {
125 my ($param) = @_;
126
127 my $storecfg = PVE::Storage::config();
128 print PVE::QemuServer::vm_commandline($storecfg, $param->{vmid}) . "\n";
129
130 return undef;
131 }});
132
133 __PACKAGE__->register_method ({
134 name => 'status',
135 path => 'status',
136 method => 'GET',
137 description => "Show VM status.",
138 parameters => {
139 additionalProperties => 0,
140 properties => {
141 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
142 verbose => {
143 description => "Verbose output format",
144 type => 'boolean',
145 optional => 1,
146 }
147 },
148 },
149 returns => { type => 'null'},
150 code => sub {
151 my ($param) = @_;
152
153 # test if VM exists
154 my $conf = PVE::QemuConfig->load_config ($param->{vmid});
155
156 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
157 my $stat = $vmstatus->{$param->{vmid}};
158 if ($param->{verbose}) {
159 foreach my $k (sort (keys %$stat)) {
160 next if $k eq 'cpu' || $k eq 'relcpu'; # always 0
161 my $v = $stat->{$k};
162 print_recursive_hash("", $v, $k);
163 }
164 } else {
165 my $status = $stat->{qmpstatus} || 'unknown';
166 print "status: $status\n";
167 }
168
169 return undef;
170 }});
171
172 __PACKAGE__->register_method ({
173 name => 'vncproxy',
174 path => 'vncproxy',
175 method => 'PUT',
176 description => "Proxy VM VNC traffic to stdin/stdout",
177 parameters => {
178 additionalProperties => 0,
179 properties => {
180 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_running }),
181 },
182 },
183 returns => { type => 'null'},
184 code => sub {
185 my ($param) = @_;
186
187 my $vmid = $param->{vmid};
188 my $vnc_socket = PVE::QemuServer::vnc_socket($vmid);
189
190 if (my $ticket = $ENV{LC_PVE_TICKET}) { # NOTE: ssh on debian only pass LC_* variables
191 PVE::QemuServer::vm_mon_cmd($vmid, "change", device => 'vnc', target => "unix:$vnc_socket,password");
192 PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'vnc', password => $ticket);
193 PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'vnc', time => "+30");
194 } else {
195 PVE::QemuServer::vm_mon_cmd($vmid, "change", device => 'vnc', target => "unix:$vnc_socket,x509,password");
196 }
197
198 run_vnc_proxy($vnc_socket);
199
200 return undef;
201 }});
202
203 __PACKAGE__->register_method ({
204 name => 'unlock',
205 path => 'unlock',
206 method => 'PUT',
207 description => "Unlock the VM.",
208 parameters => {
209 additionalProperties => 0,
210 properties => {
211 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
212 },
213 },
214 returns => { type => 'null'},
215 code => sub {
216 my ($param) = @_;
217
218 my $vmid = $param->{vmid};
219
220 PVE::QemuConfig->lock_config ($vmid, sub {
221 my $conf = PVE::QemuConfig->load_config($vmid);
222 delete $conf->{lock};
223 delete $conf->{pending}->{lock} if $conf->{pending}; # just to be sure
224 PVE::QemuConfig->write_config($vmid, $conf);
225 });
226
227 return undef;
228 }});
229
230 __PACKAGE__->register_method ({
231 name => 'nbdstop',
232 path => 'nbdstop',
233 method => 'PUT',
234 description => "Stop embedded nbd server.",
235 parameters => {
236 additionalProperties => 0,
237 properties => {
238 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
239 },
240 },
241 returns => { type => 'null'},
242 code => sub {
243 my ($param) = @_;
244
245 my $vmid = $param->{vmid};
246
247 PVE::QemuServer::nbd_stop($vmid);
248
249 return undef;
250 }});
251
252 __PACKAGE__->register_method ({
253 name => 'mtunnel',
254 path => 'mtunnel',
255 method => 'POST',
256 description => "Used by qmigrate - do not use manually.",
257 parameters => {
258 additionalProperties => 0,
259 properties => {},
260 },
261 returns => { type => 'null'},
262 code => sub {
263 my ($param) = @_;
264
265 if (!PVE::Cluster::check_cfs_quorum(1)) {
266 print "no quorum\n";
267 return undef;
268 }
269
270 my $tunnel_write = sub {
271 my $text = shift;
272 chomp $text;
273 print "$text\n";
274 *STDOUT->flush();
275 };
276
277 $tunnel_write->("tunnel online");
278 $tunnel_write->("ver 1");
279
280 while (my $line = <>) {
281 chomp $line;
282 if ($line =~ /^quit$/) {
283 $tunnel_write->("OK");
284 last;
285 } elsif ($line =~ /^resume (\d+)$/) {
286 my $vmid = $1;
287 if (PVE::QemuServer::check_running($vmid, 1)) {
288 eval { PVE::QemuServer::vm_resume($vmid, 1, 1); };
289 if ($@) {
290 $tunnel_write->("ERR: resume failed - $@");
291 } else {
292 $tunnel_write->("OK");
293 }
294 } else {
295 $tunnel_write->("ERR: resume failed - VM $vmid not running");
296 }
297 }
298 }
299
300 return undef;
301 }});
302
303 __PACKAGE__->register_method ({
304 name => 'wait',
305 path => 'wait',
306 method => 'GET',
307 description => "Wait until the VM is stopped.",
308 parameters => {
309 additionalProperties => 0,
310 properties => {
311 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_running }),
312 timeout => {
313 description => "Timeout in seconds. Default is to wait forever.",
314 type => 'integer',
315 minimum => 1,
316 optional => 1,
317 }
318 },
319 },
320 returns => { type => 'null'},
321 code => sub {
322 my ($param) = @_;
323
324 my $vmid = $param->{vmid};
325 my $timeout = $param->{timeout};
326
327 my $pid = PVE::QemuServer::check_running ($vmid);
328 return if !$pid;
329
330 print "waiting until VM $vmid stopps (PID $pid)\n";
331
332 my $count = 0;
333 while ((!$timeout || ($count < $timeout)) && PVE::QemuServer::check_running ($vmid)) {
334 $count++;
335 sleep 1;
336 }
337
338 die "wait failed - got timeout\n" if PVE::QemuServer::check_running ($vmid);
339
340 return undef;
341 }});
342
343 __PACKAGE__->register_method ({
344 name => 'monitor',
345 path => 'monitor',
346 method => 'POST',
347 description => "Enter Qemu Monitor interface.",
348 parameters => {
349 additionalProperties => 0,
350 properties => {
351 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_running }),
352 },
353 },
354 returns => { type => 'null'},
355 code => sub {
356 my ($param) = @_;
357
358 my $vmid = $param->{vmid};
359
360 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
361
362 print "Entering Qemu Monitor for VM $vmid - type 'help' for help\n";
363
364 my $term = new Term::ReadLine ('qm');
365
366 my $input;
367 while (defined ($input = $term->readline('qm> '))) {
368 chomp $input;
369
370 next if $input =~ m/^\s*$/;
371
372 last if $input =~ m/^\s*q(uit)?\s*$/;
373
374 eval {
375 print PVE::QemuServer::vm_human_monitor_command ($vmid, $input);
376 };
377 print "ERROR: $@" if $@;
378 }
379
380 return undef;
381
382 }});
383
384 __PACKAGE__->register_method ({
385 name => 'rescan',
386 path => 'rescan',
387 method => 'POST',
388 description => "Rescan all storages and update disk sizes and unused disk images.",
389 parameters => {
390 additionalProperties => 0,
391 properties => {
392 vmid => get_standard_option('pve-vmid', {
393 optional => 1,
394 completion => \&PVE::QemuServer::complete_vmid,
395 }),
396 },
397 },
398 returns => { type => 'null'},
399 code => sub {
400 my ($param) = @_;
401
402 PVE::QemuServer::rescan($param->{vmid});
403
404 return undef;
405 }});
406
407 __PACKAGE__->register_method ({
408 name => 'importdisk',
409 path => 'importdisk',
410 method => 'POST',
411 description => "Import an external disk image as an unused disk in a VM. The
412 image format has to be supported by qemu-img(1).",
413 parameters => {
414 additionalProperties => 0,
415 properties => {
416 vmid => get_standard_option('pve-vmid', {completion => \&PVE::QemuServer::complete_vmid}),
417 source => {
418 description => 'Path to the disk image to import',
419 type => 'string',
420 optional => 0,
421 },
422 storage => get_standard_option('pve-storage-id', {
423 description => 'Target storage ID',
424 completion => \&PVE::QemuServer::complete_storage,
425 optional => 0,
426 }),
427 format => {
428 type => 'string',
429 description => 'Target format',
430 enum => [ 'raw', 'qcow2', 'vmdk' ],
431 optional => 1,
432 },
433 },
434 },
435 returns => { type => 'null'},
436 code => sub {
437 my ($param) = @_;
438
439 my $vmid = extract_param($param, 'vmid');
440 my $source = extract_param($param, 'source');
441 my $storeid = extract_param($param, 'storage');
442 my $format = extract_param($param, 'format');
443
444 my $vm_conf = PVE::QemuConfig->load_config($vmid);
445 PVE::QemuConfig->check_lock($vm_conf);
446 die "$source: non-existent or non-regular file\n" if (! -f $source);
447
448 my $storecfg = PVE::Storage::config();
449 PVE::Storage::storage_check_enabled($storecfg, $storeid);
450
451 my $target_storage_config =
452 PVE::Storage::storage_config($storecfg, $storeid);
453 die "storage $storeid does not support vm images\n"
454 if !$target_storage_config->{content}->{images};
455
456 PVE::QemuServer::ImportDisk::do_import($source, $vmid, $storeid, { format => $format });
457
458 return undef;
459 }});
460
461 __PACKAGE__->register_method ({
462 name => 'terminal',
463 path => 'terminal',
464 method => 'POST',
465 description => "Open a terminal using a serial device (The VM need to have a serial device configured, for example 'serial0: socket')",
466 parameters => {
467 additionalProperties => 0,
468 properties => {
469 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_running }),
470 iface => {
471 description => "Select the serial device. By default we simply use the first suitable device.",
472 type => 'string',
473 optional => 1,
474 enum => [qw(serial0 serial1 serial2 serial3)],
475 }
476 },
477 },
478 returns => { type => 'null'},
479 code => sub {
480 my ($param) = @_;
481
482 my $vmid = $param->{vmid};
483
484 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
485
486 my $iface = $param->{iface};
487
488 if ($iface) {
489 die "serial interface '$iface' is not configured\n" if !$conf->{$iface};
490 die "wrong serial type on interface '$iface'\n" if $conf->{$iface} ne 'socket';
491 } else {
492 foreach my $opt (qw(serial0 serial1 serial2 serial3)) {
493 if ($conf->{$opt} && ($conf->{$opt} eq 'socket')) {
494 $iface = $opt;
495 last;
496 }
497 }
498 die "unable to find a serial interface\n" if !$iface;
499 }
500
501 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
502
503 my $socket = "/var/run/qemu-server/${vmid}.$iface";
504
505 my $cmd = "socat UNIX-CONNECT:$socket STDIO,raw,echo=0,escape=0x0f";
506
507 print "starting serial terminal on interface $iface (press control-O to exit)\n";
508
509 system($cmd);
510
511 return undef;
512 }});
513
514 __PACKAGE__->register_method ({
515 name => 'importovf',
516 path => 'importovf',
517 description => "Create a new VM using parameters read from an OVF manifest",
518 parameters => {
519 additionalProperties => 0,
520 properties => {
521 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
522 manifest => {
523 type => 'string',
524 description => 'path to the ovf file',
525 },
526 storage => get_standard_option('pve-storage-id', {
527 description => 'Target storage ID',
528 completion => \&PVE::QemuServer::complete_storage,
529 optional => 0,
530 }),
531 format => {
532 type => 'string',
533 description => 'Target format',
534 enum => [ 'raw', 'qcow2', 'vmdk' ],
535 optional => 1,
536 },
537 dryrun => {
538 type => 'boolean',
539 description => 'Print a parsed representation of the extracted OVF parameters, but do not create a VM',
540 optional => 1,
541 }
542 },
543 },
544 returns => { type => 'string'},
545 code => sub {
546 my ($param) = @_;
547
548 my $vmid = PVE::Tools::extract_param($param, 'vmid');
549 my $ovf_file = PVE::Tools::extract_param($param, 'manifest');
550 my $storeid = PVE::Tools::extract_param($param, 'storage');
551 my $format = PVE::Tools::extract_param($param, 'format');
552 my $dryrun = PVE::Tools::extract_param($param, 'dryrun');
553
554 die "$ovf_file: non-existent or non-regular file\n" if (! -f $ovf_file);
555 my $storecfg = PVE::Storage::config();
556 PVE::Storage::storage_check_enabled($storecfg, $storeid);
557
558 my $parsed = PVE::QemuServer::OVF::parse_ovf($ovf_file);
559
560 if ($dryrun) {
561 print Dumper($parsed);
562 exit(0);
563 }
564
565 $param->{name} = $parsed->{qm}->{name} if defined($parsed->{qm}->{name});
566 $param->{memory} = $parsed->{qm}->{memory} if defined($parsed->{qm}->{memory});
567 $param->{cores} = $parsed->{qm}->{cores} if defined($parsed->{qm}->{cores});
568
569 my $importfn = sub {
570
571 PVE::Cluster::check_vmid_unused($vmid);
572
573 my $conf = $param;
574
575 eval {
576 # order matters, as do_import() will load_config() internally
577 $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
578 PVE::QemuConfig->write_config($vmid, $conf);
579
580 foreach my $disk (@{ $parsed->{disks} }) {
581 my ($file, $drive) = ($disk->{backing_file}, $disk->{disk_address});
582 PVE::QemuServer::ImportDisk::do_import($file, $vmid, $storeid,
583 { drive_name => $drive, format => $format });
584 }
585
586 # reload after disks entries have been created
587 $conf = PVE::QemuConfig->load_config($vmid);
588 PVE::QemuConfig->check_lock($conf);
589 my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
590 $conf->{bootdisk} = $firstdisk if $firstdisk;
591 PVE::QemuConfig->write_config($vmid, $conf);
592 };
593
594 my $err = $@;
595 if ($err) {
596 my $skiplock = 1;
597 eval { PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock); };
598 die "import failed - $err";
599 }
600 };
601
602 my $wait_for_lock = 1;
603 return PVE::QemuConfig->lock_config_full($vmid, $wait_for_lock, $importfn);
604
605 }
606 });
607
608 my $print_agent_result = sub {
609 my ($data) = @_;
610
611 my $result = $data->{result};
612 return if !defined($result);
613
614 my $class = ref($result);
615
616 if (!$class) {
617 chomp $result;
618 return if $result =~ m/^\s*$/;
619 print "$result\n";
620 return;
621 }
622
623 if (($class eq 'HASH') && !scalar(keys %$result)) { # empty hash
624 return;
625 }
626
627 print to_json($result, { pretty => 1, canonical => 1});
628 };
629
630 our $cmddef = {
631 list => [ "PVE::API2::Qemu", 'vmlist', [],
632 { node => $nodename }, sub {
633 my $vmlist = shift;
634
635 exit 0 if (!scalar(@$vmlist));
636
637 printf "%10s %-20s %-10s %-10s %12s %-10s\n",
638 qw(VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID);
639
640 foreach my $rec (sort { $a->{vmid} <=> $b->{vmid} } @$vmlist) {
641 printf "%10s %-20s %-10s %-10s %12.2f %-10s\n", $rec->{vmid}, $rec->{name},
642 $rec->{qmpstatus} || $rec->{status},
643 ($rec->{maxmem} || 0)/(1024*1024),
644 ($rec->{maxdisk} || 0)/(1024*1024*1024),
645 $rec->{pid}||0;
646 }
647
648
649 } ],
650
651 create => [ "PVE::API2::Qemu", 'create_vm', ['vmid'], { node => $nodename }, $upid_exit ],
652
653 destroy => [ "PVE::API2::Qemu", 'destroy_vm', ['vmid'], { node => $nodename }, $upid_exit ],
654
655 clone => [ "PVE::API2::Qemu", 'clone_vm', ['vmid', 'newid'], { node => $nodename }, $upid_exit ],
656
657 migrate => [ "PVE::API2::Qemu", 'migrate_vm', ['vmid', 'target'], { node => $nodename }, $upid_exit ],
658
659 set => [ "PVE::API2::Qemu", 'update_vm', ['vmid'], { node => $nodename } ],
660
661 resize => [ "PVE::API2::Qemu", 'resize_vm', ['vmid', 'disk', 'size'], { node => $nodename } ],
662
663 move_disk => [ "PVE::API2::Qemu", 'move_vm_disk', ['vmid', 'disk', 'storage'], { node => $nodename }, $upid_exit ],
664
665 unlink => [ "PVE::API2::Qemu", 'unlink', ['vmid'], { node => $nodename } ],
666
667 config => [ "PVE::API2::Qemu", 'vm_config', ['vmid'],
668 { node => $nodename }, sub {
669 my $config = shift;
670 foreach my $k (sort (keys %$config)) {
671 next if $k eq 'digest';
672 my $v = $config->{$k};
673 if ($k eq 'description') {
674 $v = PVE::Tools::encode_text($v);
675 }
676 print "$k: $v\n";
677 }
678 }],
679
680 pending => [ "PVE::API2::Qemu", 'vm_pending', ['vmid'],
681 { node => $nodename }, sub {
682 my $data = shift;
683 foreach my $item (sort { $a->{key} cmp $b->{key}} @$data) {
684 my $k = $item->{key};
685 next if $k eq 'digest';
686 my $v = $item->{value};
687 my $p = $item->{pending};
688 if ($k eq 'description') {
689 $v = PVE::Tools::encode_text($v) if defined($v);
690 $p = PVE::Tools::encode_text($p) if defined($p);
691 }
692 if (defined($v)) {
693 if ($item->{delete}) {
694 print "del $k: $v\n";
695 } elsif (defined($p)) {
696 print "cur $k: $v\n";
697 print "new $k: $p\n";
698 } else {
699 print "cur $k: $v\n";
700 }
701 } elsif (defined($p)) {
702 print "new $k: $p\n";
703 }
704 }
705 }],
706
707 showcmd => [ __PACKAGE__, 'showcmd', ['vmid']],
708
709 status => [ __PACKAGE__, 'status', ['vmid']],
710
711 snapshot => [ "PVE::API2::Qemu", 'snapshot', ['vmid', 'snapname'], { node => $nodename } , $upid_exit ],
712
713 delsnapshot => [ "PVE::API2::Qemu", 'delsnapshot', ['vmid', 'snapname'], { node => $nodename } , $upid_exit ],
714
715 listsnapshot => [ "PVE::API2::Qemu", 'snapshot_list', ['vmid'], { node => $nodename },
716 sub {
717 my $res = shift;
718 foreach my $e (@$res) {
719 my $headline = $e->{description} || 'no-description';
720 $headline =~ s/\n.*//sg;
721 my $parent = $e->{parent} // 'no-parent';
722 printf("%-20s %-20s %s\n", $e->{name}, $parent, $headline);
723 }
724 }],
725
726 rollback => [ "PVE::API2::Qemu", 'rollback', ['vmid', 'snapname'], { node => $nodename } , $upid_exit ],
727
728 template => [ "PVE::API2::Qemu", 'template', ['vmid'], { node => $nodename }],
729
730 start => [ "PVE::API2::Qemu", 'vm_start', ['vmid'], { node => $nodename } , $upid_exit ],
731
732 stop => [ "PVE::API2::Qemu", 'vm_stop', ['vmid'], { node => $nodename }, $upid_exit ],
733
734 reset => [ "PVE::API2::Qemu", 'vm_reset', ['vmid'], { node => $nodename }, $upid_exit ],
735
736 shutdown => [ "PVE::API2::Qemu", 'vm_shutdown', ['vmid'], { node => $nodename }, $upid_exit ],
737
738 suspend => [ "PVE::API2::Qemu", 'vm_suspend', ['vmid'], { node => $nodename }, $upid_exit ],
739
740 resume => [ "PVE::API2::Qemu", 'vm_resume', ['vmid'], { node => $nodename }, $upid_exit ],
741
742 sendkey => [ "PVE::API2::Qemu", 'vm_sendkey', ['vmid', 'key'], { node => $nodename } ],
743
744 vncproxy => [ __PACKAGE__, 'vncproxy', ['vmid']],
745
746 wait => [ __PACKAGE__, 'wait', ['vmid']],
747
748 unlock => [ __PACKAGE__, 'unlock', ['vmid']],
749
750 rescan => [ __PACKAGE__, 'rescan', []],
751
752 monitor => [ __PACKAGE__, 'monitor', ['vmid']],
753
754 agent => [ "PVE::API2::Qemu", 'agent', ['vmid', 'command'],
755 { node => $nodename }, $print_agent_result ],
756
757 mtunnel => [ __PACKAGE__, 'mtunnel', []],
758
759 nbdstop => [ __PACKAGE__, 'nbdstop', ['vmid']],
760
761 terminal => [ __PACKAGE__, 'terminal', ['vmid']],
762
763 importdisk => [ __PACKAGE__, 'importdisk', ['vmid', 'source', 'storage']],
764
765 importovf => [ __PACKAGE__, 'importovf', ['vmid', 'manifest', 'storage']],
766
767 };
768
769 1;