]> git.proxmox.com Git - qemu-server.git/blob - PVE/API2/Qemu.pm
add a hook to call vm_devicedel with disk is removed from configuration
[qemu-server.git] / PVE / API2 / Qemu.pm
1 package PVE::API2::Qemu;
2
3 use strict;
4 use warnings;
5
6 use PVE::Cluster;
7 use PVE::SafeSyslog;
8 use PVE::Tools qw(extract_param);
9 use PVE::Exception qw(raise raise_param_exc);
10 use PVE::Storage;
11 use PVE::JSONSchema qw(get_standard_option);
12 use PVE::RESTHandler;
13 use PVE::QemuServer;
14 use PVE::QemuMigrate;
15 use PVE::RPCEnvironment;
16 use PVE::AccessControl;
17 use PVE::INotify;
18
19 use Data::Dumper; # fixme: remove
20
21 use base qw(PVE::RESTHandler);
22
23 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
24
25 my $resolve_cdrom_alias = sub {
26 my $param = shift;
27
28 if (my $value = $param->{cdrom}) {
29 $value .= ",media=cdrom" if $value !~ m/media=/;
30 $param->{ide2} = $value;
31 delete $param->{cdrom};
32 }
33 };
34
35 __PACKAGE__->register_method({
36 name => 'vmlist',
37 path => '',
38 method => 'GET',
39 description => "Virtual machine index (per node).",
40 proxyto => 'node',
41 protected => 1, # qemu pid files are only readable by root
42 parameters => {
43 additionalProperties => 0,
44 properties => {
45 node => get_standard_option('pve-node'),
46 },
47 },
48 returns => {
49 type => 'array',
50 items => {
51 type => "object",
52 properties => {},
53 },
54 links => [ { rel => 'child', href => "{vmid}" } ],
55 },
56 code => sub {
57 my ($param) = @_;
58
59 my $vmstatus = PVE::QemuServer::vmstatus();
60
61 return PVE::RESTHandler::hash_to_array($vmstatus, 'vmid');
62
63 }});
64
65 __PACKAGE__->register_method({
66 name => 'create_vm',
67 path => '',
68 method => 'POST',
69 description => "Create new virtual machine.",
70 protected => 1,
71 proxyto => 'node',
72 parameters => {
73 additionalProperties => 0,
74 properties => PVE::QemuServer::json_config_properties(
75 {
76 node => get_standard_option('pve-node'),
77 vmid => get_standard_option('pve-vmid'),
78 }),
79 },
80 returns => {
81 type => 'string',
82 },
83 code => sub {
84 my ($param) = @_;
85
86 my $rpcenv = PVE::RPCEnvironment::get();
87
88 my $user = $rpcenv->get_user();
89
90 my $node = extract_param($param, 'node');
91
92 # fixme: fork worker?
93
94 my $vmid = extract_param($param, 'vmid');
95
96 my $filename = PVE::QemuServer::config_file($vmid);
97 # first test (befor locking)
98 die "unable to create vm $vmid: config file already exists\n"
99 if -f $filename;
100
101 my $storecfg = PVE::Storage::config();
102
103 &$resolve_cdrom_alias($param);
104
105 foreach my $opt (keys %$param) {
106 if (PVE::QemuServer::valid_drivename($opt)) {
107 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
108 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
109
110 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
111 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
112 }
113 }
114
115 PVE::QemuServer::add_random_macs($param);
116
117 my $createfn = sub {
118
119 # second test (after locking test is accurate)
120 die "unable to create vm $vmid: config file already exists\n"
121 if -f $filename;
122
123 my $realcmd = sub {
124
125 my $vollist = [];
126
127 eval {
128 $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param);
129
130 # try to be smart about bootdisk
131 my @disks = PVE::QemuServer::disknames();
132 my $firstdisk;
133 foreach my $ds (reverse @disks) {
134 next if !$param->{$ds};
135 my $disk = PVE::QemuServer::parse_drive($ds, $param->{$ds});
136 next if PVE::QemuServer::drive_is_cdrom($disk);
137 $firstdisk = $ds;
138 }
139
140 if (!$param->{bootdisk} && $firstdisk) {
141 $param->{bootdisk} = $firstdisk;
142 }
143
144 PVE::QemuServer::create_conf_nolock($vmid, $param);
145 };
146 my $err = $@;
147
148 if ($err) {
149 foreach my $volid (@$vollist) {
150 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
151 warn $@ if $@;
152 }
153 die "create failed - $err";
154 }
155 };
156
157 return $rpcenv->fork_worker('qmcreate', $vmid, $user, $realcmd);
158 };
159
160 return PVE::QemuServer::lock_config($vmid, $createfn);
161 }});
162
163 __PACKAGE__->register_method({
164 name => 'vmdiridx',
165 path => '{vmid}',
166 method => 'GET',
167 proxyto => 'node',
168 description => "Directory index",
169 parameters => {
170 additionalProperties => 0,
171 properties => {
172 node => get_standard_option('pve-node'),
173 vmid => get_standard_option('pve-vmid'),
174 },
175 },
176 returns => {
177 type => 'array',
178 items => {
179 type => "object",
180 properties => {
181 subdir => { type => 'string' },
182 },
183 },
184 links => [ { rel => 'child', href => "{subdir}" } ],
185 },
186 code => sub {
187 my ($param) = @_;
188
189 my $res = [
190 { subdir => 'config' },
191 { subdir => 'status' },
192 { subdir => 'unlink' },
193 { subdir => 'vncproxy' },
194 { subdir => 'migrate' },
195 { subdir => 'rrd' },
196 { subdir => 'rrddata' },
197 ];
198
199 return $res;
200 }});
201
202 __PACKAGE__->register_method({
203 name => 'rrd',
204 path => '{vmid}/rrd',
205 method => 'GET',
206 protected => 1, # fixme: can we avoid that?
207 permissions => {
208 path => '/vms/{vmid}',
209 privs => [ 'VM.Audit' ],
210 },
211 description => "Read VM RRD statistics (returns PNG)",
212 parameters => {
213 additionalProperties => 0,
214 properties => {
215 node => get_standard_option('pve-node'),
216 vmid => get_standard_option('pve-vmid'),
217 timeframe => {
218 description => "Specify the time frame you are interested in.",
219 type => 'string',
220 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
221 },
222 ds => {
223 description => "The list of datasources you want to display.",
224 type => 'string', format => 'pve-configid-list',
225 },
226 cf => {
227 description => "The RRD consolidation function",
228 type => 'string',
229 enum => [ 'AVERAGE', 'MAX' ],
230 optional => 1,
231 },
232 },
233 },
234 returns => {
235 type => "object",
236 properties => {
237 filename => { type => 'string' },
238 },
239 },
240 code => sub {
241 my ($param) = @_;
242
243 return PVE::Cluster::create_rrd_graph(
244 "pve2-vm/$param->{vmid}", $param->{timeframe},
245 $param->{ds}, $param->{cf});
246
247 }});
248
249 __PACKAGE__->register_method({
250 name => 'rrddata',
251 path => '{vmid}/rrddata',
252 method => 'GET',
253 protected => 1, # fixme: can we avoid that?
254 permissions => {
255 path => '/vms/{vmid}',
256 privs => [ 'VM.Audit' ],
257 },
258 description => "Read VM RRD statistics",
259 parameters => {
260 additionalProperties => 0,
261 properties => {
262 node => get_standard_option('pve-node'),
263 vmid => get_standard_option('pve-vmid'),
264 timeframe => {
265 description => "Specify the time frame you are interested in.",
266 type => 'string',
267 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
268 },
269 cf => {
270 description => "The RRD consolidation function",
271 type => 'string',
272 enum => [ 'AVERAGE', 'MAX' ],
273 optional => 1,
274 },
275 },
276 },
277 returns => {
278 type => "array",
279 items => {
280 type => "object",
281 properties => {},
282 },
283 },
284 code => sub {
285 my ($param) = @_;
286
287 return PVE::Cluster::create_rrd_data(
288 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
289 }});
290
291
292 __PACKAGE__->register_method({
293 name => 'vm_config',
294 path => '{vmid}/config',
295 method => 'GET',
296 proxyto => 'node',
297 description => "Get virtual machine configuration.",
298 parameters => {
299 additionalProperties => 0,
300 properties => {
301 node => get_standard_option('pve-node'),
302 vmid => get_standard_option('pve-vmid'),
303 },
304 },
305 returns => {
306 type => "object",
307 properties => {
308 digest => {
309 type => 'string',
310 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
311 }
312 },
313 },
314 code => sub {
315 my ($param) = @_;
316
317 my $conf = PVE::QemuServer::load_config($param->{vmid});
318
319 return $conf;
320 }});
321
322 __PACKAGE__->register_method({
323 name => 'update_vm',
324 path => '{vmid}/config',
325 method => 'PUT',
326 protected => 1,
327 proxyto => 'node',
328 description => "Set virtual machine options.",
329 parameters => {
330 additionalProperties => 0,
331 properties => PVE::QemuServer::json_config_properties(
332 {
333 node => get_standard_option('pve-node'),
334 vmid => get_standard_option('pve-vmid'),
335 skiplock => get_standard_option('skiplock'),
336 delete => {
337 type => 'string', format => 'pve-configid-list',
338 description => "A list of settings you want to delete.",
339 optional => 1,
340 },
341 force => {
342 type => 'boolean',
343 description => $opt_force_description,
344 optional => 1,
345 requires => 'delete',
346 },
347 digest => {
348 type => 'string',
349 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
350 maxLength => 40,
351 optional => 1,
352 }
353 }),
354 },
355 returns => { type => 'null'},
356 code => sub {
357 my ($param) = @_;
358
359 my $rpcenv = PVE::RPCEnvironment::get();
360
361 my $user = $rpcenv->get_user();
362
363 my $node = extract_param($param, 'node');
364
365 my $vmid = extract_param($param, 'vmid');
366
367 my $digest = extract_param($param, 'digest');
368
369 my @paramarr = (); # used for log message
370 foreach my $key (keys %$param) {
371 push @paramarr, "-$key", $param->{$key};
372 }
373
374 my $skiplock = extract_param($param, 'skiplock');
375 raise_param_exc({ skiplock => "Only root may use this option." })
376 if $skiplock && $user ne 'root@pam';
377
378 my $delete = extract_param($param, 'delete');
379 my $force = extract_param($param, 'force');
380
381 die "no options specified\n" if !$delete && !scalar(keys %$param);
382
383 my $storecfg = PVE::Storage::config();
384
385 &$resolve_cdrom_alias($param);
386
387 my $eject = {};
388 my $cdchange = {};
389
390 foreach my $opt (keys %$param) {
391 if (PVE::QemuServer::valid_drivename($opt)) {
392 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
393 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
394 if ($drive->{file} eq 'eject') {
395 $eject->{$opt} = 1;
396 delete $param->{$opt};
397 next;
398 }
399
400 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
401 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
402
403 if (PVE::QemuServer::drive_is_cdrom($drive)) {
404 $cdchange->{$opt} = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
405 }
406 }
407 }
408
409 foreach my $opt (PVE::Tools::split_list($delete)) {
410 $opt = 'ide2' if $opt eq 'cdrom';
411 die "you can't use '-$opt' and '-delete $opt' at the same time\n"
412 if defined($param->{$opt});
413 }
414
415 PVE::QemuServer::add_random_macs($param);
416
417 my $vollist = [];
418
419 my $updatefn = sub {
420
421 my $conf = PVE::QemuServer::load_config($vmid);
422
423 die "checksum missmatch (file change by other user?)\n"
424 if $digest && $digest ne $conf->{digest};
425
426 PVE::QemuServer::check_lock($conf) if !$skiplock;
427
428 PVE::Cluster::log_msg('info', $user, "update VM $vmid: " . join (' ', @paramarr));
429
430 foreach my $opt (keys %$eject) {
431 if ($conf->{$opt}) {
432 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
433 $cdchange->{$opt} = undef if PVE::QemuServer::drive_is_cdrom($drive);
434 } else {
435 raise_param_exc({ $opt => "eject failed - drive does not exist." });
436 }
437 }
438
439 foreach my $opt (keys %$param) {
440 next if !PVE::QemuServer::valid_drivename($opt);
441 next if !$conf->{$opt};
442 my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
443 next if PVE::QemuServer::drive_is_cdrom($old_drive);
444 my $new_drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
445 if ($new_drive->{file} ne $old_drive->{file}) {
446 my ($path, $owner);
447 eval { ($path, $owner) = PVE::Storage::path($storecfg, $old_drive->{file}); };
448 if ($owner && ($owner == $vmid)) {
449 PVE::QemuServer::add_unused_volume($conf, $param, $old_drive->{file});
450 }
451 }
452 }
453
454 my $unset = {};
455
456 foreach my $opt (PVE::Tools::split_list($delete)) {
457 $opt = 'ide2' if $opt eq 'cdrom';
458 if (!PVE::QemuServer::option_exists($opt)) {
459 raise_param_exc({ delete => "unknown option '$opt'" });
460 }
461 next if !defined($conf->{$opt});
462 if (PVE::QemuServer::valid_drivename($opt)) {
463 PVE::QemuServer::vm_devicedel($vmid,$opt);
464 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
465 if (PVE::QemuServer::drive_is_cdrom($drive)) {
466 $cdchange->{$opt} = undef;
467 } else {
468 my $volid = $drive->{file};
469
470 if ($volid !~ m|^/|) {
471 my ($path, $owner);
472 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
473 if ($owner && ($owner == $vmid)) {
474 if ($force) {
475 push @$vollist, $volid;
476 } else {
477 PVE::QemuServer::add_unused_volume($conf, $param, $volid);
478 }
479 }
480 }
481 }
482 } elsif ($opt =~ m/^unused/) {
483 push @$vollist, $conf->{$opt};
484 }
485
486 $unset->{$opt} = 1;
487 }
488
489 PVE::QemuServer::create_disks($storecfg, $vmid, $param);
490
491 PVE::QemuServer::change_config_nolock($vmid, $param, $unset, 1);
492
493 return if !PVE::QemuServer::check_running($vmid);
494
495 foreach my $opt (keys %$cdchange) {
496 my $qdn = PVE::QemuServer::qemu_drive_name($opt, 'cdrom');
497 my $path = $cdchange->{$opt};
498 PVE::QemuServer::vm_monitor_command($vmid, "eject $qdn", 0);
499 PVE::QemuServer::vm_monitor_command($vmid, "change $qdn \"$path\"", 0) if $path;
500 }
501 };
502
503 PVE::QemuServer::lock_config($vmid, $updatefn);
504
505 foreach my $volid (@$vollist) {
506 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
507 # fixme: log ?
508 warn $@ if $@;
509 }
510
511 return undef;
512 }});
513
514
515 __PACKAGE__->register_method({
516 name => 'destroy_vm',
517 path => '{vmid}',
518 method => 'DELETE',
519 protected => 1,
520 proxyto => 'node',
521 description => "Destroy the vm (also delete all used/owned volumes).",
522 parameters => {
523 additionalProperties => 0,
524 properties => {
525 node => get_standard_option('pve-node'),
526 vmid => get_standard_option('pve-vmid'),
527 skiplock => get_standard_option('skiplock'),
528 },
529 },
530 returns => {
531 type => 'string',
532 },
533 code => sub {
534 my ($param) = @_;
535
536 my $rpcenv = PVE::RPCEnvironment::get();
537
538 my $user = $rpcenv->get_user();
539
540 my $vmid = $param->{vmid};
541
542 my $skiplock = $param->{skiplock};
543 raise_param_exc({ skiplock => "Only root may use this option." })
544 if $skiplock && $user ne 'root@pam';
545
546 # test if VM exists
547 my $conf = PVE::QemuServer::load_config($vmid);
548
549 my $storecfg = PVE::Storage::config();
550
551 my $realcmd = sub {
552 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
553 };
554
555 return $rpcenv->fork_worker('qmdestroy', $vmid, $user, $realcmd);
556 }});
557
558 __PACKAGE__->register_method({
559 name => 'unlink',
560 path => '{vmid}/unlink',
561 method => 'PUT',
562 protected => 1,
563 proxyto => 'node',
564 description => "Unlink/delete disk images.",
565 parameters => {
566 additionalProperties => 0,
567 properties => {
568 node => get_standard_option('pve-node'),
569 vmid => get_standard_option('pve-vmid'),
570 idlist => {
571 type => 'string', format => 'pve-configid-list',
572 description => "A list of disk IDs you want to delete.",
573 },
574 force => {
575 type => 'boolean',
576 description => $opt_force_description,
577 optional => 1,
578 },
579 },
580 },
581 returns => { type => 'null'},
582 code => sub {
583 my ($param) = @_;
584
585 $param->{delete} = extract_param($param, 'idlist');
586
587 __PACKAGE__->update_vm($param);
588
589 return undef;
590 }});
591
592 my $sslcert;
593
594 __PACKAGE__->register_method({
595 name => 'vncproxy',
596 path => '{vmid}/vncproxy',
597 method => 'POST',
598 protected => 1,
599 permissions => {
600 path => '/vms/{vmid}',
601 privs => [ 'VM.Console' ],
602 },
603 description => "Creates a TCP VNC proxy connections.",
604 parameters => {
605 additionalProperties => 0,
606 properties => {
607 node => get_standard_option('pve-node'),
608 vmid => get_standard_option('pve-vmid'),
609 },
610 },
611 returns => {
612 additionalProperties => 0,
613 properties => {
614 user => { type => 'string' },
615 ticket => { type => 'string' },
616 cert => { type => 'string' },
617 port => { type => 'integer' },
618 upid => { type => 'string' },
619 },
620 },
621 code => sub {
622 my ($param) = @_;
623
624 my $rpcenv = PVE::RPCEnvironment::get();
625
626 my $user = $rpcenv->get_user();
627 my $ticket = PVE::AccessControl::assemble_ticket($user);
628
629 my $vmid = $param->{vmid};
630 my $node = $param->{node};
631
632 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
633 if !$sslcert;
634
635 my $port = PVE::Tools::next_vnc_port();
636
637 my $remip;
638
639 if ($node ne PVE::INotify::nodename()) {
640 $remip = PVE::Cluster::remote_node_ip($node);
641 }
642
643 # NOTE: kvm VNC traffic is already TLS encrypted,
644 # so we select the fastest chipher here (or 'none'?)
645 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
646 '-c', 'blowfish-cbc', $remip] : [];
647
648 my $timeout = 10;
649
650 my $realcmd = sub {
651 my $upid = shift;
652
653 syslog('info', "starting vnc proxy $upid\n");
654
655 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
656
657 my $qmstr = join(' ', @$qmcmd);
658
659 # also redirect stderr (else we get RFB protocol errors)
660 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
661
662 PVE::Tools::run_command($cmd);
663
664 return;
665 };
666
667 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd);
668
669 return {
670 user => $user,
671 ticket => $ticket,
672 port => $port,
673 upid => $upid,
674 cert => $sslcert,
675 };
676 }});
677
678 __PACKAGE__->register_method({
679 name => 'vmcmdidx',
680 path => '{vmid}/status',
681 method => 'GET',
682 proxyto => 'node',
683 description => "Directory index",
684 parameters => {
685 additionalProperties => 0,
686 properties => {
687 node => get_standard_option('pve-node'),
688 vmid => get_standard_option('pve-vmid'),
689 },
690 },
691 returns => {
692 type => 'array',
693 items => {
694 type => "object",
695 properties => {
696 subdir => { type => 'string' },
697 },
698 },
699 links => [ { rel => 'child', href => "{subdir}" } ],
700 },
701 code => sub {
702 my ($param) = @_;
703
704 # test if VM exists
705 my $conf = PVE::QemuServer::load_config($param->{vmid});
706
707 my $res = [
708 { subdir => 'current' },
709 { subdir => 'start' },
710 { subdir => 'stop' },
711 ];
712
713 return $res;
714 }});
715
716 __PACKAGE__->register_method({
717 name => 'vm_status',
718 path => '{vmid}/status/current',
719 method => 'GET',
720 proxyto => 'node',
721 protected => 1, # qemu pid files are only readable by root
722 description => "Get virtual machine status.",
723 parameters => {
724 additionalProperties => 0,
725 properties => {
726 node => get_standard_option('pve-node'),
727 vmid => get_standard_option('pve-vmid'),
728 },
729 },
730 returns => { type => 'object' },
731 code => sub {
732 my ($param) = @_;
733
734 # test if VM exists
735 my $conf = PVE::QemuServer::load_config($param->{vmid});
736
737 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
738
739 return $vmstatus->{$param->{vmid}};
740 }});
741
742 __PACKAGE__->register_method({
743 name => 'vm_start',
744 path => '{vmid}/status/start',
745 method => 'POST',
746 protected => 1,
747 proxyto => 'node',
748 description => "Start virtual machine.",
749 parameters => {
750 additionalProperties => 0,
751 properties => {
752 node => get_standard_option('pve-node'),
753 vmid => get_standard_option('pve-vmid'),
754 skiplock => get_standard_option('skiplock'),
755 stateuri => get_standard_option('pve-qm-stateuri'),
756 },
757 },
758 returns => {
759 type => 'string',
760 },
761 code => sub {
762 my ($param) = @_;
763
764 my $rpcenv = PVE::RPCEnvironment::get();
765
766 my $user = $rpcenv->get_user();
767
768 my $node = extract_param($param, 'node');
769
770 my $vmid = extract_param($param, 'vmid');
771
772 my $stateuri = extract_param($param, 'stateuri');
773 raise_param_exc({ stateuri => "Only root may use this option." })
774 if $stateuri && $user ne 'root@pam';
775
776 my $skiplock = extract_param($param, 'skiplock');
777 raise_param_exc({ skiplock => "Only root may use this option." })
778 if $skiplock && $user ne 'root@pam';
779
780 my $storecfg = PVE::Storage::config();
781
782 my $realcmd = sub {
783 my $upid = shift;
784
785 syslog('info', "start VM $vmid: $upid\n");
786
787 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
788
789 return;
790 };
791
792 return $rpcenv->fork_worker('qmstart', $vmid, $user, $realcmd);
793 }});
794
795 __PACKAGE__->register_method({
796 name => 'vm_stop',
797 path => '{vmid}/status/stop',
798 method => 'POST',
799 protected => 1,
800 proxyto => 'node',
801 description => "Stop virtual machine.",
802 parameters => {
803 additionalProperties => 0,
804 properties => {
805 node => get_standard_option('pve-node'),
806 vmid => get_standard_option('pve-vmid'),
807 skiplock => get_standard_option('skiplock'),
808 timeout => {
809 description => "Wait maximal timeout seconds.",
810 type => 'integer',
811 minimum => 0,
812 optional => 1,
813 }
814 },
815 },
816 returns => {
817 type => 'string',
818 },
819 code => sub {
820 my ($param) = @_;
821
822 my $rpcenv = PVE::RPCEnvironment::get();
823
824 my $user = $rpcenv->get_user();
825
826 my $node = extract_param($param, 'node');
827
828 my $vmid = extract_param($param, 'vmid');
829
830 my $skiplock = extract_param($param, 'skiplock');
831 raise_param_exc({ skiplock => "Only root may use this option." })
832 if $skiplock && $user ne 'root@pam';
833
834 my $realcmd = sub {
835 my $upid = shift;
836
837 syslog('info', "stop VM $vmid: $upid\n");
838
839 PVE::QemuServer::vm_stop($vmid, $skiplock);
840
841 my $pid = PVE::QemuServer::check_running ($vmid);
842
843 if ($pid && $param->{timeout}) {
844 print "waiting until VM $vmid stopps (PID $pid)\n";
845
846 my $count = 0;
847 while (($count < $param->{timeout}) &&
848 PVE::QemuServer::check_running($vmid)) {
849 $count++;
850 sleep 1;
851 }
852
853 die "wait failed - got timeout\n" if PVE::QemuServer::check_running($vmid);
854 }
855
856 return;
857 };
858
859 return $rpcenv->fork_worker('qmstop', $vmid, $user, $realcmd);
860 }});
861
862 __PACKAGE__->register_method({
863 name => 'vm_reset',
864 path => '{vmid}/status/reset',
865 method => 'POST',
866 protected => 1,
867 proxyto => 'node',
868 description => "Reset virtual machine.",
869 parameters => {
870 additionalProperties => 0,
871 properties => {
872 node => get_standard_option('pve-node'),
873 vmid => get_standard_option('pve-vmid'),
874 skiplock => get_standard_option('skiplock'),
875 },
876 },
877 returns => {
878 type => 'string',
879 },
880 code => sub {
881 my ($param) = @_;
882
883 my $rpcenv = PVE::RPCEnvironment::get();
884
885 my $user = $rpcenv->get_user();
886
887 my $node = extract_param($param, 'node');
888
889 my $vmid = extract_param($param, 'vmid');
890
891 my $skiplock = extract_param($param, 'skiplock');
892 raise_param_exc({ skiplock => "Only root may use this option." })
893 if $skiplock && $user ne 'root@pam';
894
895 my $realcmd = sub {
896 my $upid = shift;
897
898 syslog('info', "reset VM $vmid: $upid\n");
899
900 PVE::QemuServer::vm_reset($vmid, $skiplock);
901
902 return;
903 };
904
905 return $rpcenv->fork_worker('qmreset', $vmid, $user, $realcmd);
906 }});
907
908 __PACKAGE__->register_method({
909 name => 'vm_shutdown',
910 path => '{vmid}/status/shutdown',
911 method => 'POST',
912 protected => 1,
913 proxyto => 'node',
914 description => "Shutdown virtual machine.",
915 parameters => {
916 additionalProperties => 0,
917 properties => {
918 node => get_standard_option('pve-node'),
919 vmid => get_standard_option('pve-vmid'),
920 skiplock => get_standard_option('skiplock'),
921 timeout => {
922 description => "Wait maximal timeout seconds.",
923 type => 'integer',
924 minimum => 0,
925 optional => 1,
926 }
927 },
928 },
929 returns => {
930 type => 'string',
931 },
932 code => sub {
933 my ($param) = @_;
934
935 my $rpcenv = PVE::RPCEnvironment::get();
936
937 my $user = $rpcenv->get_user();
938
939 my $node = extract_param($param, 'node');
940
941 my $vmid = extract_param($param, 'vmid');
942
943 my $skiplock = extract_param($param, 'skiplock');
944 raise_param_exc({ skiplock => "Only root may use this option." })
945 if $skiplock && $user ne 'root@pam';
946
947 my $realcmd = sub {
948 my $upid = shift;
949
950 syslog('info', "shutdown VM $vmid: $upid\n");
951
952 PVE::QemuServer::vm_shutdown($vmid, $skiplock);
953
954 my $pid = PVE::QemuServer::check_running ($vmid);
955
956 if ($pid && $param->{timeout}) {
957 print "waiting until VM $vmid stopps (PID $pid)\n";
958
959 my $count = 0;
960 while (($count < $param->{timeout}) &&
961 PVE::QemuServer::check_running($vmid)) {
962 $count++;
963 sleep 1;
964 }
965
966 die "wait failed - got timeout\n" if PVE::QemuServer::check_running($vmid);
967 }
968
969 return;
970 };
971
972 return $rpcenv->fork_worker('qmshutdown', $vmid, $user, $realcmd);
973 }});
974
975 __PACKAGE__->register_method({
976 name => 'vm_suspend',
977 path => '{vmid}/status/suspend',
978 method => 'POST',
979 protected => 1,
980 proxyto => 'node',
981 description => "Suspend virtual machine.",
982 parameters => {
983 additionalProperties => 0,
984 properties => {
985 node => get_standard_option('pve-node'),
986 vmid => get_standard_option('pve-vmid'),
987 skiplock => get_standard_option('skiplock'),
988 },
989 },
990 returns => {
991 type => 'string',
992 },
993 code => sub {
994 my ($param) = @_;
995
996 my $rpcenv = PVE::RPCEnvironment::get();
997
998 my $user = $rpcenv->get_user();
999
1000 my $node = extract_param($param, 'node');
1001
1002 my $vmid = extract_param($param, 'vmid');
1003
1004 my $skiplock = extract_param($param, 'skiplock');
1005 raise_param_exc({ skiplock => "Only root may use this option." })
1006 if $skiplock && $user ne 'root@pam';
1007
1008 my $realcmd = sub {
1009 my $upid = shift;
1010
1011 syslog('info', "suspend VM $vmid: $upid\n");
1012
1013 PVE::QemuServer::vm_suspend($vmid, $skiplock);
1014
1015 return;
1016 };
1017
1018 return $rpcenv->fork_worker('qmsuspend', $vmid, $user, $realcmd);
1019 }});
1020
1021 __PACKAGE__->register_method({
1022 name => 'vm_resume',
1023 path => '{vmid}/status/resume',
1024 method => 'POST',
1025 protected => 1,
1026 proxyto => 'node',
1027 description => "Resume virtual machine.",
1028 parameters => {
1029 additionalProperties => 0,
1030 properties => {
1031 node => get_standard_option('pve-node'),
1032 vmid => get_standard_option('pve-vmid'),
1033 skiplock => get_standard_option('skiplock'),
1034 },
1035 },
1036 returns => {
1037 type => 'string',
1038 },
1039 code => sub {
1040 my ($param) = @_;
1041
1042 my $rpcenv = PVE::RPCEnvironment::get();
1043
1044 my $user = $rpcenv->get_user();
1045
1046 my $node = extract_param($param, 'node');
1047
1048 my $vmid = extract_param($param, 'vmid');
1049
1050 my $skiplock = extract_param($param, 'skiplock');
1051 raise_param_exc({ skiplock => "Only root may use this option." })
1052 if $skiplock && $user ne 'root@pam';
1053
1054 my $realcmd = sub {
1055 my $upid = shift;
1056
1057 syslog('info', "resume VM $vmid: $upid\n");
1058
1059 PVE::QemuServer::vm_resume($vmid, $skiplock);
1060
1061 return;
1062 };
1063
1064 return $rpcenv->fork_worker('qmresume', $vmid, $user, $realcmd);
1065 }});
1066
1067 __PACKAGE__->register_method({
1068 name => 'vm_sendkey',
1069 path => '{vmid}/sendkey',
1070 method => 'PUT',
1071 protected => 1,
1072 proxyto => 'node',
1073 description => "Send key event to virtual machine.",
1074 parameters => {
1075 additionalProperties => 0,
1076 properties => {
1077 node => get_standard_option('pve-node'),
1078 vmid => get_standard_option('pve-vmid'),
1079 skiplock => get_standard_option('skiplock'),
1080 key => {
1081 description => "The key (qemu monitor encoding).",
1082 type => 'string'
1083 }
1084 },
1085 },
1086 returns => { type => 'null'},
1087 code => sub {
1088 my ($param) = @_;
1089
1090 my $rpcenv = PVE::RPCEnvironment::get();
1091
1092 my $user = $rpcenv->get_user();
1093
1094 my $node = extract_param($param, 'node');
1095
1096 my $vmid = extract_param($param, 'vmid');
1097
1098 my $skiplock = extract_param($param, 'skiplock');
1099 raise_param_exc({ skiplock => "Only root may use this option." })
1100 if $skiplock && $user ne 'root@pam';
1101
1102 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1103
1104 return;
1105 }});
1106
1107 __PACKAGE__->register_method({
1108 name => 'migrate_vm',
1109 path => '{vmid}/migrate',
1110 method => 'POST',
1111 protected => 1,
1112 proxyto => 'node',
1113 description => "Migrate virtual machine. Creates a new migration task.",
1114 parameters => {
1115 additionalProperties => 0,
1116 properties => {
1117 node => get_standard_option('pve-node'),
1118 vmid => get_standard_option('pve-vmid'),
1119 target => get_standard_option('pve-node', { description => "Target node." }),
1120 online => {
1121 type => 'boolean',
1122 description => "Use online/live migration.",
1123 optional => 1,
1124 },
1125 force => {
1126 type => 'boolean',
1127 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1128 optional => 1,
1129 },
1130 },
1131 },
1132 returns => {
1133 type => 'string',
1134 description => "the task ID.",
1135 },
1136 code => sub {
1137 my ($param) = @_;
1138
1139 my $rpcenv = PVE::RPCEnvironment::get();
1140
1141 my $user = $rpcenv->get_user();
1142
1143 my $target = extract_param($param, 'target');
1144
1145 my $localnode = PVE::INotify::nodename();
1146 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1147
1148 PVE::Cluster::check_cfs_quorum();
1149
1150 PVE::Cluster::check_node_exists($target);
1151
1152 my $targetip = PVE::Cluster::remote_node_ip($target);
1153
1154 my $vmid = extract_param($param, 'vmid');
1155
1156 raise_param_exc({ force => "Only root may use this option." }) if $user ne 'root@pam';
1157
1158 # test if VM exists
1159 PVE::QemuServer::load_config($vmid);
1160
1161 # try to detect errors early
1162 if (PVE::QemuServer::check_running($vmid)) {
1163 die "cant migrate running VM without --online\n"
1164 if !$param->{online};
1165 }
1166
1167 my $realcmd = sub {
1168 my $upid = shift;
1169
1170 PVE::QemuMigrate::migrate($target, $targetip, $vmid, $param->{online}, $param->{force});
1171 };
1172
1173 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $user, $realcmd);
1174
1175 return $upid;
1176 }});
1177
1178 1;