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