]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
add timeout parameter for stop/shotdown
[qemu-server.git] / PVE / API2 / Qemu.pm
CommitLineData
1e3baf05
DM
1package PVE::API2::Qemu;
2
3use strict;
4use warnings;
5
6use PVE::Cluster;
7use PVE::SafeSyslog;
8use PVE::Tools qw(extract_param);
9use PVE::Exception qw(raise raise_param_exc);
10use PVE::Storage;
11use PVE::JSONSchema qw(get_standard_option);
12use PVE::RESTHandler;
13use PVE::QemuServer;
3ea94c60 14use PVE::QemuMigrate;
1e3baf05
DM
15use PVE::RPCEnvironment;
16use PVE::AccessControl;
17use PVE::INotify;
18
19use Data::Dumper; # fixme: remove
20
21use base qw(PVE::RESTHandler);
22
23my $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
25my $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 },
5fdbe4f0
DM
80 returns => {
81 type => 'string',
82 },
1e3baf05
DM
83 code => sub {
84 my ($param) = @_;
85
5fdbe4f0
DM
86 my $rpcenv = PVE::RPCEnvironment::get();
87
88 my $user = $rpcenv->get_user();
89
1e3baf05
DM
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
1e3baf05
DM
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
5fdbe4f0 123 my $realcmd = sub {
1e3baf05 124
5fdbe4f0 125 my $vollist = [];
1e3baf05 126
5fdbe4f0
DM
127 eval {
128 $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param);
1e3baf05 129
5fdbe4f0
DM
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 }
1e3baf05 139
5fdbe4f0
DM
140 if (!$param->{bootdisk} && $firstdisk) {
141 $param->{bootdisk} = $firstdisk;
142 }
1e3baf05 143
5fdbe4f0
DM
144 PVE::QemuServer::create_conf_nolock($vmid, $param);
145 };
146 my $err = $@;
1e3baf05 147
5fdbe4f0
DM
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);
1e3baf05
DM
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' },
3ea94c60 194 { subdir => 'migrate' },
1e3baf05
DM
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",
554ac7e7
DM
307 properties => {
308 digest => {
309 type => 'string',
310 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
311 }
312 },
1e3baf05
DM
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'),
3ea94c60 335 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
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 },
554ac7e7
DM
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 }
1e3baf05
DM
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
1e3baf05
DM
365 my $vmid = extract_param($param, 'vmid');
366
5fdbe4f0
DM
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
1e3baf05 374 my $skiplock = extract_param($param, 'skiplock');
3ea94c60
DM
375 raise_param_exc({ skiplock => "Only root may use this option." })
376 if $skiplock && $user ne 'root@pam';
1e3baf05
DM
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
554ac7e7
DM
423 die "checksum missmatch (file change by other user?)\n"
424 if $digest && $digest ne $conf->{digest};
425
1e3baf05
DM
426 PVE::QemuServer::check_lock($conf) if !$skiplock;
427
5fdbe4f0
DM
428 PVE::Cluster::log_msg('info', $user, "update VM $vmid: " . join (' ', @paramarr));
429
1e3baf05
DM
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 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
464 if (PVE::QemuServer::drive_is_cdrom($drive)) {
465 $cdchange->{$opt} = undef;
466 } else {
467 my $volid = $drive->{file};
468
469 if ($volid !~ m|^/|) {
470 my ($path, $owner);
471 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
472 if ($owner && ($owner == $vmid)) {
473 if ($force) {
474 push @$vollist, $volid;
475 } else {
476 PVE::QemuServer::add_unused_volume($conf, $param, $volid);
477 }
478 }
479 }
480 }
481 } elsif ($opt =~ m/^unused/) {
482 push @$vollist, $conf->{$opt};
483 }
484
485 $unset->{$opt} = 1;
486 }
487
488 PVE::QemuServer::create_disks($storecfg, $vmid, $param);
489
490 PVE::QemuServer::change_config_nolock($vmid, $param, $unset, 1);
491
492 return if !PVE::QemuServer::check_running($vmid);
493
494 foreach my $opt (keys %$cdchange) {
495 my $qdn = PVE::QemuServer::qemu_drive_name($opt, 'cdrom');
496 my $path = $cdchange->{$opt};
497 PVE::QemuServer::vm_monitor_command($vmid, "eject $qdn", 0);
498 PVE::QemuServer::vm_monitor_command($vmid, "change $qdn \"$path\"", 0) if $path;
499 }
500 };
501
502 PVE::QemuServer::lock_config($vmid, $updatefn);
503
504 foreach my $volid (@$vollist) {
505 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
506 # fixme: log ?
507 warn $@ if $@;
508 }
509
510 return undef;
511 }});
512
513
514__PACKAGE__->register_method({
515 name => 'destroy_vm',
516 path => '{vmid}',
517 method => 'DELETE',
518 protected => 1,
519 proxyto => 'node',
520 description => "Destroy the vm (also delete all used/owned volumes).",
521 parameters => {
522 additionalProperties => 0,
523 properties => {
524 node => get_standard_option('pve-node'),
525 vmid => get_standard_option('pve-vmid'),
3ea94c60 526 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
527 },
528 },
5fdbe4f0
DM
529 returns => {
530 type => 'string',
531 },
1e3baf05
DM
532 code => sub {
533 my ($param) = @_;
534
535 my $rpcenv = PVE::RPCEnvironment::get();
536
537 my $user = $rpcenv->get_user();
538
539 my $vmid = $param->{vmid};
540
541 my $skiplock = $param->{skiplock};
542 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 543 if $skiplock && $user ne 'root@pam';
1e3baf05 544
5fdbe4f0
DM
545 # test if VM exists
546 my $conf = PVE::QemuServer::load_config($vmid);
547
1e3baf05
DM
548 my $storecfg = PVE::Storage::config();
549
5fdbe4f0
DM
550 my $realcmd = sub {
551 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
552 };
1e3baf05 553
5fdbe4f0 554 return $rpcenv->fork_worker('qmdestroy', $vmid, $user, $realcmd);
1e3baf05
DM
555 }});
556
557__PACKAGE__->register_method({
558 name => 'unlink',
559 path => '{vmid}/unlink',
560 method => 'PUT',
561 protected => 1,
562 proxyto => 'node',
563 description => "Unlink/delete disk images.",
564 parameters => {
565 additionalProperties => 0,
566 properties => {
567 node => get_standard_option('pve-node'),
568 vmid => get_standard_option('pve-vmid'),
569 idlist => {
570 type => 'string', format => 'pve-configid-list',
571 description => "A list of disk IDs you want to delete.",
572 },
573 force => {
574 type => 'boolean',
575 description => $opt_force_description,
576 optional => 1,
577 },
578 },
579 },
580 returns => { type => 'null'},
581 code => sub {
582 my ($param) = @_;
583
584 $param->{delete} = extract_param($param, 'idlist');
585
586 __PACKAGE__->update_vm($param);
587
588 return undef;
589 }});
590
591my $sslcert;
592
593__PACKAGE__->register_method({
594 name => 'vncproxy',
595 path => '{vmid}/vncproxy',
596 method => 'POST',
597 protected => 1,
598 permissions => {
599 path => '/vms/{vmid}',
600 privs => [ 'VM.Console' ],
601 },
602 description => "Creates a TCP VNC proxy connections.",
603 parameters => {
604 additionalProperties => 0,
605 properties => {
606 node => get_standard_option('pve-node'),
607 vmid => get_standard_option('pve-vmid'),
608 },
609 },
610 returns => {
611 additionalProperties => 0,
612 properties => {
613 user => { type => 'string' },
614 ticket => { type => 'string' },
615 cert => { type => 'string' },
616 port => { type => 'integer' },
617 upid => { type => 'string' },
618 },
619 },
620 code => sub {
621 my ($param) = @_;
622
623 my $rpcenv = PVE::RPCEnvironment::get();
624
625 my $user = $rpcenv->get_user();
626 my $ticket = PVE::AccessControl::assemble_ticket($user);
627
628 my $vmid = $param->{vmid};
629 my $node = $param->{node};
630
631 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
632 if !$sslcert;
633
634 my $port = PVE::Tools::next_vnc_port();
635
636 my $remip;
637
638 if ($node ne PVE::INotify::nodename()) {
639 $remip = PVE::Cluster::remote_node_ip($node);
640 }
641
642 # NOTE: kvm VNC traffic is already TLS encrypted,
643 # so we select the fastest chipher here (or 'none'?)
644 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
645 '-c', 'blowfish-cbc', $remip] : [];
646
647 my $timeout = 10;
648
649 my $realcmd = sub {
650 my $upid = shift;
651
652 syslog('info', "starting vnc proxy $upid\n");
653
654 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
655
656 my $qmstr = join(' ', @$qmcmd);
657
658 # also redirect stderr (else we get RFB protocol errors)
be62c45c 659 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 660
be62c45c 661 PVE::Tools::run_command($cmd);
1e3baf05
DM
662
663 return;
664 };
665
666 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd);
667
668 return {
669 user => $user,
670 ticket => $ticket,
671 port => $port,
672 upid => $upid,
673 cert => $sslcert,
674 };
675 }});
676
5fdbe4f0
DM
677__PACKAGE__->register_method({
678 name => 'vmcmdidx',
679 path => '{vmid}/status',
680 method => 'GET',
681 proxyto => 'node',
682 description => "Directory index",
683 parameters => {
684 additionalProperties => 0,
685 properties => {
686 node => get_standard_option('pve-node'),
687 vmid => get_standard_option('pve-vmid'),
688 },
689 },
690 returns => {
691 type => 'array',
692 items => {
693 type => "object",
694 properties => {
695 subdir => { type => 'string' },
696 },
697 },
698 links => [ { rel => 'child', href => "{subdir}" } ],
699 },
700 code => sub {
701 my ($param) = @_;
702
703 # test if VM exists
704 my $conf = PVE::QemuServer::load_config($param->{vmid});
705
706 my $res = [
707 { subdir => 'current' },
708 { subdir => 'start' },
709 { subdir => 'stop' },
710 ];
711
712 return $res;
713 }});
714
1e3baf05
DM
715__PACKAGE__->register_method({
716 name => 'vm_status',
5fdbe4f0 717 path => '{vmid}/status/current',
1e3baf05
DM
718 method => 'GET',
719 proxyto => 'node',
720 protected => 1, # qemu pid files are only readable by root
721 description => "Get virtual machine status.",
722 parameters => {
723 additionalProperties => 0,
724 properties => {
725 node => get_standard_option('pve-node'),
726 vmid => get_standard_option('pve-vmid'),
727 },
728 },
729 returns => { type => 'object' },
730 code => sub {
731 my ($param) = @_;
732
733 # test if VM exists
734 my $conf = PVE::QemuServer::load_config($param->{vmid});
735
736 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
737
738 return $vmstatus->{$param->{vmid}};
739 }});
740
741__PACKAGE__->register_method({
5fdbe4f0
DM
742 name => 'vm_start',
743 path => '{vmid}/status/start',
744 method => 'POST',
1e3baf05
DM
745 protected => 1,
746 proxyto => 'node',
5fdbe4f0 747 description => "Start virtual machine.",
1e3baf05
DM
748 parameters => {
749 additionalProperties => 0,
750 properties => {
751 node => get_standard_option('pve-node'),
752 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
753 skiplock => get_standard_option('skiplock'),
754 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05
DM
755 },
756 },
5fdbe4f0
DM
757 returns => {
758 type => 'string',
759 },
1e3baf05
DM
760 code => sub {
761 my ($param) = @_;
762
763 my $rpcenv = PVE::RPCEnvironment::get();
764
765 my $user = $rpcenv->get_user();
766
767 my $node = extract_param($param, 'node');
768
1e3baf05
DM
769 my $vmid = extract_param($param, 'vmid');
770
3ea94c60
DM
771 my $stateuri = extract_param($param, 'stateuri');
772 raise_param_exc({ stateuri => "Only root may use this option." })
773 if $stateuri && $user ne 'root@pam';
774
1e3baf05
DM
775 my $skiplock = extract_param($param, 'skiplock');
776 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 777 if $skiplock && $user ne 'root@pam';
1e3baf05 778
1e3baf05 779 my $storecfg = PVE::Storage::config();
5fdbe4f0
DM
780
781 my $realcmd = sub {
782 my $upid = shift;
783
784 syslog('info', "start VM $vmid: $upid\n");
785
3ea94c60 786 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
5fdbe4f0
DM
787
788 return;
789 };
790
791 return $rpcenv->fork_worker('qmstart', $vmid, $user, $realcmd);
792 }});
793
794__PACKAGE__->register_method({
795 name => 'vm_stop',
796 path => '{vmid}/status/stop',
797 method => 'POST',
798 protected => 1,
799 proxyto => 'node',
800 description => "Stop virtual machine.",
801 parameters => {
802 additionalProperties => 0,
803 properties => {
804 node => get_standard_option('pve-node'),
805 vmid => get_standard_option('pve-vmid'),
806 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
807 timeout => {
808 description => "Wait maximal timeout seconds.",
809 type => 'integer',
810 minimum => 0,
811 optional => 1,
812 }
5fdbe4f0
DM
813 },
814 },
815 returns => {
816 type => 'string',
817 },
818 code => sub {
819 my ($param) = @_;
820
821 my $rpcenv = PVE::RPCEnvironment::get();
822
823 my $user = $rpcenv->get_user();
824
825 my $node = extract_param($param, 'node');
826
827 my $vmid = extract_param($param, 'vmid');
828
829 my $skiplock = extract_param($param, 'skiplock');
830 raise_param_exc({ skiplock => "Only root may use this option." })
831 if $skiplock && $user ne 'root@pam';
832
833 my $realcmd = sub {
834 my $upid = shift;
835
836 syslog('info', "stop VM $vmid: $upid\n");
837
1e3baf05 838 PVE::QemuServer::vm_stop($vmid, $skiplock);
5fdbe4f0 839
c6bb9502
DM
840 my $pid = PVE::QemuServer::check_running ($vmid);
841
842 if ($pid && $param->{timeout}) {
843 print "waiting until VM $vmid stopps (PID $pid)\n";
844
845 my $count = 0;
846 while (($count < $param->{timeout}) &&
847 PVE::QemuServer::check_running($vmid)) {
848 $count++;
849 sleep 1;
850 }
851
852 die "wait failed - got timeout\n" if PVE::QemuServer::check_running($vmid);
853 }
854
5fdbe4f0
DM
855 return;
856 };
857
858 return $rpcenv->fork_worker('qmstop', $vmid, $user, $realcmd);
859 }});
860
861__PACKAGE__->register_method({
862 name => 'vm_reset',
863 path => '{vmid}/status/reset',
864 method => 'POST',
865 protected => 1,
866 proxyto => 'node',
867 description => "Reset virtual machine.",
868 parameters => {
869 additionalProperties => 0,
870 properties => {
871 node => get_standard_option('pve-node'),
872 vmid => get_standard_option('pve-vmid'),
873 skiplock => get_standard_option('skiplock'),
874 },
875 },
876 returns => {
877 type => 'string',
878 },
879 code => sub {
880 my ($param) = @_;
881
882 my $rpcenv = PVE::RPCEnvironment::get();
883
884 my $user = $rpcenv->get_user();
885
886 my $node = extract_param($param, 'node');
887
888 my $vmid = extract_param($param, 'vmid');
889
890 my $skiplock = extract_param($param, 'skiplock');
891 raise_param_exc({ skiplock => "Only root may use this option." })
892 if $skiplock && $user ne 'root@pam';
893
894 my $realcmd = sub {
895 my $upid = shift;
896
897 syslog('info', "reset VM $vmid: $upid\n");
898
1e3baf05 899 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
900
901 return;
902 };
903
904 return $rpcenv->fork_worker('qmreset', $vmid, $user, $realcmd);
905 }});
906
907__PACKAGE__->register_method({
908 name => 'vm_shutdown',
909 path => '{vmid}/status/shutdown',
910 method => 'POST',
911 protected => 1,
912 proxyto => 'node',
913 description => "Shutdown virtual machine.",
914 parameters => {
915 additionalProperties => 0,
916 properties => {
917 node => get_standard_option('pve-node'),
918 vmid => get_standard_option('pve-vmid'),
919 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
920 timeout => {
921 description => "Wait maximal timeout seconds.",
922 type => 'integer',
923 minimum => 0,
924 optional => 1,
925 }
5fdbe4f0
DM
926 },
927 },
928 returns => {
929 type => 'string',
930 },
931 code => sub {
932 my ($param) = @_;
933
934 my $rpcenv = PVE::RPCEnvironment::get();
935
936 my $user = $rpcenv->get_user();
937
938 my $node = extract_param($param, 'node');
939
940 my $vmid = extract_param($param, 'vmid');
941
942 my $skiplock = extract_param($param, 'skiplock');
943 raise_param_exc({ skiplock => "Only root may use this option." })
944 if $skiplock && $user ne 'root@pam';
945
946 my $realcmd = sub {
947 my $upid = shift;
948
949 syslog('info', "shutdown VM $vmid: $upid\n");
950
1e3baf05 951 PVE::QemuServer::vm_shutdown($vmid, $skiplock);
5fdbe4f0 952
c6bb9502
DM
953 my $pid = PVE::QemuServer::check_running ($vmid);
954
955 if ($pid && $param->{timeout}) {
956 print "waiting until VM $vmid stopps (PID $pid)\n";
957
958 my $count = 0;
959 while (($count < $param->{timeout}) &&
960 PVE::QemuServer::check_running($vmid)) {
961 $count++;
962 sleep 1;
963 }
964
965 die "wait failed - got timeout\n" if PVE::QemuServer::check_running($vmid);
966 }
967
5fdbe4f0
DM
968 return;
969 };
970
971 return $rpcenv->fork_worker('qmshutdown', $vmid, $user, $realcmd);
972 }});
973
974__PACKAGE__->register_method({
975 name => 'vm_suspend',
976 path => '{vmid}/status/suspend',
977 method => 'POST',
978 protected => 1,
979 proxyto => 'node',
980 description => "Suspend virtual machine.",
981 parameters => {
982 additionalProperties => 0,
983 properties => {
984 node => get_standard_option('pve-node'),
985 vmid => get_standard_option('pve-vmid'),
986 skiplock => get_standard_option('skiplock'),
987 },
988 },
989 returns => {
990 type => 'string',
991 },
992 code => sub {
993 my ($param) = @_;
994
995 my $rpcenv = PVE::RPCEnvironment::get();
996
997 my $user = $rpcenv->get_user();
998
999 my $node = extract_param($param, 'node');
1000
1001 my $vmid = extract_param($param, 'vmid');
1002
1003 my $skiplock = extract_param($param, 'skiplock');
1004 raise_param_exc({ skiplock => "Only root may use this option." })
1005 if $skiplock && $user ne 'root@pam';
1006
1007 my $realcmd = sub {
1008 my $upid = shift;
1009
1010 syslog('info', "suspend VM $vmid: $upid\n");
1011
1e3baf05 1012 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1013
1014 return;
1015 };
1016
1017 return $rpcenv->fork_worker('qmsuspend', $vmid, $user, $realcmd);
1018 }});
1019
1020__PACKAGE__->register_method({
1021 name => 'vm_resume',
1022 path => '{vmid}/status/resume',
1023 method => 'POST',
1024 protected => 1,
1025 proxyto => 'node',
1026 description => "Resume virtual machine.",
1027 parameters => {
1028 additionalProperties => 0,
1029 properties => {
1030 node => get_standard_option('pve-node'),
1031 vmid => get_standard_option('pve-vmid'),
1032 skiplock => get_standard_option('skiplock'),
1033 },
1034 },
1035 returns => {
1036 type => 'string',
1037 },
1038 code => sub {
1039 my ($param) = @_;
1040
1041 my $rpcenv = PVE::RPCEnvironment::get();
1042
1043 my $user = $rpcenv->get_user();
1044
1045 my $node = extract_param($param, 'node');
1046
1047 my $vmid = extract_param($param, 'vmid');
1048
1049 my $skiplock = extract_param($param, 'skiplock');
1050 raise_param_exc({ skiplock => "Only root may use this option." })
1051 if $skiplock && $user ne 'root@pam';
1052
1053 my $realcmd = sub {
1054 my $upid = shift;
1055
1056 syslog('info', "resume VM $vmid: $upid\n");
1057
1e3baf05 1058 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1059
5fdbe4f0
DM
1060 return;
1061 };
1062
1063 return $rpcenv->fork_worker('qmresume', $vmid, $user, $realcmd);
1064 }});
1065
1066__PACKAGE__->register_method({
1067 name => 'vm_sendkey',
1068 path => '{vmid}/sendkey',
1069 method => 'PUT',
1070 protected => 1,
1071 proxyto => 'node',
1072 description => "Send key event to virtual machine.",
1073 parameters => {
1074 additionalProperties => 0,
1075 properties => {
1076 node => get_standard_option('pve-node'),
1077 vmid => get_standard_option('pve-vmid'),
1078 skiplock => get_standard_option('skiplock'),
1079 key => {
1080 description => "The key (qemu monitor encoding).",
1081 type => 'string'
1082 }
1083 },
1084 },
1085 returns => { type => 'null'},
1086 code => sub {
1087 my ($param) = @_;
1088
1089 my $rpcenv = PVE::RPCEnvironment::get();
1090
1091 my $user = $rpcenv->get_user();
1092
1093 my $node = extract_param($param, 'node');
1094
1095 my $vmid = extract_param($param, 'vmid');
1096
1097 my $skiplock = extract_param($param, 'skiplock');
1098 raise_param_exc({ skiplock => "Only root may use this option." })
1099 if $skiplock && $user ne 'root@pam';
1100
1101 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1102
1103 return;
1e3baf05
DM
1104 }});
1105
3ea94c60
DM
1106__PACKAGE__->register_method({
1107 name => 'migrate_vm',
1108 path => '{vmid}/migrate',
1109 method => 'POST',
1110 protected => 1,
1111 proxyto => 'node',
1112 description => "Migrate virtual machine. Creates a new migration task.",
1113 parameters => {
1114 additionalProperties => 0,
1115 properties => {
1116 node => get_standard_option('pve-node'),
1117 vmid => get_standard_option('pve-vmid'),
1118 target => get_standard_option('pve-node', { description => "Target node." }),
1119 online => {
1120 type => 'boolean',
1121 description => "Use online/live migration.",
1122 optional => 1,
1123 },
1124 force => {
1125 type => 'boolean',
1126 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1127 optional => 1,
1128 },
1129 },
1130 },
1131 returns => {
1132 type => 'string',
1133 description => "the task ID.",
1134 },
1135 code => sub {
1136 my ($param) = @_;
1137
1138 my $rpcenv = PVE::RPCEnvironment::get();
1139
1140 my $user = $rpcenv->get_user();
1141
1142 my $target = extract_param($param, 'target');
1143
1144 my $localnode = PVE::INotify::nodename();
1145 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1146
1147 PVE::Cluster::check_cfs_quorum();
1148
1149 PVE::Cluster::check_node_exists($target);
1150
1151 my $targetip = PVE::Cluster::remote_node_ip($target);
1152
1153 my $vmid = extract_param($param, 'vmid');
1154
1155 raise_param_exc({ force => "Only root may use this option." }) if $user ne 'root@pam';
1156
1157 # test if VM exists
1158 PVE::QemuServer::load_config($vmid);
1159
1160 # try to detect errors early
1161 if (PVE::QemuServer::check_running($vmid)) {
1162 die "cant migrate running VM without --online\n"
1163 if !$param->{online};
1164 }
1165
1166 my $realcmd = sub {
1167 my $upid = shift;
1168
1169 PVE::QemuMigrate::migrate($target, $targetip, $vmid, $param->{online}, $param->{force});
1170 };
1171
1172 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $user, $realcmd);
1173
1174 return $upid;
1175 }});
1e3baf05
DM
1176
11771;