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