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