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