]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
api: use guesthelper method for vm_pending path
[qemu-server.git] / PVE / API2 / Qemu.pm
CommitLineData
1e3baf05
DM
1package PVE::API2::Qemu;
2
3use strict;
4use warnings;
5b9d692a 5use Cwd 'abs_path';
451b2b81 6use Net::SSLeay;
47314bf5 7use UUID;
655d7462
WB
8use POSIX;
9use IO::Socket::IP;
0c9a7596 10use URI::Escape;
1e3baf05 11
502d18a2 12use PVE::Cluster qw (cfs_read_file cfs_write_file);;
1e3baf05
DM
13use PVE::SafeSyslog;
14use PVE::Tools qw(extract_param);
f9bfceef 15use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
1e3baf05
DM
16use PVE::Storage;
17use PVE::JSONSchema qw(get_standard_option);
18use PVE::RESTHandler;
628bb7f2 19use PVE::ReplicationConfig;
95f42d61 20use PVE::GuestHelpers;
ffda963f 21use PVE::QemuConfig;
1e3baf05 22use PVE::QemuServer;
3ea94c60 23use PVE::QemuMigrate;
1e3baf05
DM
24use PVE::RPCEnvironment;
25use PVE::AccessControl;
26use PVE::INotify;
de8f60b2 27use PVE::Network;
e9abcde6 28use PVE::Firewall;
228a998b 29use PVE::API2::Firewall::VM;
b8158701 30use PVE::API2::Qemu::Agent;
9f11fc5f
WB
31
32BEGIN {
33 if (!$ENV{PVE_GENERATING_DOCS}) {
34 require PVE::HA::Env::PVE2;
35 import PVE::HA::Env::PVE2;
36 require PVE::HA::Config;
37 import PVE::HA::Config;
38 }
39}
1e3baf05
DM
40
41use Data::Dumper; # fixme: remove
42
43use base qw(PVE::RESTHandler);
44
45my $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.";
46
47my $resolve_cdrom_alias = sub {
48 my $param = shift;
49
50 if (my $value = $param->{cdrom}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2} = $value;
53 delete $param->{cdrom};
54 }
55};
56
bf1312d8 57my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
ae57f6b3 58my $check_storage_access = sub {
fcbb753e 59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
a0d1b1a2 60
ae57f6b3
DM
61 PVE::QemuServer::foreach_drive($settings, sub {
62 my ($ds, $drive) = @_;
a0d1b1a2 63
ae57f6b3 64 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
a0d1b1a2 65
ae57f6b3 66 my $volid = $drive->{file};
a0d1b1a2 67
0c9a7596
AD
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
69 # nothing to check
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
09d0ee64 71 # nothing to check
f5782fd0
DM
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
bf1312d8 74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
a0d1b1a2
DM
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
fcbb753e 77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
c46366fd
DC
78 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
79 raise_param_exc({ storage => "storage '$storeid' does not support vm images"})
80 if !$scfg->{content}->{images};
a0d1b1a2 81 } else {
9bb3acf1 82 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
a0d1b1a2
DM
83 }
84 });
253624c7
FG
85
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage});
ae57f6b3 88};
a0d1b1a2 89
9418baad 90my $check_storage_access_clone = sub {
81f043eb 91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
6116f729 92
55173c6b
DM
93 my $sharedvm = 1;
94
6116f729
DM
95 PVE::QemuServer::foreach_drive($conf, sub {
96 my ($ds, $drive) = @_;
97
98 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
99
100 my $volid = $drive->{file};
101
102 return if !$volid || $volid eq 'none';
103
104 if ($isCDROM) {
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
107 } else {
75466c4f 108 # we simply allow access
55173c6b
DM
109 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
110 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared};
112
6116f729
DM
113 }
114 } else {
55173c6b
DM
115 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
116 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared};
118
81f043eb 119 $sid = $storage if $storage;
6116f729
DM
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
121 }
122 });
55173c6b 123
253624c7
FG
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage});
126
55173c6b 127 return $sharedvm;
6116f729
DM
128};
129
ae57f6b3
DM
130# Note: $pool is only needed when creating a VM, because pool permissions
131# are automatically inherited if VM already exists inside a pool.
132my $create_disks = sub {
96ed3574 133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
a0d1b1a2
DM
134
135 my $vollist = [];
a0d1b1a2 136
ae57f6b3 137 my $res = {};
64932aeb
DM
138
139 my $code = sub {
ae57f6b3
DM
140 my ($ds, $disk) = @_;
141
142 my $volid = $disk->{file};
143
f5782fd0 144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
628e9a2b
AD
145 delete $disk->{size};
146 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
0c9a7596
AD
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
64d1a6ae 152
0c9a7596
AD
153 my $fmt = undef;
154 if ($scfg->{path}) {
c152600b 155 $fmt = $disk->{format} // "qcow2";
64d1a6ae
WL
156 $name .= ".$fmt";
157 } else {
c152600b 158 $fmt = $disk->{format} // "raw";
0c9a7596 159 }
64d1a6ae 160
4fdc1d3d 161 # Initial disk created with 4 MB and aligned to 4MB on regeneration
7d761a01
ML
162 my $ci_size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE;
163 my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
0c9a7596
AD
164 $disk->{file} = $volid;
165 $disk->{media} = 'cdrom';
166 push @$vollist, $volid;
167 delete $disk->{format}; # no longer needed
168 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
17677004 169 } elsif ($volid =~ $NEW_DISK_RE) {
ae57f6b3
DM
170 my ($storeid, $size) = ($2 || $default_storage, $3);
171 die "no storage ID specified (and no default storage)\n" if !$storeid;
172 my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
173 my $fmt = $disk->{format} || $defformat;
1a35631a 174
a1d8c038
TL
175 $size = PVE::Tools::convert_size($size, 'gb' => 'kb'); # vdisk_alloc uses kb
176
1a35631a
DC
177 my $volid;
178 if ($ds eq 'efidisk0') {
96ed3574 179 ($volid, $size) = PVE::QemuServer::create_efidisk($storecfg, $storeid, $vmid, $fmt, $arch);
1a35631a 180 } else {
a1d8c038 181 $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $size);
1a35631a 182 }
a0d1b1a2 183 push @$vollist, $volid;
a1d8c038
TL
184 $disk->{file} = $volid;
185 $disk->{size} = PVE::Tools::convert_size($size, 'kb' => 'b');
ae57f6b3
DM
186 delete $disk->{format}; # no longer needed
187 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
188 } else {
eabe0da0 189
9bb3acf1 190 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
75466c4f 191
7043d946 192 my $volid_is_new = 1;
35cb731c 193
7043d946
DM
194 if ($conf->{$ds}) {
195 my $olddrive = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
196 $volid_is_new = undef if $olddrive->{file} && $olddrive->{file} eq $volid;
eabe0da0 197 }
75466c4f 198
d52b8b77 199 if ($volid_is_new) {
09a89895 200
7043d946
DM
201 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
202
09a89895 203 PVE::Storage::activate_volumes($storecfg, [ $volid ]) if $storeid;
d52b8b77
DM
204
205 my $size = PVE::Storage::volume_size_info($storecfg, $volid);
206
207 die "volume $volid does not exists\n" if !$size;
208
209 $disk->{size} = $size;
09a89895 210 }
24afaca0 211
24afaca0 212 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
a0d1b1a2 213 }
64932aeb
DM
214 };
215
216 eval { PVE::QemuServer::foreach_drive($settings, $code); };
a0d1b1a2
DM
217
218 # free allocated images on error
219 if (my $err = $@) {
220 syslog('err', "VM $vmid creating disks failed");
221 foreach my $volid (@$vollist) {
222 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
223 warn $@ if $@;
224 }
225 die $err;
226 }
227
228 # modify vm config if everything went well
ae57f6b3
DM
229 foreach my $ds (keys %$res) {
230 $conf->{$ds} = $res->{$ds};
a0d1b1a2
DM
231 }
232
233 return $vollist;
234};
235
58cb690b
DC
236my $cpuoptions = {
237 'cores' => 1,
238 'cpu' => 1,
239 'cpulimit' => 1,
240 'cpuunits' => 1,
241 'numa' => 1,
242 'smp' => 1,
243 'sockets' => 1,
84b31f48 244 'vcpus' => 1,
58cb690b
DC
245};
246
247my $memoryoptions = {
248 'memory' => 1,
249 'balloon' => 1,
250 'shares' => 1,
251};
252
253my $hwtypeoptions = {
254 'acpi' => 1,
255 'hotplug' => 1,
256 'kvm' => 1,
257 'machine' => 1,
258 'scsihw' => 1,
259 'smbios1' => 1,
260 'tablet' => 1,
261 'vga' => 1,
262 'watchdog' => 1,
b2dd61a0 263 'audio0' => 1,
58cb690b
DC
264};
265
6bcacc21 266my $generaloptions = {
58cb690b
DC
267 'agent' => 1,
268 'autostart' => 1,
269 'bios' => 1,
270 'description' => 1,
271 'keyboard' => 1,
272 'localtime' => 1,
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
275 'name' => 1,
276 'onboot' => 1,
277 'ostype' => 1,
278 'protection' => 1,
279 'reboot' => 1,
280 'startdate' => 1,
281 'startup' => 1,
282 'tdf' => 1,
283 'template' => 1,
284};
285
286my $vmpoweroptions = {
287 'freeze' => 1,
288};
289
290my $diskoptions = {
291 'boot' => 1,
292 'bootdisk' => 1,
253624c7 293 'vmstatestorage' => 1,
58cb690b
DC
294};
295
7ee990cd 296my $cloudinitoptions = {
cb702ebe 297 cicustom => 1,
7ee990cd
DM
298 cipassword => 1,
299 citype => 1,
300 ciuser => 1,
301 nameserver => 1,
302 searchdomain => 1,
303 sshkeys => 1,
304};
305
a0d1b1a2 306my $check_vm_modify_config_perm = sub {
e30f75c5 307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
a0d1b1a2 308
6e5c4da7 309 return 1 if $authuser eq 'root@pam';
a0d1b1a2 310
ae57f6b3 311 foreach my $opt (@$key_list) {
97415261
TL
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
74479ee9 314 next if PVE::QemuServer::is_valid_drivename($opt);
58cb690b 315 next if $opt eq 'cdrom';
165be267
DC
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
317
a0d1b1a2 318
6bcacc21 319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
a0d1b1a2 320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
58cb690b 321 } elsif ($memoryoptions->{$opt}) {
a0d1b1a2 322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
58cb690b 323 } elsif ($hwtypeoptions->{$opt}) {
a0d1b1a2 324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
6bcacc21 325 } elsif ($generaloptions->{$opt}) {
58cb690b
DC
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
330 }
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
7ee990cd 335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
a0d1b1a2
DM
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
337 } else {
165be267 338 # catches hostpci\d+, args, lock, etc.
58cb690b
DC
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
a0d1b1a2
DM
341 }
342 }
343
344 return 1;
345};
346
1e3baf05 347__PACKAGE__->register_method({
afdb31d5
DM
348 name => 'vmlist',
349 path => '',
1e3baf05
DM
350 method => 'GET',
351 description => "Virtual machine index (per node).",
a0d1b1a2
DM
352 permissions => {
353 description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
354 user => 'all',
355 },
1e3baf05
DM
356 proxyto => 'node',
357 protected => 1, # qemu pid files are only readable by root
358 parameters => {
359 additionalProperties => 0,
360 properties => {
361 node => get_standard_option('pve-node'),
12612b09
WB
362 full => {
363 type => 'boolean',
364 optional => 1,
365 description => "Determine the full status of active VMs.",
366 },
1e3baf05
DM
367 },
368 },
369 returns => {
370 type => 'array',
371 items => {
372 type => "object",
b1a70cab 373 properties => $PVE::QemuServer::vmstatus_return_properties,
1e3baf05
DM
374 },
375 links => [ { rel => 'child', href => "{vmid}" } ],
376 },
377 code => sub {
378 my ($param) = @_;
379
a0d1b1a2
DM
380 my $rpcenv = PVE::RPCEnvironment::get();
381 my $authuser = $rpcenv->get_user();
382
12612b09 383 my $vmstatus = PVE::QemuServer::vmstatus(undef, $param->{full});
1e3baf05 384
a0d1b1a2
DM
385 my $res = [];
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
388
389 my $data = $vmstatus->{$vmid};
a0d1b1a2
DM
390 push @$res, $data;
391 }
1e3baf05 392
a0d1b1a2 393 return $res;
1e3baf05
DM
394 }});
395
d703d4c0 396
d703d4c0 397
1e3baf05 398__PACKAGE__->register_method({
afdb31d5
DM
399 name => 'create_vm',
400 path => '',
1e3baf05 401 method => 'POST',
3e16d5fc 402 description => "Create or restore a virtual machine.",
a0d1b1a2 403 permissions => {
f9bfceef
DM
404 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user => 'all', # check inside
a0d1b1a2 408 },
1e3baf05
DM
409 protected => 1,
410 proxyto => 'node',
411 parameters => {
412 additionalProperties => 0,
413 properties => PVE::QemuServer::json_config_properties(
414 {
415 node => get_standard_option('pve-node'),
65e866e5 416 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
3e16d5fc
DM
417 archive => {
418 description => "The backup file.",
419 type => 'string',
420 optional => 1,
421 maxLength => 255,
65e866e5 422 completion => \&PVE::QemuServer::complete_backup_archives,
3e16d5fc
DM
423 },
424 storage => get_standard_option('pve-storage-id', {
425 description => "Default storage.",
426 optional => 1,
335af808 427 completion => \&PVE::QemuServer::complete_storage,
3e16d5fc
DM
428 }),
429 force => {
afdb31d5 430 optional => 1,
3e16d5fc
DM
431 type => 'boolean',
432 description => "Allow to overwrite existing VM.",
51586c3a
DM
433 requires => 'archive',
434 },
435 unique => {
afdb31d5 436 optional => 1,
51586c3a
DM
437 type => 'boolean',
438 description => "Assign a unique random ethernet address.",
439 requires => 'archive',
3e16d5fc 440 },
75466c4f 441 pool => {
a0d1b1a2
DM
442 optional => 1,
443 type => 'string', format => 'pve-poolid',
444 description => "Add the VM to the specified pool.",
445 },
7c536e11 446 bwlimit => {
0aab5a16 447 description => "Override I/O bandwidth limit (in KiB/s).",
7c536e11
WB
448 optional => 1,
449 type => 'integer',
450 minimum => '0',
41756a3b 451 default => 'restore limit from datacenter or storage config',
e33f774d
TL
452 },
453 start => {
454 optional => 1,
455 type => 'boolean',
456 default => 0,
457 description => "Start VM after it was created successfully.",
458 },
1e3baf05
DM
459 }),
460 },
afdb31d5 461 returns => {
5fdbe4f0
DM
462 type => 'string',
463 },
1e3baf05
DM
464 code => sub {
465 my ($param) = @_;
466
5fdbe4f0
DM
467 my $rpcenv = PVE::RPCEnvironment::get();
468
a0d1b1a2 469 my $authuser = $rpcenv->get_user();
5fdbe4f0 470
1e3baf05
DM
471 my $node = extract_param($param, 'node');
472
1e3baf05
DM
473 my $vmid = extract_param($param, 'vmid');
474
3e16d5fc 475 my $archive = extract_param($param, 'archive');
8ba8418c 476 my $is_restore = !!$archive;
3e16d5fc
DM
477
478 my $storage = extract_param($param, 'storage');
479
51586c3a
DM
480 my $force = extract_param($param, 'force');
481
482 my $unique = extract_param($param, 'unique');
75466c4f 483
a0d1b1a2 484 my $pool = extract_param($param, 'pool');
51586c3a 485
7c536e11
WB
486 my $bwlimit = extract_param($param, 'bwlimit');
487
e33f774d
TL
488 my $start_after_create = extract_param($param, 'start');
489
ffda963f 490 my $filename = PVE::QemuConfig->config_file($vmid);
afdb31d5
DM
491
492 my $storecfg = PVE::Storage::config();
1e3baf05 493
0c9a7596
AD
494 if (defined(my $ssh_keys = $param->{sshkeys})) {
495 $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
496 PVE::Tools::validate_ssh_public_keys($ssh_keys);
497 }
498
3e16d5fc 499 PVE::Cluster::check_cfs_quorum();
1e3baf05 500
a0d1b1a2
DM
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
75466c4f 503 }
a0d1b1a2 504
fcbb753e 505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
a0d1b1a2
DM
506 if defined($storage);
507
f9bfceef
DM
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 # OK
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 # OK
512 } elsif ($archive && $force && (-f $filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
515 } else {
516 raise_perm_exc();
517 }
518
afdb31d5 519 if (!$archive) {
3e16d5fc 520 &$resolve_cdrom_alias($param);
1e3baf05 521
fcbb753e 522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
ae57f6b3 523
e30f75c5 524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
ae57f6b3 525
3e16d5fc 526 foreach my $opt (keys %$param) {
74479ee9 527 if (PVE::QemuServer::is_valid_drivename($opt)) {
3e16d5fc
DM
528 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
529 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
afdb31d5 530
3e16d5fc
DM
531 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
532 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
533 }
1e3baf05 534 }
3e16d5fc
DM
535
536 PVE::QemuServer::add_random_macs($param);
51586c3a
DM
537 } else {
538 my $keystr = join(' ', keys %$param);
bc4dcb99
DM
539 raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
540
5b9d692a 541 if ($archive eq '-') {
afdb31d5 542 die "pipe requires cli environment\n"
d7810bc1 543 if $rpcenv->{type} ne 'cli';
5b9d692a 544 } else {
9bb3acf1 545 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive);
c9928b3d 546 $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive);
971f27c4 547 }
1e3baf05
DM
548 }
549
4fedc13b 550 my $emsg = $is_restore ? "unable to restore VM $vmid -" : "unable to create VM $vmid -";
3e16d5fc 551
4fedc13b
TL
552 eval { PVE::QemuConfig->create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
3e16d5fc 554
4fedc13b
TL
555 my $restorefn = sub {
556 my $conf = PVE::QemuConfig->load_config($vmid);
4d8d55f1 557
4fedc13b 558 PVE::QemuConfig->check_protection($conf, $emsg);
3a07a8a9 559
4fedc13b 560 die "$emsg vm is running\n" if PVE::QemuServer::check_running($vmid);
3e16d5fc
DM
561
562 my $realcmd = sub {
a0d1b1a2 563 PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
51586c3a 564 storage => $storage,
a0d1b1a2 565 pool => $pool,
7c536e11 566 unique => $unique,
5294c110
CE
567 bwlimit => $bwlimit,
568 });
569 my $restored_conf = PVE::QemuConfig->load_config($vmid);
570 # Convert restored VM to template if backup was VM template
571 if (PVE::QemuConfig->is_template($restored_conf)) {
572 warn "Convert to template.\n";
573 eval { PVE::QemuServer::template_create($vmid, $restored_conf) };
574 warn $@ if $@;
575 }
502d18a2 576
be517049 577 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
e33f774d 578
5bf96183
WB
579 if ($start_after_create) {
580 eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) };
581 warn $@ if $@;
582 }
3e16d5fc
DM
583 };
584
223e032b
WL
585 # ensure no old replication state are exists
586 PVE::ReplicationState::delete_guest_states($vmid);
587
8ba8418c 588 return PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
3e16d5fc 589 };
1e3baf05 590
1e3baf05 591 my $createfn = sub {
223e032b
WL
592 # ensure no old replication state are exists
593 PVE::ReplicationState::delete_guest_states($vmid);
594
5fdbe4f0 595 my $realcmd = sub {
1e3baf05 596
5fdbe4f0 597 my $vollist = [];
1e3baf05 598
1858638f
DM
599 my $conf = $param;
600
40c3bcf8
WB
601 my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
602
5fdbe4f0 603 eval {
ae57f6b3 604
96ed3574 605 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
1e3baf05 606
ae2fcb3b
EK
607 if (!$conf->{bootdisk}) {
608 my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
609 $conf->{bootdisk} = $firstdisk if $firstdisk;
5fdbe4f0 610 }
1e3baf05 611
47314bf5
DM
612 # auto generate uuid if user did not specify smbios1 option
613 if (!$conf->{smbios1}) {
ae2fcb3b 614 $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
47314bf5
DM
615 }
616
40c3bcf8 617 if ((!defined($conf->{vmgenid}) || $conf->{vmgenid} eq '1') && $arch ne 'aarch64') {
6ee499ff
DC
618 $conf->{vmgenid} = PVE::QemuServer::generate_uuid();
619 }
620
ffda963f 621 PVE::QemuConfig->write_config($vmid, $conf);
ae9ca91d 622
5fdbe4f0
DM
623 };
624 my $err = $@;
1e3baf05 625
5fdbe4f0
DM
626 if ($err) {
627 foreach my $volid (@$vollist) {
628 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
629 warn $@ if $@;
630 }
4fedc13b 631 die "$emsg $err";
5fdbe4f0 632 }
502d18a2 633
be517049 634 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
5fdbe4f0
DM
635 };
636
e33f774d
TL
637 PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
638
639 if ($start_after_create) {
640 print "Execute autostart\n";
5bf96183
WB
641 eval { PVE::API2::Qemu->vm_start({vmid => $vmid, node => $node}) };
642 warn $@ if $@;
e33f774d 643 }
5fdbe4f0
DM
644 };
645
5bf96183
WB
646 my ($code, $worker_name);
647 if ($is_restore) {
648 $worker_name = 'qmrestore';
649 $code = sub {
650 eval { $restorefn->() };
651 if (my $err = $@) {
652 eval { PVE::QemuConfig->remove_lock($vmid, 'create') };
653 warn $@ if $@;
654 die $err;
655 }
656 };
657 } else {
658 $worker_name = 'qmcreate';
659 $code = sub {
660 eval { $createfn->() };
661 if (my $err = $@) {
662 eval {
663 my $conffile = PVE::QemuConfig->config_file($vmid);
f1e277cd 664 unlink($conffile) or die "failed to remove config file: $!\n";
5bf96183
WB
665 };
666 warn $@ if $@;
667 die $err;
668 }
669 };
670 }
8ba8418c
TL
671
672 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1e3baf05
DM
673 }});
674
675__PACKAGE__->register_method({
676 name => 'vmdiridx',
afdb31d5 677 path => '{vmid}',
1e3baf05
DM
678 method => 'GET',
679 proxyto => 'node',
680 description => "Directory index",
a0d1b1a2
DM
681 permissions => {
682 user => 'all',
683 },
1e3baf05
DM
684 parameters => {
685 additionalProperties => 0,
686 properties => {
687 node => get_standard_option('pve-node'),
688 vmid => get_standard_option('pve-vmid'),
689 },
690 },
691 returns => {
692 type => 'array',
693 items => {
694 type => "object",
695 properties => {
696 subdir => { type => 'string' },
697 },
698 },
699 links => [ { rel => 'child', href => "{subdir}" } ],
700 },
701 code => sub {
702 my ($param) = @_;
703
704 my $res = [
705 { subdir => 'config' },
df2a2dbb 706 { subdir => 'pending' },
1e3baf05
DM
707 { subdir => 'status' },
708 { subdir => 'unlink' },
709 { subdir => 'vncproxy' },
87302002 710 { subdir => 'termproxy' },
3ea94c60 711 { subdir => 'migrate' },
2f48a4f5 712 { subdir => 'resize' },
586bfa78 713 { subdir => 'move' },
1e3baf05
DM
714 { subdir => 'rrd' },
715 { subdir => 'rrddata' },
91c94f0a 716 { subdir => 'monitor' },
d1a47427 717 { subdir => 'agent' },
7e7d7b61 718 { subdir => 'snapshot' },
288eeea8 719 { subdir => 'spiceproxy' },
7aa608d6 720 { subdir => 'sendkey' },
228a998b 721 { subdir => 'firewall' },
1e3baf05 722 ];
afdb31d5 723
1e3baf05
DM
724 return $res;
725 }});
726
228a998b 727__PACKAGE__->register_method ({
f34ebd52 728 subclass => "PVE::API2::Firewall::VM",
228a998b
DM
729 path => '{vmid}/firewall',
730});
731
b8158701
DC
732__PACKAGE__->register_method ({
733 subclass => "PVE::API2::Qemu::Agent",
734 path => '{vmid}/agent',
735});
736
1e3baf05 737__PACKAGE__->register_method({
afdb31d5
DM
738 name => 'rrd',
739 path => '{vmid}/rrd',
1e3baf05
DM
740 method => 'GET',
741 protected => 1, # fixme: can we avoid that?
742 permissions => {
378b359e 743 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
744 },
745 description => "Read VM RRD statistics (returns PNG)",
746 parameters => {
747 additionalProperties => 0,
748 properties => {
749 node => get_standard_option('pve-node'),
750 vmid => get_standard_option('pve-vmid'),
751 timeframe => {
752 description => "Specify the time frame you are interested in.",
753 type => 'string',
754 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
755 },
756 ds => {
757 description => "The list of datasources you want to display.",
758 type => 'string', format => 'pve-configid-list',
759 },
760 cf => {
761 description => "The RRD consolidation function",
762 type => 'string',
763 enum => [ 'AVERAGE', 'MAX' ],
764 optional => 1,
765 },
766 },
767 },
768 returns => {
769 type => "object",
770 properties => {
771 filename => { type => 'string' },
772 },
773 },
774 code => sub {
775 my ($param) = @_;
776
777 return PVE::Cluster::create_rrd_graph(
afdb31d5 778 "pve2-vm/$param->{vmid}", $param->{timeframe},
1e3baf05 779 $param->{ds}, $param->{cf});
afdb31d5 780
1e3baf05
DM
781 }});
782
783__PACKAGE__->register_method({
afdb31d5
DM
784 name => 'rrddata',
785 path => '{vmid}/rrddata',
1e3baf05
DM
786 method => 'GET',
787 protected => 1, # fixme: can we avoid that?
788 permissions => {
378b359e 789 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
790 },
791 description => "Read VM RRD statistics",
792 parameters => {
793 additionalProperties => 0,
794 properties => {
795 node => get_standard_option('pve-node'),
796 vmid => get_standard_option('pve-vmid'),
797 timeframe => {
798 description => "Specify the time frame you are interested in.",
799 type => 'string',
800 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
801 },
802 cf => {
803 description => "The RRD consolidation function",
804 type => 'string',
805 enum => [ 'AVERAGE', 'MAX' ],
806 optional => 1,
807 },
808 },
809 },
810 returns => {
811 type => "array",
812 items => {
813 type => "object",
814 properties => {},
815 },
816 },
817 code => sub {
818 my ($param) = @_;
819
820 return PVE::Cluster::create_rrd_data(
821 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
822 }});
823
824
825__PACKAGE__->register_method({
afdb31d5
DM
826 name => 'vm_config',
827 path => '{vmid}/config',
1e3baf05
DM
828 method => 'GET',
829 proxyto => 'node',
1e7f2726 830 description => "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
a0d1b1a2
DM
831 permissions => {
832 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
833 },
1e3baf05
DM
834 parameters => {
835 additionalProperties => 0,
836 properties => {
837 node => get_standard_option('pve-node'),
335af808 838 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2a68ec78
TL
839 current => {
840 description => "Get current values (instead of pending values).",
841 optional => 1,
6d89b548
DM
842 default => 0,
843 type => 'boolean',
2a68ec78 844 },
b14477e7
RV
845 snapshot => get_standard_option('pve-snapshot-name', {
846 description => "Fetch config values from given snapshot.",
847 optional => 1,
848 completion => sub {
849 my ($cmd, $pname, $cur, $args) = @_;
850 PVE::QemuConfig->snapshot_list($args->[0]);
851 },
852 }),
1e3baf05
DM
853 },
854 },
afdb31d5 855 returns => {
ce9b0a38 856 description => "The current VM configuration.",
1e3baf05 857 type => "object",
ce9b0a38 858 properties => PVE::QemuServer::json_config_properties({
554ac7e7
DM
859 digest => {
860 type => 'string',
861 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
862 }
ce9b0a38 863 }),
1e3baf05
DM
864 },
865 code => sub {
866 my ($param) = @_;
867
d3179e1c
OB
868 raise_param_exc({ snapshot => "cannot use 'snapshot' parameter with 'current'",
869 current => "cannot use 'snapshot' parameter with 'current'"})
870 if ($param->{snapshot} && $param->{current});
1e3baf05 871
d3179e1c
OB
872 my $conf;
873 if ($param->{snapshot}) {
874 $conf = PVE::QemuConfig->load_snapshot_config($param->{vmid}, $param->{snapshot});
875 } else {
876 $conf = PVE::QemuConfig->load_current_config($param->{vmid}, $param->{current});
2254ffcf 877 }
d3179e1c 878 $conf->{cipassword} = '**********' if $conf->{cipassword};
1e3baf05 879 return $conf;
d3179e1c 880
1e3baf05
DM
881 }});
882
1e7f2726
DM
883__PACKAGE__->register_method({
884 name => 'vm_pending',
885 path => '{vmid}/pending',
886 method => 'GET',
887 proxyto => 'node',
888 description => "Get virtual machine configuration, including pending changes.",
889 permissions => {
890 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
891 },
892 parameters => {
893 additionalProperties => 0,
894 properties => {
895 node => get_standard_option('pve-node'),
335af808 896 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e7f2726
DM
897 },
898 },
899 returns => {
900 type => "array",
901 items => {
902 type => "object",
903 properties => {
904 key => {
905 description => "Configuration option name.",
906 type => 'string',
907 },
908 value => {
909 description => "Current value.",
910 type => 'string',
911 optional => 1,
912 },
913 pending => {
914 description => "Pending value.",
915 type => 'string',
916 optional => 1,
917 },
918 delete => {
1bc483f6
WB
919 description => "Indicates a pending delete request if present and not 0. " .
920 "The value 2 indicates a force-delete request.",
921 type => 'integer',
922 minimum => 0,
923 maximum => 2,
1e7f2726
DM
924 optional => 1,
925 },
926 },
927 },
928 },
929 code => sub {
930 my ($param) = @_;
931
ffda963f 932 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e7f2726 933
98bc3aeb 934 my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
1e7f2726 935
59ef7003
OB
936 $conf->{cipassword} = '**********' if defined($conf->{cipassword});
937 $conf->{pending}->{cipassword} = '********** ' if defined($conf->{pending}->{cipassword});
1e7f2726 938
59ef7003
OB
939 return PVE::GuestHelpers::conf_table_with_pending($conf, $pending_delete_hash);
940 }});
1e7f2726 941
5555edea
DM
942# POST/PUT {vmid}/config implementation
943#
944# The original API used PUT (idempotent) an we assumed that all operations
945# are fast. But it turned out that almost any configuration change can
946# involve hot-plug actions, or disk alloc/free. Such actions can take long
947# time to complete and have side effects (not idempotent).
948#
7043d946 949# The new implementation uses POST and forks a worker process. We added
5555edea 950# a new option 'background_delay'. If specified we wait up to
7043d946 951# 'background_delay' second for the worker task to complete. It returns null
5555edea 952# if the task is finished within that time, else we return the UPID.
7043d946 953
5555edea
DM
954my $update_vm_api = sub {
955 my ($param, $sync) = @_;
a0d1b1a2 956
5555edea 957 my $rpcenv = PVE::RPCEnvironment::get();
1e3baf05 958
5555edea 959 my $authuser = $rpcenv->get_user();
1e3baf05 960
5555edea 961 my $node = extract_param($param, 'node');
1e3baf05 962
5555edea 963 my $vmid = extract_param($param, 'vmid');
1e3baf05 964
5555edea 965 my $digest = extract_param($param, 'digest');
1e3baf05 966
5555edea 967 my $background_delay = extract_param($param, 'background_delay');
1e3baf05 968
cefb41fa
WB
969 if (defined(my $cipassword = $param->{cipassword})) {
970 # Same logic as in cloud-init (but with the regex fixed...)
971 $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
972 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
973 }
0c9a7596 974
5555edea 975 my @paramarr = (); # used for log message
edd48c32 976 foreach my $key (sort keys %$param) {
cefb41fa
WB
977 my $value = $key eq 'cipassword' ? '<hidden>' : $param->{$key};
978 push @paramarr, "-$key", $value;
5555edea 979 }
0532bc63 980
5555edea
DM
981 my $skiplock = extract_param($param, 'skiplock');
982 raise_param_exc({ skiplock => "Only root may use this option." })
983 if $skiplock && $authuser ne 'root@pam';
1e3baf05 984
5555edea 985 my $delete_str = extract_param($param, 'delete');
0532bc63 986
d3df8cf3
DM
987 my $revert_str = extract_param($param, 'revert');
988
5555edea 989 my $force = extract_param($param, 'force');
1e68cb19 990
0c9a7596 991 if (defined(my $ssh_keys = $param->{sshkeys})) {
231f824b
WB
992 $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
993 PVE::Tools::validate_ssh_public_keys($ssh_keys);
0c9a7596
AD
994 }
995
d3df8cf3 996 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
7bfdeb5f 997
5555edea 998 my $storecfg = PVE::Storage::config();
1e68cb19 999
5555edea 1000 my $defaults = PVE::QemuServer::load_defaults();
1e68cb19 1001
5555edea 1002 &$resolve_cdrom_alias($param);
0532bc63 1003
5555edea 1004 # now try to verify all parameters
ae57f6b3 1005
d3df8cf3
DM
1006 my $revert = {};
1007 foreach my $opt (PVE::Tools::split_list($revert_str)) {
1008 if (!PVE::QemuServer::option_exists($opt)) {
1009 raise_param_exc({ revert => "unknown option '$opt'" });
1010 }
1011
1012 raise_param_exc({ delete => "you can't use '-$opt' and " .
1013 "-revert $opt' at the same time" })
1014 if defined($param->{$opt});
1015
1016 $revert->{$opt} = 1;
1017 }
1018
5555edea
DM
1019 my @delete = ();
1020 foreach my $opt (PVE::Tools::split_list($delete_str)) {
1021 $opt = 'ide2' if $opt eq 'cdrom';
d3df8cf3 1022
5555edea
DM
1023 raise_param_exc({ delete => "you can't use '-$opt' and " .
1024 "-delete $opt' at the same time" })
1025 if defined($param->{$opt});
7043d946 1026
d3df8cf3
DM
1027 raise_param_exc({ revert => "you can't use '-delete $opt' and " .
1028 "-revert $opt' at the same time" })
1029 if $revert->{$opt};
1030
5555edea
DM
1031 if (!PVE::QemuServer::option_exists($opt)) {
1032 raise_param_exc({ delete => "unknown option '$opt'" });
0532bc63 1033 }
1e3baf05 1034
5555edea
DM
1035 push @delete, $opt;
1036 }
1037
17677004
WB
1038 my $repl_conf = PVE::ReplicationConfig->new();
1039 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1040 my $check_replication = sub {
1041 my ($drive) = @_;
1042 return if !$is_replicated;
1043 my $volid = $drive->{file};
1044 return if !$volid || !($drive->{replicate}//1);
1045 return if PVE::QemuServer::drive_is_cdrom($drive);
1046 my ($storeid, $format);
1047 if ($volid =~ $NEW_DISK_RE) {
1048 $storeid = $2;
1049 $format = $drive->{format} || PVE::Storage::storage_default_format($storecfg, $storeid);
1050 } else {
1051 ($storeid, undef) = PVE::Storage::parse_volume_id($volid, 1);
1052 $format = (PVE::Storage::parse_volname($storecfg, $volid))[6];
1053 }
1054 return if PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
9b1396ed
WB
1055 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
1056 return if $scfg->{shared};
17677004
WB
1057 die "cannot add non-replicatable volume to a replicated VM\n";
1058 };
1059
5555edea 1060 foreach my $opt (keys %$param) {
74479ee9 1061 if (PVE::QemuServer::is_valid_drivename($opt)) {
5555edea
DM
1062 # cleanup drive path
1063 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
f9091201 1064 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
5555edea 1065 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
17677004 1066 $check_replication->($drive);
5555edea
DM
1067 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
1068 } elsif ($opt =~ m/^net(\d+)$/) {
1069 # add macaddr
1070 my $net = PVE::QemuServer::parse_net($param->{$opt});
1071 $param->{$opt} = PVE::QemuServer::print_net($net);
6ee499ff
DC
1072 } elsif ($opt eq 'vmgenid') {
1073 if ($param->{$opt} eq '1') {
1074 $param->{$opt} = PVE::QemuServer::generate_uuid();
1075 }
9e784b11
DC
1076 } elsif ($opt eq 'hookscript') {
1077 eval { PVE::GuestHelpers::check_hookscript($param->{$opt}, $storecfg); };
1078 raise_param_exc({ $opt => $@ }) if $@;
1e68cb19 1079 }
5555edea 1080 }
1e3baf05 1081
5555edea 1082 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
ae57f6b3 1083
e30f75c5 1084 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
ae57f6b3 1085
5555edea 1086 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1e3baf05 1087
5555edea 1088 my $updatefn = sub {
1e3baf05 1089
ffda963f 1090 my $conf = PVE::QemuConfig->load_config($vmid);
1e3baf05 1091
5555edea
DM
1092 die "checksum missmatch (file change by other user?)\n"
1093 if $digest && $digest ne $conf->{digest};
1094
ffda963f 1095 PVE::QemuConfig->check_lock($conf) if !$skiplock;
7043d946 1096
d3df8cf3
DM
1097 foreach my $opt (keys %$revert) {
1098 if (defined($conf->{$opt})) {
1099 $param->{$opt} = $conf->{$opt};
1100 } elsif (defined($conf->{pending}->{$opt})) {
1101 push @delete, $opt;
1102 }
1103 }
1104
5555edea 1105 if ($param->{memory} || defined($param->{balloon})) {
6ca8b698
DM
1106 my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
1107 my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
7043d946 1108
5555edea
DM
1109 die "balloon value too large (must be smaller than assigned memory)\n"
1110 if $balloon && $balloon > $maxmem;
1111 }
1e3baf05 1112
5555edea 1113 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1e3baf05 1114
5555edea 1115 my $worker = sub {
7bfdeb5f 1116
5555edea 1117 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
c2a64aa7 1118
202d1f45
DM
1119 # write updates to pending section
1120
3a11fadb
DM
1121 my $modified = {}; # record what $option we modify
1122
202d1f45 1123 foreach my $opt (@delete) {
3a11fadb 1124 $modified->{$opt} = 1;
ffda963f 1125 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
f70a6ea9
TL
1126
1127 # value of what we want to delete, independent if pending or not
1128 my $val = $conf->{$opt} // $conf->{pending}->{$opt};
1129 if (!defined($val)) {
d2c6bf93
FG
1130 warn "cannot delete '$opt' - not set in current configuration!\n";
1131 $modified->{$opt} = 0;
1132 next;
1133 }
f70a6ea9 1134 my $is_pending_val = defined($conf->{pending}->{$opt});
d2c6bf93 1135
202d1f45 1136 if ($opt =~ m/^unused/) {
f70a6ea9 1137 my $drive = PVE::QemuServer::parse_drive($opt, $val);
ffda963f 1138 PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'");
4d8d55f1 1139 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3dc38fbb
WB
1140 if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1141 delete $conf->{$opt};
ffda963f 1142 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1143 }
74479ee9 1144 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
ffda963f 1145 PVE::QemuConfig->check_protection($conf, "can't remove drive '$opt'");
202d1f45 1146 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
f70a6ea9
TL
1147 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $val))
1148 if $is_pending_val;
98bc3aeb 1149 PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
ffda963f 1150 PVE::QemuConfig->write_config($vmid, $conf);
e30f75c5 1151 } elsif ($opt =~ m/^serial\d+$/) {
f70a6ea9 1152 if ($val eq 'socket') {
e5453043
DC
1153 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1154 } elsif ($authuser ne 'root@pam') {
1155 die "only root can delete '$opt' config for real devices\n";
1156 }
98bc3aeb 1157 PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
e5453043 1158 PVE::QemuConfig->write_config($vmid, $conf);
165be267 1159 } elsif ($opt =~ m/^usb\d+$/) {
f70a6ea9 1160 if ($val =~ m/spice/) {
165be267
DC
1161 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1162 } elsif ($authuser ne 'root@pam') {
1163 die "only root can delete '$opt' config for real devices\n";
1164 }
98bc3aeb 1165 PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
165be267 1166 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1167 } else {
98bc3aeb 1168 PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
ffda963f 1169 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1170 }
5d39a182 1171 }
1e3baf05 1172
202d1f45 1173 foreach my $opt (keys %$param) { # add/change
3a11fadb 1174 $modified->{$opt} = 1;
ffda963f 1175 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
202d1f45
DM
1176 next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
1177
96ed3574
WB
1178 my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
1179
74479ee9 1180 if (PVE::QemuServer::is_valid_drivename($opt)) {
202d1f45 1181 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
9ed7a77c 1182 # FIXME: cloudinit: CDROM or Disk?
202d1f45
DM
1183 if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
1184 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1185 } else {
1186 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1187 }
055d554d 1188 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45
DM
1189 if defined($conf->{pending}->{$opt});
1190
96ed3574 1191 &$create_disks($rpcenv, $authuser, $conf->{pending}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
e30f75c5
DC
1192 } elsif ($opt =~ m/^serial\d+/) {
1193 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1194 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1195 } elsif ($authuser ne 'root@pam') {
1196 die "only root can modify '$opt' config for real devices\n";
1197 }
1198 $conf->{pending}->{$opt} = $param->{$opt};
165be267
DC
1199 } elsif ($opt =~ m/^usb\d+/) {
1200 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1201 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1202 } elsif ($authuser ne 'root@pam') {
1203 die "only root can modify '$opt' config for real devices\n";
1204 }
1205 $conf->{pending}->{$opt} = $param->{$opt};
202d1f45
DM
1206 } else {
1207 $conf->{pending}->{$opt} = $param->{$opt};
1208 }
98bc3aeb 1209 PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
ffda963f 1210 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45
DM
1211 }
1212
1213 # remove pending changes when nothing changed
ffda963f 1214 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
98bc3aeb 1215 my $changes = PVE::QemuConfig->cleanup_pending($conf);
ffda963f 1216 PVE::QemuConfig->write_config($vmid, $conf) if $changes;
202d1f45
DM
1217
1218 return if !scalar(keys %{$conf->{pending}});
1219
7bfdeb5f 1220 my $running = PVE::QemuServer::check_running($vmid);
39001640
DM
1221
1222 # apply pending changes
1223
ffda963f 1224 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
39001640 1225
3a11fadb
DM
1226 if ($running) {
1227 my $errors = {};
1228 PVE::QemuServer::vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors);
1229 raise_param_exc($errors) if scalar(keys %$errors);
1230 } else {
1231 PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running);
1232 }
1e68cb19 1233
915d3481 1234 return;
5d39a182
DM
1235 };
1236
5555edea
DM
1237 if ($sync) {
1238 &$worker();
1239 return undef;
1240 } else {
1241 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
fcdb0117 1242
5555edea
DM
1243 if ($background_delay) {
1244
1245 # Note: It would be better to do that in the Event based HTTPServer
7043d946 1246 # to avoid blocking call to sleep.
5555edea
DM
1247
1248 my $end_time = time() + $background_delay;
1249
1250 my $task = PVE::Tools::upid_decode($upid);
1251
1252 my $running = 1;
1253 while (time() < $end_time) {
1254 $running = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
1255 last if !$running;
1256 sleep(1); # this gets interrupted when child process ends
1257 }
1258
1259 if (!$running) {
1260 my $status = PVE::Tools::upid_read_status($upid);
1261 return undef if $status eq 'OK';
1262 die $status;
1263 }
7043d946 1264 }
5555edea
DM
1265
1266 return $upid;
1267 }
1268 };
1269
ffda963f 1270 return PVE::QemuConfig->lock_config($vmid, $updatefn);
5555edea
DM
1271};
1272
1273my $vm_config_perm_list = [
1274 'VM.Config.Disk',
1275 'VM.Config.CDROM',
1276 'VM.Config.CPU',
1277 'VM.Config.Memory',
1278 'VM.Config.Network',
1279 'VM.Config.HWType',
1280 'VM.Config.Options',
1281 ];
1282
1283__PACKAGE__->register_method({
1284 name => 'update_vm_async',
1285 path => '{vmid}/config',
1286 method => 'POST',
1287 protected => 1,
1288 proxyto => 'node',
1289 description => "Set virtual machine options (asynchrounous API).",
1290 permissions => {
1291 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1292 },
1293 parameters => {
1294 additionalProperties => 0,
1295 properties => PVE::QemuServer::json_config_properties(
1296 {
1297 node => get_standard_option('pve-node'),
1298 vmid => get_standard_option('pve-vmid'),
1299 skiplock => get_standard_option('skiplock'),
1300 delete => {
1301 type => 'string', format => 'pve-configid-list',
1302 description => "A list of settings you want to delete.",
1303 optional => 1,
1304 },
4c8365fa
DM
1305 revert => {
1306 type => 'string', format => 'pve-configid-list',
1307 description => "Revert a pending change.",
1308 optional => 1,
1309 },
5555edea
DM
1310 force => {
1311 type => 'boolean',
1312 description => $opt_force_description,
1313 optional => 1,
1314 requires => 'delete',
1315 },
1316 digest => {
1317 type => 'string',
1318 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1319 maxLength => 40,
1320 optional => 1,
1321 },
1322 background_delay => {
1323 type => 'integer',
1324 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1325 minimum => 1,
1326 maximum => 30,
1327 optional => 1,
1328 },
1329 }),
1330 },
1331 returns => {
1332 type => 'string',
1333 optional => 1,
1334 },
1335 code => $update_vm_api,
1336});
1337
1338__PACKAGE__->register_method({
1339 name => 'update_vm',
1340 path => '{vmid}/config',
1341 method => 'PUT',
1342 protected => 1,
1343 proxyto => 'node',
1344 description => "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1345 permissions => {
1346 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1347 },
1348 parameters => {
1349 additionalProperties => 0,
1350 properties => PVE::QemuServer::json_config_properties(
1351 {
1352 node => get_standard_option('pve-node'),
335af808 1353 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
5555edea
DM
1354 skiplock => get_standard_option('skiplock'),
1355 delete => {
1356 type => 'string', format => 'pve-configid-list',
1357 description => "A list of settings you want to delete.",
1358 optional => 1,
1359 },
4c8365fa
DM
1360 revert => {
1361 type => 'string', format => 'pve-configid-list',
1362 description => "Revert a pending change.",
1363 optional => 1,
1364 },
5555edea
DM
1365 force => {
1366 type => 'boolean',
1367 description => $opt_force_description,
1368 optional => 1,
1369 requires => 'delete',
1370 },
1371 digest => {
1372 type => 'string',
1373 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1374 maxLength => 40,
1375 optional => 1,
1376 },
1377 }),
1378 },
1379 returns => { type => 'null' },
1380 code => sub {
1381 my ($param) = @_;
1382 &$update_vm_api($param, 1);
1e3baf05 1383 return undef;
5555edea
DM
1384 }
1385});
1e3baf05 1386
1e3baf05 1387__PACKAGE__->register_method({
afdb31d5
DM
1388 name => 'destroy_vm',
1389 path => '{vmid}',
1e3baf05
DM
1390 method => 'DELETE',
1391 protected => 1,
1392 proxyto => 'node',
1393 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
1394 permissions => {
1395 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1396 },
1e3baf05
DM
1397 parameters => {
1398 additionalProperties => 0,
1399 properties => {
1400 node => get_standard_option('pve-node'),
335af808 1401 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60 1402 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
1403 },
1404 },
afdb31d5 1405 returns => {
5fdbe4f0
DM
1406 type => 'string',
1407 },
1e3baf05
DM
1408 code => sub {
1409 my ($param) = @_;
1410
1411 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 1412 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1413 my $vmid = $param->{vmid};
1414
1415 my $skiplock = $param->{skiplock};
afdb31d5 1416 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1417 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1418
5fdbe4f0 1419 # test if VM exists
ffda963f 1420 my $conf = PVE::QemuConfig->load_config($vmid);
afdb31d5 1421 my $storecfg = PVE::Storage::config();
ffda963f 1422 PVE::QemuConfig->check_protection($conf, "can't remove VM $vmid");
952e3ac3
DM
1423 die "unable to remove VM $vmid - used in HA resources\n"
1424 if PVE::HA::Config::vm_is_ha_managed($vmid);
e9f2f8e5 1425
628bb7f2
DM
1426 # do not allow destroy if there are replication jobs
1427 my $repl_conf = PVE::ReplicationConfig->new();
1428 $repl_conf->check_for_existing_jobs($vmid);
1429
db593da2
DM
1430 # early tests (repeat after locking)
1431 die "VM $vmid is running - destroy failed\n"
1432 if PVE::QemuServer::check_running($vmid);
1433
5fdbe4f0 1434 my $realcmd = sub {
ff1a2432
DM
1435 my $upid = shift;
1436
1437 syslog('info', "destroy VM $vmid: $upid\n");
3e8e214d
DJ
1438 PVE::QemuConfig->lock_config($vmid, sub {
1439 die "VM $vmid is running - destroy failed\n"
1440 if (PVE::QemuServer::check_running($vmid));
1441 PVE::QemuServer::destroy_vm($storecfg, $vmid, 1, $skiplock);
1442 PVE::AccessControl::remove_vm_access($vmid);
1443 PVE::Firewall::remove_vmfw_conf($vmid);
5172770d
TL
1444
1445 # only now remove the zombie config, else we can have reuse race
1446 PVE::QemuConfig->destroy_config($vmid);
3e8e214d 1447 });
5fdbe4f0 1448 };
1e3baf05 1449
a0d1b1a2 1450 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
1451 }});
1452
1453__PACKAGE__->register_method({
afdb31d5
DM
1454 name => 'unlink',
1455 path => '{vmid}/unlink',
1e3baf05
DM
1456 method => 'PUT',
1457 protected => 1,
1458 proxyto => 'node',
1459 description => "Unlink/delete disk images.",
a0d1b1a2
DM
1460 permissions => {
1461 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1462 },
1e3baf05
DM
1463 parameters => {
1464 additionalProperties => 0,
1465 properties => {
1466 node => get_standard_option('pve-node'),
335af808 1467 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e3baf05
DM
1468 idlist => {
1469 type => 'string', format => 'pve-configid-list',
1470 description => "A list of disk IDs you want to delete.",
1471 },
1472 force => {
1473 type => 'boolean',
1474 description => $opt_force_description,
1475 optional => 1,
1476 },
1477 },
1478 },
1479 returns => { type => 'null'},
1480 code => sub {
1481 my ($param) = @_;
1482
1483 $param->{delete} = extract_param($param, 'idlist');
1484
1485 __PACKAGE__->update_vm($param);
1486
1487 return undef;
1488 }});
1489
1490my $sslcert;
1491
1492__PACKAGE__->register_method({
afdb31d5
DM
1493 name => 'vncproxy',
1494 path => '{vmid}/vncproxy',
1e3baf05
DM
1495 method => 'POST',
1496 protected => 1,
1497 permissions => {
378b359e 1498 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
1499 },
1500 description => "Creates a TCP VNC proxy connections.",
1501 parameters => {
1502 additionalProperties => 0,
1503 properties => {
1504 node => get_standard_option('pve-node'),
1505 vmid => get_standard_option('pve-vmid'),
b4d5c000
SP
1506 websocket => {
1507 optional => 1,
1508 type => 'boolean',
1509 description => "starts websockify instead of vncproxy",
1510 },
1e3baf05
DM
1511 },
1512 },
afdb31d5 1513 returns => {
1e3baf05
DM
1514 additionalProperties => 0,
1515 properties => {
1516 user => { type => 'string' },
1517 ticket => { type => 'string' },
1518 cert => { type => 'string' },
1519 port => { type => 'integer' },
1520 upid => { type => 'string' },
1521 },
1522 },
1523 code => sub {
1524 my ($param) = @_;
1525
1526 my $rpcenv = PVE::RPCEnvironment::get();
1527
a0d1b1a2 1528 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1529
1530 my $vmid = $param->{vmid};
1531 my $node = $param->{node};
983d4582 1532 my $websocket = $param->{websocket};
1e3baf05 1533
ffda963f 1534 my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
414b42d8 1535 my $use_serial = ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/));
ef5e2be2 1536
b6f39da2
DM
1537 my $authpath = "/vms/$vmid";
1538
a0d1b1a2 1539 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 1540
1e3baf05
DM
1541 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1542 if !$sslcert;
1543
414b42d8 1544 my $family;
ef5e2be2 1545 my $remcmd = [];
afdb31d5 1546
4f1be36c 1547 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
414b42d8
DC
1548 (undef, $family) = PVE::Cluster::remote_node_ip($node);
1549 my $sshinfo = PVE::Cluster::get_ssh_info($node);
b4d5c000 1550 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
8fef2bdb 1551 $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, $use_serial ? '-t' : '-T');
af0eba7e
WB
1552 } else {
1553 $family = PVE::Tools::get_host_address_family($node);
1e3baf05
DM
1554 }
1555
af0eba7e
WB
1556 my $port = PVE::Tools::next_vnc_port($family);
1557
afdb31d5 1558 my $timeout = 10;
1e3baf05
DM
1559
1560 my $realcmd = sub {
1561 my $upid = shift;
1562
1563 syslog('info', "starting vnc proxy $upid\n");
1564
ef5e2be2 1565 my $cmd;
1e3baf05 1566
414b42d8 1567 if ($use_serial) {
b4d5c000 1568
ccb88f45 1569 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga}, '-escape', '0' ];
9e6d6e97 1570
ef5e2be2 1571 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
fa8ea931 1572 '-timeout', $timeout, '-authpath', $authpath,
9e6d6e97
DC
1573 '-perm', 'Sys.Console'];
1574
1575 if ($param->{websocket}) {
1576 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
1577 push @$cmd, '-notls', '-listen', 'localhost';
1578 }
1579
1580 push @$cmd, '-c', @$remcmd, @$termcmd;
1581
655d7462 1582 PVE::Tools::run_command($cmd);
9e6d6e97 1583
ef5e2be2 1584 } else {
1e3baf05 1585
3e7567e0
DM
1586 $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy"
1587
655d7462
WB
1588 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1589
1590 my $sock = IO::Socket::IP->new(
dd32a466 1591 ReuseAddr => 1,
655d7462
WB
1592 Listen => 1,
1593 LocalPort => $port,
1594 Proto => 'tcp',
1595 GetAddrInfoFlags => 0,
1596 ) or die "failed to create socket: $!\n";
1597 # Inside the worker we shouldn't have any previous alarms
1598 # running anyway...:
1599 alarm(0);
1600 local $SIG{ALRM} = sub { die "connection timed out\n" };
1601 alarm $timeout;
1602 accept(my $cli, $sock) or die "connection failed: $!\n";
058ff55b 1603 alarm(0);
655d7462
WB
1604 close($sock);
1605 if (PVE::Tools::run_command($cmd,
1606 output => '>&'.fileno($cli),
1607 input => '<&'.fileno($cli),
1608 noerr => 1) != 0)
1609 {
1610 die "Failed to run vncproxy.\n";
1611 }
ef5e2be2 1612 }
1e3baf05 1613
1e3baf05
DM
1614 return;
1615 };
1616
2c7fc947 1617 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1e3baf05 1618
3da85107
DM
1619 PVE::Tools::wait_for_vnc_port($port);
1620
1e3baf05 1621 return {
a0d1b1a2 1622 user => $authuser,
1e3baf05 1623 ticket => $ticket,
afdb31d5
DM
1624 port => $port,
1625 upid => $upid,
1626 cert => $sslcert,
1e3baf05
DM
1627 };
1628 }});
1629
87302002
DC
1630__PACKAGE__->register_method({
1631 name => 'termproxy',
1632 path => '{vmid}/termproxy',
1633 method => 'POST',
1634 protected => 1,
1635 permissions => {
1636 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1637 },
1638 description => "Creates a TCP proxy connections.",
1639 parameters => {
1640 additionalProperties => 0,
1641 properties => {
1642 node => get_standard_option('pve-node'),
1643 vmid => get_standard_option('pve-vmid'),
1644 serial=> {
1645 optional => 1,
1646 type => 'string',
1647 enum => [qw(serial0 serial1 serial2 serial3)],
1648 description => "opens a serial terminal (defaults to display)",
1649 },
1650 },
1651 },
1652 returns => {
1653 additionalProperties => 0,
1654 properties => {
1655 user => { type => 'string' },
1656 ticket => { type => 'string' },
1657 port => { type => 'integer' },
1658 upid => { type => 'string' },
1659 },
1660 },
1661 code => sub {
1662 my ($param) = @_;
1663
1664 my $rpcenv = PVE::RPCEnvironment::get();
1665
1666 my $authuser = $rpcenv->get_user();
1667
1668 my $vmid = $param->{vmid};
1669 my $node = $param->{node};
1670 my $serial = $param->{serial};
1671
1672 my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
1673
1674 if (!defined($serial)) {
1675 if ($conf->{vga} && $conf->{vga} =~ m/^serial\d+$/) {
1676 $serial = $conf->{vga};
1677 }
1678 }
1679
1680 my $authpath = "/vms/$vmid";
1681
1682 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
1683
414b42d8
DC
1684 my $family;
1685 my $remcmd = [];
87302002
DC
1686
1687 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
414b42d8
DC
1688 (undef, $family) = PVE::Cluster::remote_node_ip($node);
1689 my $sshinfo = PVE::Cluster::get_ssh_info($node);
1690 $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, '-t');
1691 push @$remcmd, '--';
87302002
DC
1692 } else {
1693 $family = PVE::Tools::get_host_address_family($node);
1694 }
1695
1696 my $port = PVE::Tools::next_vnc_port($family);
1697
ccb88f45 1698 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
87302002
DC
1699 push @$termcmd, '-iface', $serial if $serial;
1700
1701 my $realcmd = sub {
1702 my $upid = shift;
1703
1704 syslog('info', "starting qemu termproxy $upid\n");
1705
1706 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1707 '--perm', 'VM.Console', '--'];
1708 push @$cmd, @$remcmd, @$termcmd;
1709
1710 PVE::Tools::run_command($cmd);
1711 };
1712
1713 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1714
1715 PVE::Tools::wait_for_vnc_port($port);
1716
1717 return {
1718 user => $authuser,
1719 ticket => $ticket,
1720 port => $port,
1721 upid => $upid,
1722 };
1723 }});
1724
3e7567e0
DM
1725__PACKAGE__->register_method({
1726 name => 'vncwebsocket',
1727 path => '{vmid}/vncwebsocket',
1728 method => 'GET',
3e7567e0 1729 permissions => {
c422ce93 1730 description => "You also need to pass a valid ticket (vncticket).",
3e7567e0
DM
1731 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1732 },
4d00f52f 1733 description => "Opens a weksocket for VNC traffic.",
3e7567e0
DM
1734 parameters => {
1735 additionalProperties => 0,
1736 properties => {
1737 node => get_standard_option('pve-node'),
1738 vmid => get_standard_option('pve-vmid'),
c422ce93
DM
1739 vncticket => {
1740 description => "Ticket from previous call to vncproxy.",
1741 type => 'string',
1742 maxLength => 512,
1743 },
3e7567e0
DM
1744 port => {
1745 description => "Port number returned by previous vncproxy call.",
1746 type => 'integer',
1747 minimum => 5900,
1748 maximum => 5999,
1749 },
1750 },
1751 },
1752 returns => {
1753 type => "object",
1754 properties => {
1755 port => { type => 'string' },
1756 },
1757 },
1758 code => sub {
1759 my ($param) = @_;
1760
1761 my $rpcenv = PVE::RPCEnvironment::get();
1762
1763 my $authuser = $rpcenv->get_user();
1764
1765 my $vmid = $param->{vmid};
1766 my $node = $param->{node};
1767
c422ce93
DM
1768 my $authpath = "/vms/$vmid";
1769
1770 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1771
ffda963f 1772 my $conf = PVE::QemuConfig->load_config($vmid, $node); # VM exists ?
3e7567e0
DM
1773
1774 # Note: VNC ports are acessible from outside, so we do not gain any
1775 # security if we verify that $param->{port} belongs to VM $vmid. This
1776 # check is done by verifying the VNC ticket (inside VNC protocol).
1777
1778 my $port = $param->{port};
f34ebd52 1779
3e7567e0
DM
1780 return { port => $port };
1781 }});
1782
288eeea8
DM
1783__PACKAGE__->register_method({
1784 name => 'spiceproxy',
1785 path => '{vmid}/spiceproxy',
78252ce7 1786 method => 'POST',
288eeea8 1787 protected => 1,
78252ce7 1788 proxyto => 'node',
288eeea8
DM
1789 permissions => {
1790 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1791 },
1792 description => "Returns a SPICE configuration to connect to the VM.",
1793 parameters => {
1794 additionalProperties => 0,
1795 properties => {
1796 node => get_standard_option('pve-node'),
1797 vmid => get_standard_option('pve-vmid'),
dd25eecf 1798 proxy => get_standard_option('spice-proxy', { optional => 1 }),
288eeea8
DM
1799 },
1800 },
dd25eecf 1801 returns => get_standard_option('remote-viewer-config'),
288eeea8
DM
1802 code => sub {
1803 my ($param) = @_;
1804
1805 my $rpcenv = PVE::RPCEnvironment::get();
1806
1807 my $authuser = $rpcenv->get_user();
1808
1809 my $vmid = $param->{vmid};
1810 my $node = $param->{node};
fb6c7260 1811 my $proxy = $param->{proxy};
288eeea8 1812
ffda963f 1813 my $conf = PVE::QemuConfig->load_config($vmid, $node);
7f9e28e4
TL
1814 my $title = "VM $vmid";
1815 $title .= " - ". $conf->{name} if $conf->{name};
288eeea8 1816
943340a6 1817 my $port = PVE::QemuServer::spice_port($vmid);
dd25eecf 1818
f34ebd52 1819 my ($ticket, undef, $remote_viewer_config) =
dd25eecf 1820 PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
f34ebd52 1821
288eeea8
DM
1822 PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
1823 PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
f34ebd52 1824
dd25eecf 1825 return $remote_viewer_config;
288eeea8
DM
1826 }});
1827
5fdbe4f0
DM
1828__PACKAGE__->register_method({
1829 name => 'vmcmdidx',
afdb31d5 1830 path => '{vmid}/status',
5fdbe4f0
DM
1831 method => 'GET',
1832 proxyto => 'node',
1833 description => "Directory index",
a0d1b1a2
DM
1834 permissions => {
1835 user => 'all',
1836 },
5fdbe4f0
DM
1837 parameters => {
1838 additionalProperties => 0,
1839 properties => {
1840 node => get_standard_option('pve-node'),
1841 vmid => get_standard_option('pve-vmid'),
1842 },
1843 },
1844 returns => {
1845 type => 'array',
1846 items => {
1847 type => "object",
1848 properties => {
1849 subdir => { type => 'string' },
1850 },
1851 },
1852 links => [ { rel => 'child', href => "{subdir}" } ],
1853 },
1854 code => sub {
1855 my ($param) = @_;
1856
1857 # test if VM exists
ffda963f 1858 my $conf = PVE::QemuConfig->load_config($param->{vmid});
5fdbe4f0
DM
1859
1860 my $res = [
1861 { subdir => 'current' },
1862 { subdir => 'start' },
1863 { subdir => 'stop' },
58f9db6a
DC
1864 { subdir => 'reset' },
1865 { subdir => 'shutdown' },
1866 { subdir => 'suspend' },
165411f0 1867 { subdir => 'reboot' },
5fdbe4f0 1868 ];
afdb31d5 1869
5fdbe4f0
DM
1870 return $res;
1871 }});
1872
1e3baf05 1873__PACKAGE__->register_method({
afdb31d5 1874 name => 'vm_status',
5fdbe4f0 1875 path => '{vmid}/status/current',
1e3baf05
DM
1876 method => 'GET',
1877 proxyto => 'node',
1878 protected => 1, # qemu pid files are only readable by root
1879 description => "Get virtual machine status.",
a0d1b1a2
DM
1880 permissions => {
1881 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1882 },
1e3baf05
DM
1883 parameters => {
1884 additionalProperties => 0,
1885 properties => {
1886 node => get_standard_option('pve-node'),
1887 vmid => get_standard_option('pve-vmid'),
1888 },
1889 },
b1a70cab
DM
1890 returns => {
1891 type => 'object',
1892 properties => {
1893 %$PVE::QemuServer::vmstatus_return_properties,
1894 ha => {
1895 description => "HA manager service status.",
1896 type => 'object',
1897 },
1898 spice => {
1899 description => "Qemu VGA configuration supports spice.",
1900 type => 'boolean',
1901 optional => 1,
1902 },
1903 agent => {
1904 description => "Qemu GuestAgent enabled in config.",
1905 type => 'boolean',
1906 optional => 1,
1907 },
1908 },
1909 },
1e3baf05
DM
1910 code => sub {
1911 my ($param) = @_;
1912
1913 # test if VM exists
ffda963f 1914 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 1915
03a33f30 1916 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1917 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1918
4d2a734e 1919 $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
8610701a 1920
86b8228b 1921 $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
9d66b397 1922 $status->{agent} = 1 if (PVE::QemuServer::parse_guest_agent($conf)->{enabled});
c9a074b8 1923
8610701a 1924 return $status;
1e3baf05
DM
1925 }});
1926
1927__PACKAGE__->register_method({
afdb31d5 1928 name => 'vm_start',
5fdbe4f0
DM
1929 path => '{vmid}/status/start',
1930 method => 'POST',
1e3baf05
DM
1931 protected => 1,
1932 proxyto => 'node',
5fdbe4f0 1933 description => "Start virtual machine.",
a0d1b1a2
DM
1934 permissions => {
1935 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1936 },
1e3baf05
DM
1937 parameters => {
1938 additionalProperties => 0,
1939 properties => {
1940 node => get_standard_option('pve-node'),
ab5904f7
TL
1941 vmid => get_standard_option('pve-vmid',
1942 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1943 skiplock => get_standard_option('skiplock'),
1944 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1945 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1946 migration_type => {
1947 type => 'string',
1948 enum => ['secure', 'insecure'],
1949 description => "Migration traffic is encrypted using an SSH " .
1950 "tunnel by default. On secure, completely private networks " .
1951 "this can be disabled to increase performance.",
1952 optional => 1,
1953 },
1954 migration_network => {
29ddbe70 1955 type => 'string', format => 'CIDR',
2de2d6f7
TL
1956 description => "CIDR of the (sub) network that is used for migration.",
1957 optional => 1,
1958 },
952958bc 1959 machine => get_standard_option('pve-qm-machine'),
2189246c 1960 targetstorage => {
bd2d5fe6 1961 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
1962 type => 'string',
1963 optional => 1
1964 }
1e3baf05
DM
1965 },
1966 },
afdb31d5 1967 returns => {
5fdbe4f0
DM
1968 type => 'string',
1969 },
1e3baf05
DM
1970 code => sub {
1971 my ($param) = @_;
1972
1973 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 1974 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1975
1976 my $node = extract_param($param, 'node');
1e3baf05
DM
1977 my $vmid = extract_param($param, 'vmid');
1978
952958bc
DM
1979 my $machine = extract_param($param, 'machine');
1980
736c92f6
TL
1981 my $get_root_param = sub {
1982 my $value = extract_param($param, $_[0]);
1983 raise_param_exc({ "$_[0]" => "Only root may use this option." })
1984 if $value && $authuser ne 'root@pam';
1985 return $value;
1986 };
2de2d6f7 1987
736c92f6
TL
1988 my $stateuri = $get_root_param->('stateuri');
1989 my $skiplock = $get_root_param->('skiplock');
1990 my $migratedfrom = $get_root_param->('migratedfrom');
1991 my $migration_type = $get_root_param->('migration_type');
1992 my $migration_network = $get_root_param->('migration_network');
1993 my $targetstorage = $get_root_param->('targetstorage');
2189246c
AD
1994
1995 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
1996 if $targetstorage && !$migratedfrom;
1997
7c14dcae
DM
1998 # read spice ticket from STDIN
1999 my $spice_ticket;
ccab68c2 2000 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
e5caa02e 2001 if (defined(my $line = <STDIN>)) {
760fb3c8
DM
2002 chomp $line;
2003 $spice_ticket = $line;
2004 }
7c14dcae
DM
2005 }
2006
98cbd0f4
WB
2007 PVE::Cluster::check_cfs_quorum();
2008
afdb31d5 2009 my $storecfg = PVE::Storage::config();
5fdbe4f0 2010
a4262553 2011 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri && $rpcenv->{type} ne 'ha') {
88fc87b4
DM
2012 my $hacmd = sub {
2013 my $upid = shift;
5fdbe4f0 2014
02765844 2015 print "Requesting HA start for VM $vmid\n";
88fc87b4 2016
a4262553 2017 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
88fc87b4 2018 PVE::Tools::run_command($cmd);
88fc87b4
DM
2019 return;
2020 };
2021
2022 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2023
2024 } else {
2025
2026 my $realcmd = sub {
2027 my $upid = shift;
2028
2029 syslog('info', "start VM $vmid: $upid\n");
2030
fa8ea931 2031 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 2032 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
2033 return;
2034 };
5fdbe4f0 2035
88fc87b4
DM
2036 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2037 }
5fdbe4f0
DM
2038 }});
2039
2040__PACKAGE__->register_method({
afdb31d5 2041 name => 'vm_stop',
5fdbe4f0
DM
2042 path => '{vmid}/status/stop',
2043 method => 'POST',
2044 protected => 1,
2045 proxyto => 'node',
346130b2 2046 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 2047 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
2048 permissions => {
2049 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2050 },
5fdbe4f0
DM
2051 parameters => {
2052 additionalProperties => 0,
2053 properties => {
2054 node => get_standard_option('pve-node'),
ab5904f7
TL
2055 vmid => get_standard_option('pve-vmid',
2056 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2057 skiplock => get_standard_option('skiplock'),
debe8882 2058 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
2059 timeout => {
2060 description => "Wait maximal timeout seconds.",
2061 type => 'integer',
2062 minimum => 0,
2063 optional => 1,
254575e9
DM
2064 },
2065 keepActive => {
94a17e1d 2066 description => "Do not deactivate storage volumes.",
254575e9
DM
2067 type => 'boolean',
2068 optional => 1,
2069 default => 0,
c6bb9502 2070 }
5fdbe4f0
DM
2071 },
2072 },
afdb31d5 2073 returns => {
5fdbe4f0
DM
2074 type => 'string',
2075 },
2076 code => sub {
2077 my ($param) = @_;
2078
2079 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2080 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2081
2082 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2083 my $vmid = extract_param($param, 'vmid');
2084
2085 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2086 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2087 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2088
254575e9 2089 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2090 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2091 if $keepActive && $authuser ne 'root@pam';
254575e9 2092
af30308f
DM
2093 my $migratedfrom = extract_param($param, 'migratedfrom');
2094 raise_param_exc({ migratedfrom => "Only root may use this option." })
2095 if $migratedfrom && $authuser ne 'root@pam';
2096
2097
ff1a2432
DM
2098 my $storecfg = PVE::Storage::config();
2099
2003f0f8 2100 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 2101
88fc87b4
DM
2102 my $hacmd = sub {
2103 my $upid = shift;
5fdbe4f0 2104
02765844 2105 print "Requesting HA stop for VM $vmid\n";
88fc87b4 2106
a4262553 2107 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
88fc87b4 2108 PVE::Tools::run_command($cmd);
88fc87b4
DM
2109 return;
2110 };
2111
2112 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2113
2114 } else {
2115 my $realcmd = sub {
2116 my $upid = shift;
2117
2118 syslog('info', "stop VM $vmid: $upid\n");
2119
2120 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 2121 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
2122 return;
2123 };
2124
2125 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2126 }
5fdbe4f0
DM
2127 }});
2128
2129__PACKAGE__->register_method({
afdb31d5 2130 name => 'vm_reset',
5fdbe4f0
DM
2131 path => '{vmid}/status/reset',
2132 method => 'POST',
2133 protected => 1,
2134 proxyto => 'node',
2135 description => "Reset virtual machine.",
a0d1b1a2
DM
2136 permissions => {
2137 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2138 },
5fdbe4f0
DM
2139 parameters => {
2140 additionalProperties => 0,
2141 properties => {
2142 node => get_standard_option('pve-node'),
ab5904f7
TL
2143 vmid => get_standard_option('pve-vmid',
2144 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2145 skiplock => get_standard_option('skiplock'),
2146 },
2147 },
afdb31d5 2148 returns => {
5fdbe4f0
DM
2149 type => 'string',
2150 },
2151 code => sub {
2152 my ($param) = @_;
2153
2154 my $rpcenv = PVE::RPCEnvironment::get();
2155
a0d1b1a2 2156 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2157
2158 my $node = extract_param($param, 'node');
2159
2160 my $vmid = extract_param($param, 'vmid');
2161
2162 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2163 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2164 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2165
ff1a2432
DM
2166 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2167
5fdbe4f0
DM
2168 my $realcmd = sub {
2169 my $upid = shift;
2170
1e3baf05 2171 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
2172
2173 return;
2174 };
2175
a0d1b1a2 2176 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2177 }});
2178
2179__PACKAGE__->register_method({
afdb31d5 2180 name => 'vm_shutdown',
5fdbe4f0
DM
2181 path => '{vmid}/status/shutdown',
2182 method => 'POST',
2183 protected => 1,
2184 proxyto => 'node',
d6c747ff
EK
2185 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2186 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
2187 permissions => {
2188 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2189 },
5fdbe4f0
DM
2190 parameters => {
2191 additionalProperties => 0,
2192 properties => {
2193 node => get_standard_option('pve-node'),
ab5904f7
TL
2194 vmid => get_standard_option('pve-vmid',
2195 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2196 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
2197 timeout => {
2198 description => "Wait maximal timeout seconds.",
2199 type => 'integer',
2200 minimum => 0,
2201 optional => 1,
9269013a
DM
2202 },
2203 forceStop => {
2204 description => "Make sure the VM stops.",
2205 type => 'boolean',
2206 optional => 1,
2207 default => 0,
254575e9
DM
2208 },
2209 keepActive => {
94a17e1d 2210 description => "Do not deactivate storage volumes.",
254575e9
DM
2211 type => 'boolean',
2212 optional => 1,
2213 default => 0,
c6bb9502 2214 }
5fdbe4f0
DM
2215 },
2216 },
afdb31d5 2217 returns => {
5fdbe4f0
DM
2218 type => 'string',
2219 },
2220 code => sub {
2221 my ($param) = @_;
2222
2223 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2224 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2225
2226 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2227 my $vmid = extract_param($param, 'vmid');
2228
2229 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2230 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2231 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2232
254575e9 2233 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2234 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2235 if $keepActive && $authuser ne 'root@pam';
254575e9 2236
02d07cf5
DM
2237 my $storecfg = PVE::Storage::config();
2238
89897367
DC
2239 my $shutdown = 1;
2240
2241 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2242 # otherwise, we will infer a shutdown command, but run into the timeout,
2243 # then when the vm is resumed, it will instantly shutdown
2244 #
2245 # checking the qmp status here to get feedback to the gui/cli/api
2246 # and the status query should not take too long
a4262553
TL
2247 my $qmpstatus = eval {
2248 PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
89897367
DC
2249 };
2250 my $err = $@ if $@;
2251
2252 if (!$err && $qmpstatus->{status} eq "paused") {
2253 if ($param->{forceStop}) {
2254 warn "VM is paused - stop instead of shutdown\n";
2255 $shutdown = 0;
2256 } else {
2257 die "VM is paused - cannot shutdown\n";
2258 }
2259 }
2260
a4262553 2261 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
5fdbe4f0 2262
ae849692
DM
2263 my $hacmd = sub {
2264 my $upid = shift;
5fdbe4f0 2265
02765844 2266 print "Requesting HA stop for VM $vmid\n";
ae849692 2267
a4262553 2268 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
ae849692 2269 PVE::Tools::run_command($cmd);
ae849692
DM
2270 return;
2271 };
2272
2273 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2274
2275 } else {
2276
2277 my $realcmd = sub {
2278 my $upid = shift;
2279
2280 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2281
ae849692
DM
2282 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2283 $shutdown, $param->{forceStop}, $keepActive);
ae849692
DM
2284 return;
2285 };
2286
2287 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2288 }
5fdbe4f0
DM
2289 }});
2290
165411f0
DC
2291__PACKAGE__->register_method({
2292 name => 'vm_reboot',
2293 path => '{vmid}/status/reboot',
2294 method => 'POST',
2295 protected => 1,
2296 proxyto => 'node',
2297 description => "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2298 permissions => {
2299 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2300 },
2301 parameters => {
2302 additionalProperties => 0,
2303 properties => {
2304 node => get_standard_option('pve-node'),
2305 vmid => get_standard_option('pve-vmid',
2306 { completion => \&PVE::QemuServer::complete_vmid_running }),
2307 timeout => {
2308 description => "Wait maximal timeout seconds for the shutdown.",
2309 type => 'integer',
2310 minimum => 0,
2311 optional => 1,
2312 },
2313 },
2314 },
2315 returns => {
2316 type => 'string',
2317 },
2318 code => sub {
2319 my ($param) = @_;
2320
2321 my $rpcenv = PVE::RPCEnvironment::get();
2322 my $authuser = $rpcenv->get_user();
2323
2324 my $node = extract_param($param, 'node');
2325 my $vmid = extract_param($param, 'vmid');
2326
2327 my $qmpstatus = eval {
2328 PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
2329 };
2330 my $err = $@ if $@;
2331
2332 if (!$err && $qmpstatus->{status} eq "paused") {
2333 die "VM is paused - cannot shutdown\n";
2334 }
2335
2336 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2337
2338 my $realcmd = sub {
2339 my $upid = shift;
2340
2341 syslog('info', "requesting reboot of VM $vmid: $upid\n");
2342 PVE::QemuServer::vm_reboot($vmid, $param->{timeout});
2343 return;
2344 };
2345
2346 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2347 }});
2348
5fdbe4f0 2349__PACKAGE__->register_method({
afdb31d5 2350 name => 'vm_suspend',
5fdbe4f0
DM
2351 path => '{vmid}/status/suspend',
2352 method => 'POST',
2353 protected => 1,
2354 proxyto => 'node',
2355 description => "Suspend virtual machine.",
a0d1b1a2
DM
2356 permissions => {
2357 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2358 },
5fdbe4f0
DM
2359 parameters => {
2360 additionalProperties => 0,
2361 properties => {
2362 node => get_standard_option('pve-node'),
ab5904f7
TL
2363 vmid => get_standard_option('pve-vmid',
2364 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2365 skiplock => get_standard_option('skiplock'),
22371fe0
DC
2366 todisk => {
2367 type => 'boolean',
2368 default => 0,
2369 optional => 1,
2370 description => 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2371 },
48b4cdc2
DC
2372 statestorage => get_standard_option('pve-storage-id', {
2373 description => "The storage for the VM state",
2374 requires => 'todisk',
2375 optional => 1,
2376 completion => \&PVE::Storage::complete_storage_enabled,
2377 }),
5fdbe4f0
DM
2378 },
2379 },
afdb31d5 2380 returns => {
5fdbe4f0
DM
2381 type => 'string',
2382 },
2383 code => sub {
2384 my ($param) = @_;
2385
2386 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2387 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2388
2389 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2390 my $vmid = extract_param($param, 'vmid');
2391
22371fe0
DC
2392 my $todisk = extract_param($param, 'todisk') // 0;
2393
48b4cdc2
DC
2394 my $statestorage = extract_param($param, 'statestorage');
2395
5fdbe4f0 2396 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2397 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2398 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2399
ff1a2432
DM
2400 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2401
22371fe0
DC
2402 die "Cannot suspend HA managed VM to disk\n"
2403 if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);
2404
5fdbe4f0
DM
2405 my $realcmd = sub {
2406 my $upid = shift;
2407
2408 syslog('info', "suspend VM $vmid: $upid\n");
2409
48b4cdc2 2410 PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
5fdbe4f0
DM
2411
2412 return;
2413 };
2414
a4262553 2415 my $taskname = $todisk ? 'qmsuspend' : 'qmpause';
f17fb184 2416 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2417 }});
2418
2419__PACKAGE__->register_method({
afdb31d5 2420 name => 'vm_resume',
5fdbe4f0
DM
2421 path => '{vmid}/status/resume',
2422 method => 'POST',
2423 protected => 1,
2424 proxyto => 'node',
2425 description => "Resume virtual machine.",
a0d1b1a2
DM
2426 permissions => {
2427 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2428 },
5fdbe4f0
DM
2429 parameters => {
2430 additionalProperties => 0,
2431 properties => {
2432 node => get_standard_option('pve-node'),
ab5904f7
TL
2433 vmid => get_standard_option('pve-vmid',
2434 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2435 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2436 nocheck => { type => 'boolean', optional => 1 },
2437
5fdbe4f0
DM
2438 },
2439 },
afdb31d5 2440 returns => {
5fdbe4f0
DM
2441 type => 'string',
2442 },
2443 code => sub {
2444 my ($param) = @_;
2445
2446 my $rpcenv = PVE::RPCEnvironment::get();
2447
a0d1b1a2 2448 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2449
2450 my $node = extract_param($param, 'node');
2451
2452 my $vmid = extract_param($param, 'vmid');
2453
2454 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2455 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2456 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2457
289e0b85
AD
2458 my $nocheck = extract_param($param, 'nocheck');
2459
cd9a035b
TL
2460 my $to_disk_suspended;
2461 eval {
2462 PVE::QemuConfig->lock_config($vmid, sub {
2463 my $conf = PVE::QemuConfig->load_config($vmid);
2464 $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
2465 });
2466 };
2467
2468 die "VM $vmid not running\n"
2469 if !$to_disk_suspended && !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2470
5fdbe4f0
DM
2471 my $realcmd = sub {
2472 my $upid = shift;
2473
2474 syslog('info', "resume VM $vmid: $upid\n");
2475
cd9a035b
TL
2476 if (!$to_disk_suspended) {
2477 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
2478 } else {
2479 my $storecfg = PVE::Storage::config();
2480 PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
2481 }
1e3baf05 2482
5fdbe4f0
DM
2483 return;
2484 };
2485
a0d1b1a2 2486 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2487 }});
2488
2489__PACKAGE__->register_method({
afdb31d5 2490 name => 'vm_sendkey',
5fdbe4f0
DM
2491 path => '{vmid}/sendkey',
2492 method => 'PUT',
2493 protected => 1,
2494 proxyto => 'node',
2495 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2496 permissions => {
2497 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2498 },
5fdbe4f0
DM
2499 parameters => {
2500 additionalProperties => 0,
2501 properties => {
2502 node => get_standard_option('pve-node'),
ab5904f7
TL
2503 vmid => get_standard_option('pve-vmid',
2504 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2505 skiplock => get_standard_option('skiplock'),
2506 key => {
2507 description => "The key (qemu monitor encoding).",
2508 type => 'string'
2509 }
2510 },
2511 },
2512 returns => { type => 'null'},
2513 code => sub {
2514 my ($param) = @_;
2515
2516 my $rpcenv = PVE::RPCEnvironment::get();
2517
a0d1b1a2 2518 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2519
2520 my $node = extract_param($param, 'node');
2521
2522 my $vmid = extract_param($param, 'vmid');
2523
2524 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2525 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2526 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2527
2528 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2529
2530 return;
1e3baf05
DM
2531 }});
2532
1ac0d2ee
AD
2533__PACKAGE__->register_method({
2534 name => 'vm_feature',
2535 path => '{vmid}/feature',
2536 method => 'GET',
2537 proxyto => 'node',
75466c4f 2538 protected => 1,
1ac0d2ee
AD
2539 description => "Check if feature for virtual machine is available.",
2540 permissions => {
2541 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2542 },
2543 parameters => {
2544 additionalProperties => 0,
2545 properties => {
2546 node => get_standard_option('pve-node'),
2547 vmid => get_standard_option('pve-vmid'),
2548 feature => {
2549 description => "Feature to check.",
2550 type => 'string',
7758ce86 2551 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2552 },
2553 snapname => get_standard_option('pve-snapshot-name', {
2554 optional => 1,
2555 }),
2556 },
1ac0d2ee
AD
2557 },
2558 returns => {
719893a9
DM
2559 type => "object",
2560 properties => {
2561 hasFeature => { type => 'boolean' },
7043d946 2562 nodes => {
719893a9
DM
2563 type => 'array',
2564 items => { type => 'string' },
2565 }
2566 },
1ac0d2ee
AD
2567 },
2568 code => sub {
2569 my ($param) = @_;
2570
2571 my $node = extract_param($param, 'node');
2572
2573 my $vmid = extract_param($param, 'vmid');
2574
2575 my $snapname = extract_param($param, 'snapname');
2576
2577 my $feature = extract_param($param, 'feature');
2578
2579 my $running = PVE::QemuServer::check_running($vmid);
2580
ffda963f 2581 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2582
2583 if($snapname){
2584 my $snap = $conf->{snapshots}->{$snapname};
2585 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2586 $conf = $snap;
2587 }
2588 my $storecfg = PVE::Storage::config();
2589
719893a9 2590 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2591 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2592
719893a9
DM
2593 return {
2594 hasFeature => $hasFeature,
2595 nodes => [ keys %$nodelist ],
7043d946 2596 };
1ac0d2ee
AD
2597 }});
2598
6116f729 2599__PACKAGE__->register_method({
9418baad
DM
2600 name => 'clone_vm',
2601 path => '{vmid}/clone',
6116f729
DM
2602 method => 'POST',
2603 protected => 1,
2604 proxyto => 'node',
37329185 2605 description => "Create a copy of virtual machine/template.",
6116f729 2606 permissions => {
9418baad 2607 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2608 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2609 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2610 check =>
2611 [ 'and',
9418baad 2612 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2613 [ 'or',
6116f729
DM
2614 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2615 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2616 ],
2617 ]
2618 },
2619 parameters => {
2620 additionalProperties => 0,
2621 properties => {
6116f729 2622 node => get_standard_option('pve-node'),
335af808 2623 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1ae43f8c
DM
2624 newid => get_standard_option('pve-vmid', {
2625 completion => \&PVE::Cluster::complete_next_vmid,
2626 description => 'VMID for the clone.' }),
a60ab1a6
DM
2627 name => {
2628 optional => 1,
2629 type => 'string', format => 'dns-name',
2630 description => "Set a name for the new VM.",
2631 },
2632 description => {
2633 optional => 1,
2634 type => 'string',
2635 description => "Description for the new VM.",
2636 },
75466c4f 2637 pool => {
6116f729
DM
2638 optional => 1,
2639 type => 'string', format => 'pve-poolid',
2640 description => "Add the new VM to the specified pool.",
2641 },
9076d880 2642 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2643 optional => 1,
2644 }),
81f043eb 2645 storage => get_standard_option('pve-storage-id', {
9418baad 2646 description => "Target storage for full clone.",
81f043eb
AD
2647 optional => 1,
2648 }),
55173c6b 2649 'format' => {
fd13b1d0 2650 description => "Target format for file storage. Only valid for full clone.",
42a19c87
AD
2651 type => 'string',
2652 optional => 1,
55173c6b 2653 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2654 },
6116f729
DM
2655 full => {
2656 optional => 1,
55173c6b 2657 type => 'boolean',
fd13b1d0 2658 description => "Create a full copy of all disks. This is always done when " .
9418baad 2659 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729 2660 },
75466c4f 2661 target => get_standard_option('pve-node', {
55173c6b
DM
2662 description => "Target node. Only allowed if the original VM is on shared storage.",
2663 optional => 1,
2664 }),
0aab5a16
SI
2665 bwlimit => {
2666 description => "Override I/O bandwidth limit (in KiB/s).",
2667 optional => 1,
2668 type => 'integer',
2669 minimum => '0',
41756a3b 2670 default => 'clone limit from datacenter or storage config',
0aab5a16 2671 },
55173c6b 2672 },
6116f729
DM
2673 },
2674 returns => {
2675 type => 'string',
2676 },
2677 code => sub {
2678 my ($param) = @_;
2679
2680 my $rpcenv = PVE::RPCEnvironment::get();
2681
55173c6b 2682 my $authuser = $rpcenv->get_user();
6116f729
DM
2683
2684 my $node = extract_param($param, 'node');
2685
2686 my $vmid = extract_param($param, 'vmid');
2687
2688 my $newid = extract_param($param, 'newid');
2689
6116f729
DM
2690 my $pool = extract_param($param, 'pool');
2691
2692 if (defined($pool)) {
2693 $rpcenv->check_pool_exist($pool);
2694 }
2695
55173c6b 2696 my $snapname = extract_param($param, 'snapname');
9076d880 2697
81f043eb
AD
2698 my $storage = extract_param($param, 'storage');
2699
42a19c87
AD
2700 my $format = extract_param($param, 'format');
2701
55173c6b
DM
2702 my $target = extract_param($param, 'target');
2703
2704 my $localnode = PVE::INotify::nodename();
2705
751cc556 2706 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2707
2708 PVE::Cluster::check_node_exists($target) if $target;
2709
6116f729
DM
2710 my $storecfg = PVE::Storage::config();
2711
4a5a2590
DM
2712 if ($storage) {
2713 # check if storage is enabled on local node
2714 PVE::Storage::storage_check_enabled($storecfg, $storage);
2715 if ($target) {
2716 # check if storage is available on target node
2717 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2718 # clone only works if target storage is shared
2719 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2720 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2721 }
2722 }
2723
55173c6b 2724 PVE::Cluster::check_cfs_quorum();
6116f729 2725
4e4f83fe
DM
2726 my $running = PVE::QemuServer::check_running($vmid) || 0;
2727
4e4f83fe
DM
2728 # exclusive lock if VM is running - else shared lock is enough;
2729 my $shared_lock = $running ? 0 : 1;
2730
9418baad 2731 my $clonefn = sub {
6116f729 2732
829967a9
DM
2733 # do all tests after lock
2734 # we also try to do all tests before we fork the worker
2735
ffda963f 2736 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2737
ffda963f 2738 PVE::QemuConfig->check_lock($conf);
6116f729 2739
4e4f83fe 2740 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2741
4e4f83fe 2742 die "unexpected state change\n" if $verify_running != $running;
6116f729 2743
75466c4f
DM
2744 die "snapshot '$snapname' does not exist\n"
2745 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2746
fd13b1d0
DM
2747 my $full = extract_param($param, 'full');
2748 if (!defined($full)) {
2749 $full = !PVE::QemuConfig->is_template($conf);
2750 }
2751
2752 die "parameter 'storage' not allowed for linked clones\n"
2753 if defined($storage) && !$full;
2754
2755 die "parameter 'format' not allowed for linked clones\n"
2756 if defined($format) && !$full;
2757
75466c4f 2758 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2759
9418baad 2760 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2761
9418baad 2762 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2763
ffda963f 2764 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2765
2766 die "unable to create VM $newid: config file already exists\n"
2767 if -f $conffile;
2768
9418baad 2769 my $newconf = { lock => 'clone' };
829967a9 2770 my $drives = {};
34456bf0 2771 my $fullclone = {};
829967a9
DM
2772 my $vollist = [];
2773
2774 foreach my $opt (keys %$oldconf) {
2775 my $value = $oldconf->{$opt};
2776
2777 # do not copy snapshot related info
2778 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2779 $opt eq 'vmstate' || $opt eq 'snapstate';
2780
a78ea5df
WL
2781 # no need to copy unused images, because VMID(owner) changes anyways
2782 next if $opt =~ m/^unused\d+$/;
2783
829967a9
DM
2784 # always change MAC! address
2785 if ($opt =~ m/^net(\d+)$/) {
2786 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2787 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2788 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2789 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2790 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2791 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2792 die "unable to parse drive options for '$opt'\n" if !$drive;
7d6c99f0 2793 if (PVE::QemuServer::drive_is_cdrom($drive)) {
829967a9
DM
2794 $newconf->{$opt} = $value; # simply copy configuration
2795 } else {
7d6c99f0 2796 if ($full) {
6318daca 2797 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2798 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2799 $fullclone->{$opt} = 1;
64ff6fe4
SP
2800 } else {
2801 # not full means clone instead of copy
6318daca 2802 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2803 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2804 }
829967a9
DM
2805 $drives->{$opt} = $drive;
2806 push @$vollist, $drive->{file};
2807 }
2808 } else {
2809 # copy everything else
2810 $newconf->{$opt} = $value;
2811 }
2812 }
2813
cd11416f 2814 # auto generate a new uuid
cd11416f 2815 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
6ee499ff 2816 $smbios1->{uuid} = PVE::QemuServer::generate_uuid();
cd11416f
DM
2817 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2818
6ee499ff
DC
2819 # auto generate a new vmgenid if the option was set
2820 if ($newconf->{vmgenid}) {
2821 $newconf->{vmgenid} = PVE::QemuServer::generate_uuid();
2822 }
2823
829967a9
DM
2824 delete $newconf->{template};
2825
2826 if ($param->{name}) {
2827 $newconf->{name} = $param->{name};
2828 } else {
c55fee03
DM
2829 if ($oldconf->{name}) {
2830 $newconf->{name} = "Copy-of-$oldconf->{name}";
2831 } else {
2832 $newconf->{name} = "Copy-of-VM-$vmid";
2833 }
829967a9 2834 }
2dd53043 2835
829967a9
DM
2836 if ($param->{description}) {
2837 $newconf->{description} = $param->{description};
2838 }
2839
6116f729 2840 # create empty/temp config - this fails if VM already exists on other node
9418baad 2841 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2842
2843 my $realcmd = sub {
2844 my $upid = shift;
2845
b83e0181 2846 my $newvollist = [];
c6fdd002 2847 my $jobs = {};
6116f729 2848
b83e0181 2849 eval {
eaae66be
TL
2850 local $SIG{INT} =
2851 local $SIG{TERM} =
2852 local $SIG{QUIT} =
2853 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2854
eb15b9f0 2855 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2856
0aab5a16
SI
2857 my $bwlimit = extract_param($param, 'bwlimit');
2858
c6fdd002
AD
2859 my $total_jobs = scalar(keys %{$drives});
2860 my $i = 1;
c6fdd002 2861
829967a9
DM
2862 foreach my $opt (keys %$drives) {
2863 my $drive = $drives->{$opt};
3b4cf0f0 2864 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2865
0aab5a16 2866 my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
f0dbdb68
TL
2867 my $storage_list = [ $src_sid ];
2868 push @$storage_list, $storage if defined($storage);
ee43cd48 2869 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
0aab5a16 2870
152fe752 2871 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0 2872 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
0aab5a16 2873 $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
00b095ca 2874
152fe752 2875 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2876
ffda963f 2877 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2878 $i++;
829967a9 2879 }
b83e0181
DM
2880
2881 delete $newconf->{lock};
68e46b84
DC
2882
2883 # do not write pending changes
c725dd5f
DC
2884 if (my @changes = keys %{$newconf->{pending}}) {
2885 my $pending = join(',', @changes);
2886 warn "found pending changes for '$pending', discarding for clone\n";
68e46b84
DC
2887 delete $newconf->{pending};
2888 }
2889
ffda963f 2890 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2891
2892 if ($target) {
baca276d 2893 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2894 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2895 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2896
ffda963f 2897 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2898 die "Failed to move config to node '$target' - rename failed: $!\n"
2899 if !rename($conffile, $newconffile);
2900 }
d703d4c0 2901
be517049 2902 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2903 };
75466c4f 2904 if (my $err = $@) {
6116f729
DM
2905 unlink $conffile;
2906
c6fdd002
AD
2907 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2908
b83e0181
DM
2909 sleep 1; # some storage like rbd need to wait before release volume - really?
2910
2911 foreach my $volid (@$newvollist) {
2912 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2913 warn $@ if $@;
2914 }
9418baad 2915 die "clone failed: $err";
6116f729
DM
2916 }
2917
2918 return;
2919 };
2920
457010cc
AG
2921 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2922
9418baad 2923 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2924 };
2925
ffda963f 2926 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2927 # Aquire exclusive lock lock for $newid
ffda963f 2928 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2929 });
2930
2931 }});
2932
586bfa78 2933__PACKAGE__->register_method({
43bc02a9
DM
2934 name => 'move_vm_disk',
2935 path => '{vmid}/move_disk',
e2cd75fa 2936 method => 'POST',
586bfa78
AD
2937 protected => 1,
2938 proxyto => 'node',
2939 description => "Move volume to different storage.",
2940 permissions => {
c07a9e3d
DM
2941 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2942 check => [ 'and',
2943 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2944 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2945 ],
586bfa78
AD
2946 },
2947 parameters => {
2948 additionalProperties => 0,
c07a9e3d 2949 properties => {
586bfa78 2950 node => get_standard_option('pve-node'),
335af808 2951 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2952 disk => {
2953 type => 'string',
2954 description => "The disk you want to move.",
74479ee9 2955 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2956 },
335af808
DM
2957 storage => get_standard_option('pve-storage-id', {
2958 description => "Target storage.",
2959 completion => \&PVE::QemuServer::complete_storage,
2960 }),
635c3c44 2961 'format' => {
586bfa78
AD
2962 type => 'string',
2963 description => "Target Format.",
2964 enum => [ 'raw', 'qcow2', 'vmdk' ],
2965 optional => 1,
2966 },
70d45e33
DM
2967 delete => {
2968 type => 'boolean',
2969 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2970 optional => 1,
2971 default => 0,
2972 },
586bfa78
AD
2973 digest => {
2974 type => 'string',
2975 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2976 maxLength => 40,
2977 optional => 1,
2978 },
0aab5a16
SI
2979 bwlimit => {
2980 description => "Override I/O bandwidth limit (in KiB/s).",
2981 optional => 1,
2982 type => 'integer',
2983 minimum => '0',
41756a3b 2984 default => 'move limit from datacenter or storage config',
0aab5a16 2985 },
586bfa78
AD
2986 },
2987 },
e2cd75fa
DM
2988 returns => {
2989 type => 'string',
2990 description => "the task ID.",
2991 },
586bfa78
AD
2992 code => sub {
2993 my ($param) = @_;
2994
2995 my $rpcenv = PVE::RPCEnvironment::get();
2996
2997 my $authuser = $rpcenv->get_user();
2998
2999 my $node = extract_param($param, 'node');
3000
3001 my $vmid = extract_param($param, 'vmid');
3002
3003 my $digest = extract_param($param, 'digest');
3004
3005 my $disk = extract_param($param, 'disk');
3006
3007 my $storeid = extract_param($param, 'storage');
3008
3009 my $format = extract_param($param, 'format');
3010
586bfa78
AD
3011 my $storecfg = PVE::Storage::config();
3012
3013 my $updatefn = sub {
3014
ffda963f 3015 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 3016
dcce9b46
FG
3017 PVE::QemuConfig->check_lock($conf);
3018
586bfa78
AD
3019 die "checksum missmatch (file change by other user?)\n"
3020 if $digest && $digest ne $conf->{digest};
586bfa78
AD
3021
3022 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3023
3024 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3025
70d45e33 3026 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78 3027
931432bd 3028 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
586bfa78 3029
e2cd75fa 3030 my $oldfmt;
70d45e33 3031 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
3032 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3033 $oldfmt = $1;
3034 }
3035
7043d946 3036 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 3037 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 3038
9dbf9b54
FG
3039 # this only checks snapshots because $disk is passed!
3040 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
3041 die "you can't move a disk with snapshots and delete the source\n"
3042 if $snapshotted && $param->{delete};
3043
586bfa78
AD
3044 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3045
3046 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
3047
3048 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
3049
586bfa78
AD
3050 my $realcmd = sub {
3051
3052 my $newvollist = [];
3053
3054 eval {
6cb0144a
EK
3055 local $SIG{INT} =
3056 local $SIG{TERM} =
3057 local $SIG{QUIT} =
3058 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 3059
9dbf9b54
FG
3060 warn "moving disk with snapshots, snapshots will not be moved!\n"
3061 if $snapshotted;
3062
0aab5a16
SI
3063 my $bwlimit = extract_param($param, 'bwlimit');
3064 my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
3065
e2cd75fa 3066 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
0aab5a16 3067 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
e2cd75fa
DM
3068
3069 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
3070
8793d495 3071 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 3072
fbd7dcce
FG
3073 # convert moved disk to base if part of template
3074 PVE::QemuServer::template_create($vmid, $conf, $disk)
3075 if PVE::QemuConfig->is_template($conf);
3076
ffda963f 3077 PVE::QemuConfig->write_config($vmid, $conf);
73272365 3078
ca662131
SI
3079 if ($running && PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks} && PVE::QemuServer::qga_check_running($vmid)) {
3080 eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fstrim"); };
3081 }
3082
f34ebd52 3083 eval {
73272365 3084 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 3085 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
3086 if !$running;
3087 };
3088 warn $@ if $@;
586bfa78
AD
3089 };
3090 if (my $err = $@) {
3091
3092 foreach my $volid (@$newvollist) {
3093 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
3094 warn $@ if $@;
3095 }
3096 die "storage migration failed: $err";
3097 }
70d45e33
DM
3098
3099 if ($param->{delete}) {
a3d0bafb
FG
3100 eval {
3101 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
3102 PVE::Storage::vdisk_free($storecfg, $old_volid);
3103 };
3104 warn $@ if $@;
70d45e33 3105 }
586bfa78
AD
3106 };
3107
3108 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3109 };
e2cd75fa 3110
ffda963f 3111 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
3112 }});
3113
71fc647f
TM
3114my $check_vm_disks_local = sub {
3115 my ($storecfg, $vmconf, $vmid) = @_;
3116
3117 my $local_disks = {};
3118
3119 # add some more information to the disks e.g. cdrom
3120 PVE::QemuServer::foreach_volid($vmconf, sub {
3121 my ($volid, $attr) = @_;
3122
3123 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
3124 if ($storeid) {
3125 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
3126 return if $scfg->{shared};
3127 }
3128 # The shared attr here is just a special case where the vdisk
3129 # is marked as shared manually
3130 return if $attr->{shared};
3131 return if $attr->{cdrom} and $volid eq "none";
3132
3133 if (exists $local_disks->{$volid}) {
3134 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3135 } else {
3136 $local_disks->{$volid} = $attr;
3137 # ensure volid is present in case it's needed
3138 $local_disks->{$volid}->{volid} = $volid;
3139 }
3140 });
3141
3142 return $local_disks;
3143};
3144
3145__PACKAGE__->register_method({
3146 name => 'migrate_vm_precondition',
3147 path => '{vmid}/migrate',
3148 method => 'GET',
3149 protected => 1,
3150 proxyto => 'node',
3151 description => "Get preconditions for migration.",
3152 permissions => {
3153 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3154 },
3155 parameters => {
3156 additionalProperties => 0,
3157 properties => {
3158 node => get_standard_option('pve-node'),
3159 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
3160 target => get_standard_option('pve-node', {
3161 description => "Target node.",
3162 completion => \&PVE::Cluster::complete_migration_target,
3163 optional => 1,
3164 }),
3165 },
3166 },
3167 returns => {
3168 type => "object",
3169 properties => {
3170 running => { type => 'boolean' },
3171 allowed_nodes => {
3172 type => 'array',
3173 optional => 1,
f25852c2
TM
3174 description => "List nodes allowed for offline migration, only passed if VM is offline"
3175 },
3176 not_allowed_nodes => {
3177 type => 'object',
3178 optional => 1,
3179 description => "List not allowed nodes with additional informations, only passed if VM is offline"
71fc647f
TM
3180 },
3181 local_disks => {
3182 type => 'array',
3183 description => "List local disks including CD-Rom, unsused and not referenced disks"
3184 },
3185 local_resources => {
3186 type => 'array',
3187 description => "List local resources e.g. pci, usb"
3188 }
3189 },
3190 },
3191 code => sub {
3192 my ($param) = @_;
3193
3194 my $rpcenv = PVE::RPCEnvironment::get();
3195
3196 my $authuser = $rpcenv->get_user();
3197
3198 PVE::Cluster::check_cfs_quorum();
3199
3200 my $res = {};
3201
3202 my $vmid = extract_param($param, 'vmid');
3203 my $target = extract_param($param, 'target');
3204 my $localnode = PVE::INotify::nodename();
3205
3206
3207 # test if VM exists
3208 my $vmconf = PVE::QemuConfig->load_config($vmid);
3209 my $storecfg = PVE::Storage::config();
3210
3211
3212 # try to detect errors early
3213 PVE::QemuConfig->check_lock($vmconf);
3214
3215 $res->{running} = PVE::QemuServer::check_running($vmid) ? 1:0;
3216
3217 # if vm is not running, return target nodes where local storage is available
3218 # for offline migration
3219 if (!$res->{running}) {
f25852c2
TM
3220 $res->{allowed_nodes} = [];
3221 my $checked_nodes = PVE::QemuServer::check_local_storage_availability($vmconf, $storecfg);
32075a2c 3222 delete $checked_nodes->{$localnode};
f25852c2 3223
f25852c2 3224 foreach my $node (keys %$checked_nodes) {
32075a2c 3225 if (!defined $checked_nodes->{$node}->{unavailable_storages}) {
f25852c2 3226 push @{$res->{allowed_nodes}}, $node;
f25852c2 3227 }
71fc647f 3228
f25852c2
TM
3229 }
3230 $res->{not_allowed_nodes} = $checked_nodes;
71fc647f
TM
3231 }
3232
3233
3234 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3235 $res->{local_disks} = [ values %$local_disks ];;
3236
3237 my $local_resources = PVE::QemuServer::check_local_resources($vmconf, 1);
3238
3239 $res->{local_resources} = $local_resources;
3240
3241 return $res;
3242
3243
3244 }});
3245
3ea94c60 3246__PACKAGE__->register_method({
afdb31d5 3247 name => 'migrate_vm',
3ea94c60
DM
3248 path => '{vmid}/migrate',
3249 method => 'POST',
3250 protected => 1,
3251 proxyto => 'node',
3252 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
3253 permissions => {
3254 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3255 },
3ea94c60
DM
3256 parameters => {
3257 additionalProperties => 0,
3258 properties => {
3259 node => get_standard_option('pve-node'),
335af808 3260 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
c2ed338e 3261 target => get_standard_option('pve-node', {
335af808
DM
3262 description => "Target node.",
3263 completion => \&PVE::Cluster::complete_migration_target,
3264 }),
3ea94c60
DM
3265 online => {
3266 type => 'boolean',
13739386 3267 description => "Use online/live migration if VM is running. Ignored if VM is stopped.",
3ea94c60
DM
3268 optional => 1,
3269 },
3270 force => {
3271 type => 'boolean',
3272 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
3273 optional => 1,
3274 },
2de2d6f7
TL
3275 migration_type => {
3276 type => 'string',
3277 enum => ['secure', 'insecure'],
c07a9e3d 3278 description => "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2de2d6f7
TL
3279 optional => 1,
3280 },
3281 migration_network => {
c07a9e3d 3282 type => 'string', format => 'CIDR',
2de2d6f7
TL
3283 description => "CIDR of the (sub) network that is used for migration.",
3284 optional => 1,
3285 },
56af7146
AD
3286 "with-local-disks" => {
3287 type => 'boolean',
3288 description => "Enable live storage migration for local disk",
b74cad8a 3289 optional => 1,
56af7146
AD
3290 },
3291 targetstorage => get_standard_option('pve-storage-id', {
3292 description => "Default target storage.",
3293 optional => 1,
3294 completion => \&PVE::QemuServer::complete_storage,
3295 }),
0aab5a16
SI
3296 bwlimit => {
3297 description => "Override I/O bandwidth limit (in KiB/s).",
3298 optional => 1,
3299 type => 'integer',
3300 minimum => '0',
41756a3b 3301 default => 'migrate limit from datacenter or storage config',
0aab5a16 3302 },
3ea94c60
DM
3303 },
3304 },
afdb31d5 3305 returns => {
3ea94c60
DM
3306 type => 'string',
3307 description => "the task ID.",
3308 },
3309 code => sub {
3310 my ($param) = @_;
3311
3312 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 3313 my $authuser = $rpcenv->get_user();
3ea94c60
DM
3314
3315 my $target = extract_param($param, 'target');
3316
3317 my $localnode = PVE::INotify::nodename();
3318 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
3319
3320 PVE::Cluster::check_cfs_quorum();
3321
3322 PVE::Cluster::check_node_exists($target);
3323
3324 my $targetip = PVE::Cluster::remote_node_ip($target);
3325
3326 my $vmid = extract_param($param, 'vmid');
3327
afdb31d5 3328 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 3329 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 3330
2de2d6f7
TL
3331 raise_param_exc({ migration_type => "Only root may use this option." })
3332 if $param->{migration_type} && $authuser ne 'root@pam';
3333
3334 # allow root only until better network permissions are available
3335 raise_param_exc({ migration_network => "Only root may use this option." })
3336 if $param->{migration_network} && $authuser ne 'root@pam';
3337
3ea94c60 3338 # test if VM exists
ffda963f 3339 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
3340
3341 # try to detect errors early
a5ed42d3 3342
ffda963f 3343 PVE::QemuConfig->check_lock($conf);
a5ed42d3 3344
3ea94c60 3345 if (PVE::QemuServer::check_running($vmid)) {
fda72913 3346 die "can't migrate running VM without --online\n" if !$param->{online};
13739386 3347 } else {
fda72913 3348 warn "VM isn't running. Doing offline migration instead\n." if $param->{online};
13739386 3349 $param->{online} = 0;
3ea94c60
DM
3350 }
3351
13739386
FE
3352 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
3353 if !$param->{online} && $param->{targetstorage};
3354
47152e2e 3355 my $storecfg = PVE::Storage::config();
d80ad67f
AD
3356
3357 if( $param->{targetstorage}) {
3358 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
3359 } else {
3360 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
3361 }
47152e2e 3362
2003f0f8 3363 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 3364
88fc87b4
DM
3365 my $hacmd = sub {
3366 my $upid = shift;
3ea94c60 3367
02765844 3368 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4 3369
a4262553 3370 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
88fc87b4 3371 PVE::Tools::run_command($cmd);
88fc87b4
DM
3372 return;
3373 };
3374
3375 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3376
3377 } else {
3378
f53c6ad8 3379 my $realcmd = sub {
f53c6ad8
DM
3380 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3381 };
88fc87b4 3382
f53c6ad8
DM
3383 my $worker = sub {
3384 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
3385 };
3386
f53c6ad8 3387 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 3388 }
3ea94c60 3389
3ea94c60 3390 }});
1e3baf05 3391
91c94f0a 3392__PACKAGE__->register_method({
afdb31d5
DM
3393 name => 'monitor',
3394 path => '{vmid}/monitor',
91c94f0a
DM
3395 method => 'POST',
3396 protected => 1,
3397 proxyto => 'node',
3398 description => "Execute Qemu monitor commands.",
a0d1b1a2 3399 permissions => {
a8f2f427 3400 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 3401 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 3402 },
91c94f0a
DM
3403 parameters => {
3404 additionalProperties => 0,
3405 properties => {
3406 node => get_standard_option('pve-node'),
3407 vmid => get_standard_option('pve-vmid'),
3408 command => {
3409 type => 'string',
3410 description => "The monitor command.",
3411 }
3412 },
3413 },
3414 returns => { type => 'string'},
3415 code => sub {
3416 my ($param) = @_;
3417
a8f2f427
FG
3418 my $rpcenv = PVE::RPCEnvironment::get();
3419 my $authuser = $rpcenv->get_user();
3420
3421 my $is_ro = sub {
3422 my $command = shift;
3423 return $command =~ m/^\s*info(\s+|$)/
3424 || $command =~ m/^\s*help\s*$/;
3425 };
3426
3427 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3428 if !&$is_ro($param->{command});
3429
91c94f0a
DM
3430 my $vmid = $param->{vmid};
3431
ffda963f 3432 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
3433
3434 my $res = '';
3435 eval {
7b7c6d1b 3436 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
3437 };
3438 $res = "ERROR: $@" if $@;
3439
3440 return $res;
3441 }});
3442
0d02881c
AD
3443__PACKAGE__->register_method({
3444 name => 'resize_vm',
614e3941 3445 path => '{vmid}/resize',
0d02881c
AD
3446 method => 'PUT',
3447 protected => 1,
3448 proxyto => 'node',
2f48a4f5 3449 description => "Extend volume size.",
0d02881c 3450 permissions => {
3b2773f6 3451 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3452 },
3453 parameters => {
3454 additionalProperties => 0,
2f48a4f5
DM
3455 properties => {
3456 node => get_standard_option('pve-node'),
335af808 3457 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3458 skiplock => get_standard_option('skiplock'),
3459 disk => {
3460 type => 'string',
3461 description => "The disk you want to resize.",
74479ee9 3462 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3463 },
3464 size => {
3465 type => 'string',
f91b2e45 3466 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3467 description => "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
2f48a4f5
DM
3468 },
3469 digest => {
3470 type => 'string',
3471 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3472 maxLength => 40,
3473 optional => 1,
3474 },
3475 },
0d02881c
AD
3476 },
3477 returns => { type => 'null'},
3478 code => sub {
3479 my ($param) = @_;
3480
3481 my $rpcenv = PVE::RPCEnvironment::get();
3482
3483 my $authuser = $rpcenv->get_user();
3484
3485 my $node = extract_param($param, 'node');
3486
3487 my $vmid = extract_param($param, 'vmid');
3488
3489 my $digest = extract_param($param, 'digest');
3490
2f48a4f5 3491 my $disk = extract_param($param, 'disk');
75466c4f 3492
2f48a4f5 3493 my $sizestr = extract_param($param, 'size');
0d02881c 3494
f91b2e45 3495 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3496 raise_param_exc({ skiplock => "Only root may use this option." })
3497 if $skiplock && $authuser ne 'root@pam';
3498
0d02881c
AD
3499 my $storecfg = PVE::Storage::config();
3500
0d02881c
AD
3501 my $updatefn = sub {
3502
ffda963f 3503 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3504
3505 die "checksum missmatch (file change by other user?)\n"
3506 if $digest && $digest ne $conf->{digest};
ffda963f 3507 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3508
f91b2e45
DM
3509 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3510
3511 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3512
d662790a
WL
3513 my (undef, undef, undef, undef, undef, undef, $format) =
3514 PVE::Storage::parse_volname($storecfg, $drive->{file});
3515
c2ed338e 3516 die "can't resize volume: $disk if snapshot exists\n"
d662790a
WL
3517 if %{$conf->{snapshots}} && $format eq 'qcow2';
3518
f91b2e45
DM
3519 my $volid = $drive->{file};
3520
3521 die "disk '$disk' has no associated volume\n" if !$volid;
3522
3523 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3524
3525 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3526
3527 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3528
b572a606 3529 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3530 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3531
ed94b2ad 3532 die "Could not determine current size of volume '$volid'\n" if !defined($size);
f8b829aa 3533
f91b2e45
DM
3534 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3535 my ($ext, $newsize, $unit) = ($1, $2, $4);
3536 if ($unit) {
3537 if ($unit eq 'K') {
3538 $newsize = $newsize * 1024;
3539 } elsif ($unit eq 'M') {
3540 $newsize = $newsize * 1024 * 1024;
3541 } elsif ($unit eq 'G') {
3542 $newsize = $newsize * 1024 * 1024 * 1024;
3543 } elsif ($unit eq 'T') {
3544 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3545 }
3546 }
3547 $newsize += $size if $ext;
3548 $newsize = int($newsize);
3549
9a478b17 3550 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3551
3552 return if $size == $newsize;
3553
2f48a4f5 3554 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3555
f91b2e45 3556 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3557
f91b2e45
DM
3558 $drive->{size} = $newsize;
3559 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3560
ffda963f 3561 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3562 };
0d02881c 3563
ffda963f 3564 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3565 return undef;
3566 }});
3567
9dbd1ee4 3568__PACKAGE__->register_method({
7e7d7b61 3569 name => 'snapshot_list',
9dbd1ee4 3570 path => '{vmid}/snapshot',
7e7d7b61
DM
3571 method => 'GET',
3572 description => "List all snapshots.",
3573 permissions => {
3574 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3575 },
3576 proxyto => 'node',
3577 protected => 1, # qemu pid files are only readable by root
3578 parameters => {
3579 additionalProperties => 0,
3580 properties => {
e261de40 3581 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3582 node => get_standard_option('pve-node'),
3583 },
3584 },
3585 returns => {
3586 type => 'array',
3587 items => {
3588 type => "object",
ce9b0a38
DM
3589 properties => {
3590 name => {
3591 description => "Snapshot identifier. Value 'current' identifies the current VM.",
3592 type => 'string',
3593 },
3594 vmstate => {
3595 description => "Snapshot includes RAM.",
3596 type => 'boolean',
3597 optional => 1,
3598 },
3599 description => {
3600 description => "Snapshot description.",
3601 type => 'string',
3602 },
3603 snaptime => {
3604 description => "Snapshot creation time",
3605 type => 'integer',
3606 renderer => 'timestamp',
3607 optional => 1,
3608 },
3609 parent => {
3610 description => "Parent snapshot identifier.",
3611 type => 'string',
3612 optional => 1,
3613 },
3614 },
7e7d7b61
DM
3615 },
3616 links => [ { rel => 'child', href => "{name}" } ],
3617 },
3618 code => sub {
3619 my ($param) = @_;
3620
6aa4651b
DM
3621 my $vmid = $param->{vmid};
3622
ffda963f 3623 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3624 my $snaphash = $conf->{snapshots} || {};
3625
3626 my $res = [];
3627
3628 foreach my $name (keys %$snaphash) {
0ea6bc69 3629 my $d = $snaphash->{$name};
75466c4f
DM
3630 my $item = {
3631 name => $name,
3632 snaptime => $d->{snaptime} || 0,
6aa4651b 3633 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3634 description => $d->{description} || '',
3635 };
0ea6bc69 3636 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3637 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3638 push @$res, $item;
3639 }
3640
6aa4651b 3641 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
ce9b0a38
DM
3642 my $current = {
3643 name => 'current',
3644 digest => $conf->{digest},
3645 running => $running,
3646 description => "You are here!",
3647 };
d1914468
DM
3648 $current->{parent} = $conf->{parent} if $conf->{parent};
3649
3650 push @$res, $current;
7e7d7b61
DM
3651
3652 return $res;
3653 }});
3654
3655__PACKAGE__->register_method({
3656 name => 'snapshot',
3657 path => '{vmid}/snapshot',
3658 method => 'POST',
9dbd1ee4
AD
3659 protected => 1,
3660 proxyto => 'node',
3661 description => "Snapshot a VM.",
3662 permissions => {
f1baf1df 3663 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3664 },
3665 parameters => {
3666 additionalProperties => 0,
3667 properties => {
3668 node => get_standard_option('pve-node'),
335af808 3669 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3670 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3671 vmstate => {
3672 optional => 1,
3673 type => 'boolean',
3674 description => "Save the vmstate",
3675 },
782f4f75
DM
3676 description => {
3677 optional => 1,
3678 type => 'string',
3679 description => "A textual description or comment.",
3680 },
9dbd1ee4
AD
3681 },
3682 },
7e7d7b61
DM
3683 returns => {
3684 type => 'string',
3685 description => "the task ID.",
3686 },
9dbd1ee4
AD
3687 code => sub {
3688 my ($param) = @_;
3689
3690 my $rpcenv = PVE::RPCEnvironment::get();
3691
3692 my $authuser = $rpcenv->get_user();
3693
3694 my $node = extract_param($param, 'node');
3695
3696 my $vmid = extract_param($param, 'vmid');
3697
9dbd1ee4
AD
3698 my $snapname = extract_param($param, 'snapname');
3699
d1914468
DM
3700 die "unable to use snapshot name 'current' (reserved name)\n"
3701 if $snapname eq 'current';
3702
7e7d7b61 3703 my $realcmd = sub {
22c377f0 3704 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
c2ed338e 3705 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3706 $param->{description});
7e7d7b61
DM
3707 };
3708
3709 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3710 }});
3711
154ccdcd
DM
3712__PACKAGE__->register_method({
3713 name => 'snapshot_cmd_idx',
3714 path => '{vmid}/snapshot/{snapname}',
3715 description => '',
3716 method => 'GET',
3717 permissions => {
3718 user => 'all',
3719 },
3720 parameters => {
3721 additionalProperties => 0,
3722 properties => {
3723 vmid => get_standard_option('pve-vmid'),
3724 node => get_standard_option('pve-node'),
8abd398b 3725 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3726 },
3727 },
3728 returns => {
3729 type => 'array',
3730 items => {
3731 type => "object",
3732 properties => {},
3733 },
3734 links => [ { rel => 'child', href => "{cmd}" } ],
3735 },
3736 code => sub {
3737 my ($param) = @_;
3738
3739 my $res = [];
3740
3741 push @$res, { cmd => 'rollback' };
d788cea6 3742 push @$res, { cmd => 'config' };
154ccdcd
DM
3743
3744 return $res;
3745 }});
3746
d788cea6
DM
3747__PACKAGE__->register_method({
3748 name => 'update_snapshot_config',
3749 path => '{vmid}/snapshot/{snapname}/config',
3750 method => 'PUT',
3751 protected => 1,
3752 proxyto => 'node',
3753 description => "Update snapshot metadata.",
3754 permissions => {
3755 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3756 },
3757 parameters => {
3758 additionalProperties => 0,
3759 properties => {
3760 node => get_standard_option('pve-node'),
3761 vmid => get_standard_option('pve-vmid'),
3762 snapname => get_standard_option('pve-snapshot-name'),
3763 description => {
3764 optional => 1,
3765 type => 'string',
3766 description => "A textual description or comment.",
3767 },
3768 },
3769 },
3770 returns => { type => 'null' },
3771 code => sub {
3772 my ($param) = @_;
3773
3774 my $rpcenv = PVE::RPCEnvironment::get();
3775
3776 my $authuser = $rpcenv->get_user();
3777
3778 my $vmid = extract_param($param, 'vmid');
3779
3780 my $snapname = extract_param($param, 'snapname');
3781
3782 return undef if !defined($param->{description});
3783
3784 my $updatefn = sub {
3785
ffda963f 3786 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3787
ffda963f 3788 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3789
3790 my $snap = $conf->{snapshots}->{$snapname};
3791
75466c4f
DM
3792 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3793
d788cea6
DM
3794 $snap->{description} = $param->{description} if defined($param->{description});
3795
ffda963f 3796 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3797 };
3798
ffda963f 3799 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3800
3801 return undef;
3802 }});
3803
3804__PACKAGE__->register_method({
3805 name => 'get_snapshot_config',
3806 path => '{vmid}/snapshot/{snapname}/config',
3807 method => 'GET',
3808 proxyto => 'node',
3809 description => "Get snapshot configuration",
3810 permissions => {
c268337d 3811 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3812 },
3813 parameters => {
3814 additionalProperties => 0,
3815 properties => {
3816 node => get_standard_option('pve-node'),
3817 vmid => get_standard_option('pve-vmid'),
3818 snapname => get_standard_option('pve-snapshot-name'),
3819 },
3820 },
3821 returns => { type => "object" },
3822 code => sub {
3823 my ($param) = @_;
3824
3825 my $rpcenv = PVE::RPCEnvironment::get();
3826
3827 my $authuser = $rpcenv->get_user();
3828
3829 my $vmid = extract_param($param, 'vmid');
3830
3831 my $snapname = extract_param($param, 'snapname');
3832
ffda963f 3833 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3834
3835 my $snap = $conf->{snapshots}->{$snapname};
3836
75466c4f
DM
3837 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3838
d788cea6
DM
3839 return $snap;
3840 }});
3841
7e7d7b61
DM
3842__PACKAGE__->register_method({
3843 name => 'rollback',
154ccdcd 3844 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3845 method => 'POST',
3846 protected => 1,
3847 proxyto => 'node',
3848 description => "Rollback VM state to specified snapshot.",
3849 permissions => {
c268337d 3850 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3851 },
3852 parameters => {
3853 additionalProperties => 0,
3854 properties => {
3855 node => get_standard_option('pve-node'),
335af808 3856 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3857 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3858 },
3859 },
3860 returns => {
3861 type => 'string',
3862 description => "the task ID.",
3863 },
3864 code => sub {
3865 my ($param) = @_;
3866
3867 my $rpcenv = PVE::RPCEnvironment::get();
3868
3869 my $authuser = $rpcenv->get_user();
3870
3871 my $node = extract_param($param, 'node');
3872
3873 my $vmid = extract_param($param, 'vmid');
3874
3875 my $snapname = extract_param($param, 'snapname');
3876
7e7d7b61 3877 my $realcmd = sub {
22c377f0 3878 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3879 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3880 };
3881
c068c1c3
WL
3882 my $worker = sub {
3883 # hold migration lock, this makes sure that nobody create replication snapshots
3884 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3885 };
3886
3887 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3888 }});
3889
3890__PACKAGE__->register_method({
3891 name => 'delsnapshot',
3892 path => '{vmid}/snapshot/{snapname}',
3893 method => 'DELETE',
3894 protected => 1,
3895 proxyto => 'node',
3896 description => "Delete a VM snapshot.",
3897 permissions => {
f1baf1df 3898 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3899 },
3900 parameters => {
3901 additionalProperties => 0,
3902 properties => {
3903 node => get_standard_option('pve-node'),
335af808 3904 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3905 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3906 force => {
3907 optional => 1,
3908 type => 'boolean',
3909 description => "For removal from config file, even if removing disk snapshots fails.",
3910 },
7e7d7b61
DM
3911 },
3912 },
3913 returns => {
3914 type => 'string',
3915 description => "the task ID.",
3916 },
3917 code => sub {
3918 my ($param) = @_;
3919
3920 my $rpcenv = PVE::RPCEnvironment::get();
3921
3922 my $authuser = $rpcenv->get_user();
3923
3924 my $node = extract_param($param, 'node');
3925
3926 my $vmid = extract_param($param, 'vmid');
3927
3928 my $snapname = extract_param($param, 'snapname');
3929
7e7d7b61 3930 my $realcmd = sub {
22c377f0 3931 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3932 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3933 };
9dbd1ee4 3934
7b2257a8 3935 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3936 }});
3937
04a69bb4
AD
3938__PACKAGE__->register_method({
3939 name => 'template',
3940 path => '{vmid}/template',
3941 method => 'POST',
3942 protected => 1,
3943 proxyto => 'node',
3944 description => "Create a Template.",
b02691d8 3945 permissions => {
7af0a6c8
DM
3946 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3947 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3948 },
04a69bb4
AD
3949 parameters => {
3950 additionalProperties => 0,
3951 properties => {
3952 node => get_standard_option('pve-node'),
335af808 3953 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3954 disk => {
3955 optional => 1,
3956 type => 'string',
3957 description => "If you want to convert only 1 disk to base image.",
74479ee9 3958 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3959 },
3960
3961 },
3962 },
3963 returns => { type => 'null'},
3964 code => sub {
3965 my ($param) = @_;
3966
3967 my $rpcenv = PVE::RPCEnvironment::get();
3968
3969 my $authuser = $rpcenv->get_user();
3970
3971 my $node = extract_param($param, 'node');
3972
3973 my $vmid = extract_param($param, 'vmid');
3974
3975 my $disk = extract_param($param, 'disk');
3976
3977 my $updatefn = sub {
3978
ffda963f 3979 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3980
ffda963f 3981 PVE::QemuConfig->check_lock($conf);
04a69bb4 3982
75466c4f 3983 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3984 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3985
75466c4f 3986 die "you can't convert a template to a template\n"
ffda963f 3987 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3988
75466c4f 3989 die "you can't convert a VM to template if VM is running\n"
218cab9a 3990 if PVE::QemuServer::check_running($vmid);
35c5fdef 3991
04a69bb4
AD
3992 my $realcmd = sub {
3993 PVE::QemuServer::template_create($vmid, $conf, $disk);
3994 };
04a69bb4 3995
75e7e997 3996 $conf->{template} = 1;
ffda963f 3997 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3998
3999 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
4000 };
4001
ffda963f 4002 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
4003 return undef;
4004 }});
4005
73709749
ML
4006__PACKAGE__->register_method({
4007 name => 'cloudinit_generated_config_dump',
4008 path => '{vmid}/cloudinit/dump',
4009 method => 'GET',
4010 proxyto => 'node',
4011 description => "Get automatically generated cloudinit config.",
4012 permissions => {
4013 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4014 },
4015 parameters => {
4016 additionalProperties => 0,
4017 properties => {
4018 node => get_standard_option('pve-node'),
4019 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
4020 type => {
4021 description => 'Config type.',
4022 type => 'string',
4023 enum => ['user', 'network', 'meta'],
4024 },
4025 },
4026 },
4027 returns => {
4028 type => 'string',
4029 },
4030 code => sub {
4031 my ($param) = @_;
4032
4033 my $conf = PVE::QemuConfig->load_config($param->{vmid});
4034
4035 return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type});
4036 }});
4037
1e3baf05 40381;