]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
use new syntax for permission attribute
[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 {
971f27c4 153 my $path;
5b9d692a 154 if (PVE::Storage::parse_volume_id($archive, 1)) {
971f27c4 155 $path = PVE::Storage::path($storecfg, $archive);
5b9d692a
DM
156 } else {
157 raise_param_exc({ archive => "Only root can pass arbitrary paths." })
158 if $user ne 'root@pam';
159
971f27c4 160 $path = abs_path($archive);
5b9d692a 161 }
971f27c4
DM
162 die "can't find archive file '$archive'\n" if !($path && -f $path);
163 $archive = $path;
164 }
1e3baf05
DM
165 }
166
3e16d5fc
DM
167 my $restorefn = sub {
168
169 if (-f $filename) {
170 die "unable to restore vm $vmid: config file already exists\n"
51586c3a 171 if !$force;
3e16d5fc
DM
172
173 die "unable to restore vm $vmid: vm is running\n"
174 if PVE::QemuServer::check_running($vmid);
a6af7b3e
DM
175
176 # destroy existing data - keep empty config
177 PVE::QemuServer::destroy_vm($storecfg, $vmid, 1);
3e16d5fc
DM
178 }
179
180 my $realcmd = sub {
51586c3a
DM
181 PVE::QemuServer::restore_archive($archive, $vmid, {
182 storage => $storage,
183 unique => $unique });
3e16d5fc
DM
184 };
185
186 return $rpcenv->fork_worker('qmrestore', $vmid, $user, $realcmd);
187 };
1e3baf05 188
1e3baf05
DM
189 my $createfn = sub {
190
191 # second test (after locking test is accurate)
192 die "unable to create vm $vmid: config file already exists\n"
193 if -f $filename;
194
5fdbe4f0 195 my $realcmd = sub {
1e3baf05 196
5fdbe4f0 197 my $vollist = [];
1e3baf05 198
5fdbe4f0 199 eval {
3e16d5fc 200 $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param, $storage);
1e3baf05 201
5fdbe4f0
DM
202 # try to be smart about bootdisk
203 my @disks = PVE::QemuServer::disknames();
204 my $firstdisk;
205 foreach my $ds (reverse @disks) {
206 next if !$param->{$ds};
207 my $disk = PVE::QemuServer::parse_drive($ds, $param->{$ds});
208 next if PVE::QemuServer::drive_is_cdrom($disk);
209 $firstdisk = $ds;
210 }
1e3baf05 211
5fdbe4f0
DM
212 if (!$param->{bootdisk} && $firstdisk) {
213 $param->{bootdisk} = $firstdisk;
214 }
1e3baf05 215
5fdbe4f0
DM
216 PVE::QemuServer::create_conf_nolock($vmid, $param);
217 };
218 my $err = $@;
1e3baf05 219
5fdbe4f0
DM
220 if ($err) {
221 foreach my $volid (@$vollist) {
222 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
223 warn $@ if $@;
224 }
225 die "create failed - $err";
226 }
227 };
228
229 return $rpcenv->fork_worker('qmcreate', $vmid, $user, $realcmd);
230 };
231
3e16d5fc 232 return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn);
1e3baf05
DM
233 }});
234
235__PACKAGE__->register_method({
236 name => 'vmdiridx',
237 path => '{vmid}',
238 method => 'GET',
239 proxyto => 'node',
240 description => "Directory index",
241 parameters => {
242 additionalProperties => 0,
243 properties => {
244 node => get_standard_option('pve-node'),
245 vmid => get_standard_option('pve-vmid'),
246 },
247 },
248 returns => {
249 type => 'array',
250 items => {
251 type => "object",
252 properties => {
253 subdir => { type => 'string' },
254 },
255 },
256 links => [ { rel => 'child', href => "{subdir}" } ],
257 },
258 code => sub {
259 my ($param) = @_;
260
261 my $res = [
262 { subdir => 'config' },
263 { subdir => 'status' },
264 { subdir => 'unlink' },
265 { subdir => 'vncproxy' },
3ea94c60 266 { subdir => 'migrate' },
1e3baf05
DM
267 { subdir => 'rrd' },
268 { subdir => 'rrddata' },
91c94f0a 269 { subdir => 'monitor' },
1e3baf05
DM
270 ];
271
272 return $res;
273 }});
274
275__PACKAGE__->register_method({
276 name => 'rrd',
277 path => '{vmid}/rrd',
278 method => 'GET',
279 protected => 1, # fixme: can we avoid that?
280 permissions => {
378b359e 281 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
282 },
283 description => "Read VM RRD statistics (returns PNG)",
284 parameters => {
285 additionalProperties => 0,
286 properties => {
287 node => get_standard_option('pve-node'),
288 vmid => get_standard_option('pve-vmid'),
289 timeframe => {
290 description => "Specify the time frame you are interested in.",
291 type => 'string',
292 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
293 },
294 ds => {
295 description => "The list of datasources you want to display.",
296 type => 'string', format => 'pve-configid-list',
297 },
298 cf => {
299 description => "The RRD consolidation function",
300 type => 'string',
301 enum => [ 'AVERAGE', 'MAX' ],
302 optional => 1,
303 },
304 },
305 },
306 returns => {
307 type => "object",
308 properties => {
309 filename => { type => 'string' },
310 },
311 },
312 code => sub {
313 my ($param) = @_;
314
315 return PVE::Cluster::create_rrd_graph(
316 "pve2-vm/$param->{vmid}", $param->{timeframe},
317 $param->{ds}, $param->{cf});
318
319 }});
320
321__PACKAGE__->register_method({
322 name => 'rrddata',
323 path => '{vmid}/rrddata',
324 method => 'GET',
325 protected => 1, # fixme: can we avoid that?
326 permissions => {
378b359e 327 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
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
fcdb0117 440 my @hotplugerr = ();
5fdbe4f0
DM
441 my @paramarr = (); # used for log message
442 foreach my $key (keys %$param) {
443 push @paramarr, "-$key", $param->{$key};
444 }
445
1e3baf05 446 my $skiplock = extract_param($param, 'skiplock');
3ea94c60
DM
447 raise_param_exc({ skiplock => "Only root may use this option." })
448 if $skiplock && $user ne 'root@pam';
1e3baf05
DM
449
450 my $delete = extract_param($param, 'delete');
451 my $force = extract_param($param, 'force');
452
453 die "no options specified\n" if !$delete && !scalar(keys %$param);
454
455 my $storecfg = PVE::Storage::config();
456
457 &$resolve_cdrom_alias($param);
458
459 my $eject = {};
460 my $cdchange = {};
461
462 foreach my $opt (keys %$param) {
463 if (PVE::QemuServer::valid_drivename($opt)) {
464 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
465 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
466 if ($drive->{file} eq 'eject') {
467 $eject->{$opt} = 1;
468 delete $param->{$opt};
469 next;
470 }
471
472 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
473 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
474
475 if (PVE::QemuServer::drive_is_cdrom($drive)) {
476 $cdchange->{$opt} = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
477 }
478 }
479 }
480
481 foreach my $opt (PVE::Tools::split_list($delete)) {
482 $opt = 'ide2' if $opt eq 'cdrom';
483 die "you can't use '-$opt' and '-delete $opt' at the same time\n"
484 if defined($param->{$opt});
485 }
486
487 PVE::QemuServer::add_random_macs($param);
488
489 my $vollist = [];
490
491 my $updatefn = sub {
492
493 my $conf = PVE::QemuServer::load_config($vmid);
494
554ac7e7
DM
495 die "checksum missmatch (file change by other user?)\n"
496 if $digest && $digest ne $conf->{digest};
497
1e3baf05
DM
498 PVE::QemuServer::check_lock($conf) if !$skiplock;
499
5fdbe4f0
DM
500 PVE::Cluster::log_msg('info', $user, "update VM $vmid: " . join (' ', @paramarr));
501
fcdb0117
DA
502 my @newdelete = ();
503 foreach my $opt (PVE::Tools::split_list($delete)) {
504 if(PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt)) {
505 push(@newdelete, $opt);
506 }
507 else {
508 push(@hotplugerr, $opt);
509 }
510 }
511 $delete = join(',', @newdelete) if scalar(@newdelete) > 0;
512
1e3baf05
DM
513 foreach my $opt (keys %$eject) {
514 if ($conf->{$opt}) {
515 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
516 $cdchange->{$opt} = undef if PVE::QemuServer::drive_is_cdrom($drive);
517 } else {
518 raise_param_exc({ $opt => "eject failed - drive does not exist." });
519 }
520 }
521
522 foreach my $opt (keys %$param) {
523 next if !PVE::QemuServer::valid_drivename($opt);
524 next if !$conf->{$opt};
525 my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
526 next if PVE::QemuServer::drive_is_cdrom($old_drive);
527 my $new_drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
528 if ($new_drive->{file} ne $old_drive->{file}) {
529 my ($path, $owner);
530 eval { ($path, $owner) = PVE::Storage::path($storecfg, $old_drive->{file}); };
531 if ($owner && ($owner == $vmid)) {
532 PVE::QemuServer::add_unused_volume($conf, $param, $old_drive->{file});
533 }
534 }
535 }
536
537 my $unset = {};
538
539 foreach my $opt (PVE::Tools::split_list($delete)) {
540 $opt = 'ide2' if $opt eq 'cdrom';
541 if (!PVE::QemuServer::option_exists($opt)) {
542 raise_param_exc({ delete => "unknown option '$opt'" });
543 }
544 next if !defined($conf->{$opt});
545 if (PVE::QemuServer::valid_drivename($opt)) {
546 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
547 if (PVE::QemuServer::drive_is_cdrom($drive)) {
548 $cdchange->{$opt} = undef;
549 } else {
550 my $volid = $drive->{file};
551
552 if ($volid !~ m|^/|) {
553 my ($path, $owner);
554 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
555 if ($owner && ($owner == $vmid)) {
556 if ($force) {
557 push @$vollist, $volid;
558 } else {
559 PVE::QemuServer::add_unused_volume($conf, $param, $volid);
560 }
561 }
562 }
563 }
564 } elsif ($opt =~ m/^unused/) {
565 push @$vollist, $conf->{$opt};
566 }
567
568 $unset->{$opt} = 1;
569 }
570
f19d1c47 571 PVE::QemuServer::create_disks($storecfg, $vmid, $param, $conf);
1e3baf05 572
fcdb0117
DA
573 #hotplug disks
574 foreach my $opt (keys %$param) {
575 if($opt =~ m/^(scsi|virtio)(\d+)$/) {
576 my $device = PVE::QemuServer::parse_drive($opt, $param->{$opt});
577 if(!PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $device)) {
578 $unset->{$opt} = 1;
579 PVE::QemuServer::add_unused_volume($param, $device->{file});
580 push(@hotplugerr, $opt);
581 }
582 }
583 }
584
1e3baf05
DM
585 PVE::QemuServer::change_config_nolock($vmid, $param, $unset, 1);
586
587 return if !PVE::QemuServer::check_running($vmid);
588
589 foreach my $opt (keys %$cdchange) {
590 my $qdn = PVE::QemuServer::qemu_drive_name($opt, 'cdrom');
591 my $path = $cdchange->{$opt};
592 PVE::QemuServer::vm_monitor_command($vmid, "eject $qdn", 0);
593 PVE::QemuServer::vm_monitor_command($vmid, "change $qdn \"$path\"", 0) if $path;
594 }
595 };
596
597 PVE::QemuServer::lock_config($vmid, $updatefn);
598
599 foreach my $volid (@$vollist) {
600 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
601 # fixme: log ?
602 warn $@ if $@;
603 }
604
fcdb0117
DA
605 raise_param_exc({ hotplug => "error hotplug/unplug ".join(',', @hotplugerr)})
606 if scalar(@hotplugerr) > 0;
607
1e3baf05
DM
608 return undef;
609 }});
610
611
612__PACKAGE__->register_method({
613 name => 'destroy_vm',
614 path => '{vmid}',
615 method => 'DELETE',
616 protected => 1,
617 proxyto => 'node',
618 description => "Destroy the vm (also delete all used/owned volumes).",
619 parameters => {
620 additionalProperties => 0,
621 properties => {
622 node => get_standard_option('pve-node'),
623 vmid => get_standard_option('pve-vmid'),
3ea94c60 624 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
625 },
626 },
5fdbe4f0
DM
627 returns => {
628 type => 'string',
629 },
1e3baf05
DM
630 code => sub {
631 my ($param) = @_;
632
633 my $rpcenv = PVE::RPCEnvironment::get();
634
635 my $user = $rpcenv->get_user();
636
637 my $vmid = $param->{vmid};
638
639 my $skiplock = $param->{skiplock};
640 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 641 if $skiplock && $user ne 'root@pam';
1e3baf05 642
5fdbe4f0
DM
643 # test if VM exists
644 my $conf = PVE::QemuServer::load_config($vmid);
645
1e3baf05
DM
646 my $storecfg = PVE::Storage::config();
647
5fdbe4f0 648 my $realcmd = sub {
ff1a2432
DM
649 my $upid = shift;
650
651 syslog('info', "destroy VM $vmid: $upid\n");
652
5fdbe4f0
DM
653 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
654 };
1e3baf05 655
5fdbe4f0 656 return $rpcenv->fork_worker('qmdestroy', $vmid, $user, $realcmd);
1e3baf05
DM
657 }});
658
659__PACKAGE__->register_method({
660 name => 'unlink',
661 path => '{vmid}/unlink',
662 method => 'PUT',
663 protected => 1,
664 proxyto => 'node',
665 description => "Unlink/delete disk images.",
666 parameters => {
667 additionalProperties => 0,
668 properties => {
669 node => get_standard_option('pve-node'),
670 vmid => get_standard_option('pve-vmid'),
671 idlist => {
672 type => 'string', format => 'pve-configid-list',
673 description => "A list of disk IDs you want to delete.",
674 },
675 force => {
676 type => 'boolean',
677 description => $opt_force_description,
678 optional => 1,
679 },
680 },
681 },
682 returns => { type => 'null'},
683 code => sub {
684 my ($param) = @_;
685
686 $param->{delete} = extract_param($param, 'idlist');
687
688 __PACKAGE__->update_vm($param);
689
690 return undef;
691 }});
692
693my $sslcert;
694
695__PACKAGE__->register_method({
696 name => 'vncproxy',
697 path => '{vmid}/vncproxy',
698 method => 'POST',
699 protected => 1,
700 permissions => {
378b359e 701 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
702 },
703 description => "Creates a TCP VNC proxy connections.",
704 parameters => {
705 additionalProperties => 0,
706 properties => {
707 node => get_standard_option('pve-node'),
708 vmid => get_standard_option('pve-vmid'),
709 },
710 },
711 returns => {
712 additionalProperties => 0,
713 properties => {
714 user => { type => 'string' },
715 ticket => { type => 'string' },
716 cert => { type => 'string' },
717 port => { type => 'integer' },
718 upid => { type => 'string' },
719 },
720 },
721 code => sub {
722 my ($param) = @_;
723
724 my $rpcenv = PVE::RPCEnvironment::get();
725
726 my $user = $rpcenv->get_user();
1e3baf05
DM
727
728 my $vmid = $param->{vmid};
729 my $node = $param->{node};
730
b6f39da2
DM
731 my $authpath = "/vms/$vmid";
732
733 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
734
1e3baf05
DM
735 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
736 if !$sslcert;
737
738 my $port = PVE::Tools::next_vnc_port();
739
740 my $remip;
741
4f1be36c 742 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1e3baf05
DM
743 $remip = PVE::Cluster::remote_node_ip($node);
744 }
745
746 # NOTE: kvm VNC traffic is already TLS encrypted,
747 # so we select the fastest chipher here (or 'none'?)
748 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
749 '-c', 'blowfish-cbc', $remip] : [];
750
751 my $timeout = 10;
752
753 my $realcmd = sub {
754 my $upid = shift;
755
756 syslog('info', "starting vnc proxy $upid\n");
757
758 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
759
760 my $qmstr = join(' ', @$qmcmd);
761
762 # also redirect stderr (else we get RFB protocol errors)
be62c45c 763 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 764
be62c45c 765 PVE::Tools::run_command($cmd);
1e3baf05
DM
766
767 return;
768 };
769
770 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd);
771
772 return {
773 user => $user,
774 ticket => $ticket,
775 port => $port,
776 upid => $upid,
777 cert => $sslcert,
778 };
779 }});
780
5fdbe4f0
DM
781__PACKAGE__->register_method({
782 name => 'vmcmdidx',
783 path => '{vmid}/status',
784 method => 'GET',
785 proxyto => 'node',
786 description => "Directory index",
787 parameters => {
788 additionalProperties => 0,
789 properties => {
790 node => get_standard_option('pve-node'),
791 vmid => get_standard_option('pve-vmid'),
792 },
793 },
794 returns => {
795 type => 'array',
796 items => {
797 type => "object",
798 properties => {
799 subdir => { type => 'string' },
800 },
801 },
802 links => [ { rel => 'child', href => "{subdir}" } ],
803 },
804 code => sub {
805 my ($param) = @_;
806
807 # test if VM exists
808 my $conf = PVE::QemuServer::load_config($param->{vmid});
809
810 my $res = [
811 { subdir => 'current' },
812 { subdir => 'start' },
813 { subdir => 'stop' },
814 ];
815
816 return $res;
817 }});
818
1e3baf05
DM
819__PACKAGE__->register_method({
820 name => 'vm_status',
5fdbe4f0 821 path => '{vmid}/status/current',
1e3baf05
DM
822 method => 'GET',
823 proxyto => 'node',
824 protected => 1, # qemu pid files are only readable by root
825 description => "Get virtual machine status.",
826 parameters => {
827 additionalProperties => 0,
828 properties => {
829 node => get_standard_option('pve-node'),
830 vmid => get_standard_option('pve-vmid'),
831 },
832 },
833 returns => { type => 'object' },
834 code => sub {
835 my ($param) = @_;
836
837 # test if VM exists
838 my $conf = PVE::QemuServer::load_config($param->{vmid});
839
ff1a2432 840 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
8610701a 841 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 842
8610701a
DM
843 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
844 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $param->{vmid}, 1)) {
845 $status->{ha} = 1;
846 } else {
847 $status->{ha} = 0;
848 }
849
850 return $status;
1e3baf05
DM
851 }});
852
853__PACKAGE__->register_method({
5fdbe4f0
DM
854 name => 'vm_start',
855 path => '{vmid}/status/start',
856 method => 'POST',
1e3baf05
DM
857 protected => 1,
858 proxyto => 'node',
5fdbe4f0 859 description => "Start virtual machine.",
1e3baf05
DM
860 parameters => {
861 additionalProperties => 0,
862 properties => {
863 node => get_standard_option('pve-node'),
864 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
865 skiplock => get_standard_option('skiplock'),
866 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05
DM
867 },
868 },
5fdbe4f0
DM
869 returns => {
870 type => 'string',
871 },
1e3baf05
DM
872 code => sub {
873 my ($param) = @_;
874
875 my $rpcenv = PVE::RPCEnvironment::get();
876
877 my $user = $rpcenv->get_user();
878
879 my $node = extract_param($param, 'node');
880
1e3baf05
DM
881 my $vmid = extract_param($param, 'vmid');
882
3ea94c60
DM
883 my $stateuri = extract_param($param, 'stateuri');
884 raise_param_exc({ stateuri => "Only root may use this option." })
885 if $stateuri && $user ne 'root@pam';
886
1e3baf05
DM
887 my $skiplock = extract_param($param, 'skiplock');
888 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 889 if $skiplock && $user ne 'root@pam';
1e3baf05 890
1e3baf05 891 my $storecfg = PVE::Storage::config();
5fdbe4f0
DM
892
893 my $realcmd = sub {
894 my $upid = shift;
895
896 syslog('info', "start VM $vmid: $upid\n");
897
3ea94c60 898 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
5fdbe4f0
DM
899
900 return;
901 };
902
903 return $rpcenv->fork_worker('qmstart', $vmid, $user, $realcmd);
904 }});
905
906__PACKAGE__->register_method({
907 name => 'vm_stop',
908 path => '{vmid}/status/stop',
909 method => 'POST',
910 protected => 1,
911 proxyto => 'node',
912 description => "Stop virtual machine.",
913 parameters => {
914 additionalProperties => 0,
915 properties => {
916 node => get_standard_option('pve-node'),
917 vmid => get_standard_option('pve-vmid'),
918 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
919 timeout => {
920 description => "Wait maximal timeout seconds.",
921 type => 'integer',
922 minimum => 0,
923 optional => 1,
254575e9
DM
924 },
925 keepActive => {
926 description => "Do not decativate storage volumes.",
927 type => 'boolean',
928 optional => 1,
929 default => 0,
c6bb9502 930 }
5fdbe4f0
DM
931 },
932 },
933 returns => {
934 type => 'string',
935 },
936 code => sub {
937 my ($param) = @_;
938
939 my $rpcenv = PVE::RPCEnvironment::get();
940
941 my $user = $rpcenv->get_user();
942
943 my $node = extract_param($param, 'node');
944
945 my $vmid = extract_param($param, 'vmid');
946
947 my $skiplock = extract_param($param, 'skiplock');
948 raise_param_exc({ skiplock => "Only root may use this option." })
949 if $skiplock && $user ne 'root@pam';
950
254575e9
DM
951 my $keepActive = extract_param($param, 'keepActive');
952 raise_param_exc({ keepActive => "Only root may use this option." })
953 if $keepActive && $user ne 'root@pam';
954
ff1a2432
DM
955 my $storecfg = PVE::Storage::config();
956
5fdbe4f0
DM
957 my $realcmd = sub {
958 my $upid = shift;
959
960 syslog('info', "stop VM $vmid: $upid\n");
961
254575e9
DM
962 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
963 $param->{timeout}, 0, 1, $keepActive);
c6bb9502 964
5fdbe4f0
DM
965 return;
966 };
967
968 return $rpcenv->fork_worker('qmstop', $vmid, $user, $realcmd);
969 }});
970
971__PACKAGE__->register_method({
972 name => 'vm_reset',
973 path => '{vmid}/status/reset',
974 method => 'POST',
975 protected => 1,
976 proxyto => 'node',
977 description => "Reset virtual machine.",
978 parameters => {
979 additionalProperties => 0,
980 properties => {
981 node => get_standard_option('pve-node'),
982 vmid => get_standard_option('pve-vmid'),
983 skiplock => get_standard_option('skiplock'),
984 },
985 },
986 returns => {
987 type => 'string',
988 },
989 code => sub {
990 my ($param) = @_;
991
992 my $rpcenv = PVE::RPCEnvironment::get();
993
994 my $user = $rpcenv->get_user();
995
996 my $node = extract_param($param, 'node');
997
998 my $vmid = extract_param($param, 'vmid');
999
1000 my $skiplock = extract_param($param, 'skiplock');
1001 raise_param_exc({ skiplock => "Only root may use this option." })
1002 if $skiplock && $user ne 'root@pam';
1003
ff1a2432
DM
1004 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1005
5fdbe4f0
DM
1006 my $realcmd = sub {
1007 my $upid = shift;
1008
1e3baf05 1009 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1010
1011 return;
1012 };
1013
1014 return $rpcenv->fork_worker('qmreset', $vmid, $user, $realcmd);
1015 }});
1016
1017__PACKAGE__->register_method({
1018 name => 'vm_shutdown',
1019 path => '{vmid}/status/shutdown',
1020 method => 'POST',
1021 protected => 1,
1022 proxyto => 'node',
1023 description => "Shutdown virtual machine.",
1024 parameters => {
1025 additionalProperties => 0,
1026 properties => {
1027 node => get_standard_option('pve-node'),
1028 vmid => get_standard_option('pve-vmid'),
1029 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1030 timeout => {
1031 description => "Wait maximal timeout seconds.",
1032 type => 'integer',
1033 minimum => 0,
1034 optional => 1,
9269013a
DM
1035 },
1036 forceStop => {
1037 description => "Make sure the VM stops.",
1038 type => 'boolean',
1039 optional => 1,
1040 default => 0,
254575e9
DM
1041 },
1042 keepActive => {
1043 description => "Do not decativate storage volumes.",
1044 type => 'boolean',
1045 optional => 1,
1046 default => 0,
c6bb9502 1047 }
5fdbe4f0
DM
1048 },
1049 },
1050 returns => {
1051 type => 'string',
1052 },
1053 code => sub {
1054 my ($param) = @_;
1055
1056 my $rpcenv = PVE::RPCEnvironment::get();
1057
1058 my $user = $rpcenv->get_user();
1059
1060 my $node = extract_param($param, 'node');
1061
1062 my $vmid = extract_param($param, 'vmid');
1063
1064 my $skiplock = extract_param($param, 'skiplock');
1065 raise_param_exc({ skiplock => "Only root may use this option." })
1066 if $skiplock && $user ne 'root@pam';
1067
254575e9
DM
1068 my $keepActive = extract_param($param, 'keepActive');
1069 raise_param_exc({ keepActive => "Only root may use this option." })
1070 if $keepActive && $user ne 'root@pam';
1071
02d07cf5
DM
1072 my $storecfg = PVE::Storage::config();
1073
5fdbe4f0
DM
1074 my $realcmd = sub {
1075 my $upid = shift;
1076
1077 syslog('info', "shutdown VM $vmid: $upid\n");
1078
254575e9
DM
1079 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
1080 1, $param->{forceStop}, $keepActive);
c6bb9502 1081
5fdbe4f0
DM
1082 return;
1083 };
1084
1085 return $rpcenv->fork_worker('qmshutdown', $vmid, $user, $realcmd);
1086 }});
1087
1088__PACKAGE__->register_method({
1089 name => 'vm_suspend',
1090 path => '{vmid}/status/suspend',
1091 method => 'POST',
1092 protected => 1,
1093 proxyto => 'node',
1094 description => "Suspend virtual machine.",
1095 parameters => {
1096 additionalProperties => 0,
1097 properties => {
1098 node => get_standard_option('pve-node'),
1099 vmid => get_standard_option('pve-vmid'),
1100 skiplock => get_standard_option('skiplock'),
1101 },
1102 },
1103 returns => {
1104 type => 'string',
1105 },
1106 code => sub {
1107 my ($param) = @_;
1108
1109 my $rpcenv = PVE::RPCEnvironment::get();
1110
1111 my $user = $rpcenv->get_user();
1112
1113 my $node = extract_param($param, 'node');
1114
1115 my $vmid = extract_param($param, 'vmid');
1116
1117 my $skiplock = extract_param($param, 'skiplock');
1118 raise_param_exc({ skiplock => "Only root may use this option." })
1119 if $skiplock && $user ne 'root@pam';
1120
ff1a2432
DM
1121 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1122
5fdbe4f0
DM
1123 my $realcmd = sub {
1124 my $upid = shift;
1125
1126 syslog('info', "suspend VM $vmid: $upid\n");
1127
1e3baf05 1128 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1129
1130 return;
1131 };
1132
1133 return $rpcenv->fork_worker('qmsuspend', $vmid, $user, $realcmd);
1134 }});
1135
1136__PACKAGE__->register_method({
1137 name => 'vm_resume',
1138 path => '{vmid}/status/resume',
1139 method => 'POST',
1140 protected => 1,
1141 proxyto => 'node',
1142 description => "Resume virtual machine.",
1143 parameters => {
1144 additionalProperties => 0,
1145 properties => {
1146 node => get_standard_option('pve-node'),
1147 vmid => get_standard_option('pve-vmid'),
1148 skiplock => get_standard_option('skiplock'),
1149 },
1150 },
1151 returns => {
1152 type => 'string',
1153 },
1154 code => sub {
1155 my ($param) = @_;
1156
1157 my $rpcenv = PVE::RPCEnvironment::get();
1158
1159 my $user = $rpcenv->get_user();
1160
1161 my $node = extract_param($param, 'node');
1162
1163 my $vmid = extract_param($param, 'vmid');
1164
1165 my $skiplock = extract_param($param, 'skiplock');
1166 raise_param_exc({ skiplock => "Only root may use this option." })
1167 if $skiplock && $user ne 'root@pam';
1168
b7eeab21 1169 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
ff1a2432 1170
5fdbe4f0
DM
1171 my $realcmd = sub {
1172 my $upid = shift;
1173
1174 syslog('info', "resume VM $vmid: $upid\n");
1175
1e3baf05 1176 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1177
5fdbe4f0
DM
1178 return;
1179 };
1180
1181 return $rpcenv->fork_worker('qmresume', $vmid, $user, $realcmd);
1182 }});
1183
1184__PACKAGE__->register_method({
1185 name => 'vm_sendkey',
1186 path => '{vmid}/sendkey',
1187 method => 'PUT',
1188 protected => 1,
1189 proxyto => 'node',
1190 description => "Send key event to virtual machine.",
1191 parameters => {
1192 additionalProperties => 0,
1193 properties => {
1194 node => get_standard_option('pve-node'),
1195 vmid => get_standard_option('pve-vmid'),
1196 skiplock => get_standard_option('skiplock'),
1197 key => {
1198 description => "The key (qemu monitor encoding).",
1199 type => 'string'
1200 }
1201 },
1202 },
1203 returns => { type => 'null'},
1204 code => sub {
1205 my ($param) = @_;
1206
1207 my $rpcenv = PVE::RPCEnvironment::get();
1208
1209 my $user = $rpcenv->get_user();
1210
1211 my $node = extract_param($param, 'node');
1212
1213 my $vmid = extract_param($param, 'vmid');
1214
1215 my $skiplock = extract_param($param, 'skiplock');
1216 raise_param_exc({ skiplock => "Only root may use this option." })
1217 if $skiplock && $user ne 'root@pam';
1218
1219 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1220
1221 return;
1e3baf05
DM
1222 }});
1223
3ea94c60
DM
1224__PACKAGE__->register_method({
1225 name => 'migrate_vm',
1226 path => '{vmid}/migrate',
1227 method => 'POST',
1228 protected => 1,
1229 proxyto => 'node',
1230 description => "Migrate virtual machine. Creates a new migration task.",
1231 parameters => {
1232 additionalProperties => 0,
1233 properties => {
1234 node => get_standard_option('pve-node'),
1235 vmid => get_standard_option('pve-vmid'),
1236 target => get_standard_option('pve-node', { description => "Target node." }),
1237 online => {
1238 type => 'boolean',
1239 description => "Use online/live migration.",
1240 optional => 1,
1241 },
1242 force => {
1243 type => 'boolean',
1244 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1245 optional => 1,
1246 },
1247 },
1248 },
1249 returns => {
1250 type => 'string',
1251 description => "the task ID.",
1252 },
1253 code => sub {
1254 my ($param) = @_;
1255
1256 my $rpcenv = PVE::RPCEnvironment::get();
1257
1258 my $user = $rpcenv->get_user();
1259
1260 my $target = extract_param($param, 'target');
1261
1262 my $localnode = PVE::INotify::nodename();
1263 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1264
1265 PVE::Cluster::check_cfs_quorum();
1266
1267 PVE::Cluster::check_node_exists($target);
1268
1269 my $targetip = PVE::Cluster::remote_node_ip($target);
1270
1271 my $vmid = extract_param($param, 'vmid');
1272
a591eeba
DM
1273 raise_param_exc({ force => "Only root may use this option." })
1274 if $param->{force} && $user ne 'root@pam';
3ea94c60
DM
1275
1276 # test if VM exists
a5ed42d3 1277 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
1278
1279 # try to detect errors early
a5ed42d3
DM
1280
1281 PVE::QemuServer::check_lock($conf);
1282
3ea94c60
DM
1283 if (PVE::QemuServer::check_running($vmid)) {
1284 die "cant migrate running VM without --online\n"
1285 if !$param->{online};
1286 }
1287
1288 my $realcmd = sub {
1289 my $upid = shift;
1290
16e903f2 1291 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3ea94c60
DM
1292 };
1293
1294 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $user, $realcmd);
1295
1296 return $upid;
1297 }});
1e3baf05 1298
91c94f0a
DM
1299__PACKAGE__->register_method({
1300 name => 'monitor',
1301 path => '{vmid}/monitor',
1302 method => 'POST',
1303 protected => 1,
1304 proxyto => 'node',
1305 description => "Execute Qemu monitor commands.",
1306 parameters => {
1307 additionalProperties => 0,
1308 properties => {
1309 node => get_standard_option('pve-node'),
1310 vmid => get_standard_option('pve-vmid'),
1311 command => {
1312 type => 'string',
1313 description => "The monitor command.",
1314 }
1315 },
1316 },
1317 returns => { type => 'string'},
1318 code => sub {
1319 my ($param) = @_;
1320
1321 my $vmid = $param->{vmid};
1322
1323 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
1324
1325 my $res = '';
1326 eval {
1327 $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
1328 };
1329 $res = "ERROR: $@" if $@;
1330
1331 return $res;
1332 }});
1333
1e3baf05 13341;