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