]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
test: compare also with our qemu cfgs for pci addr conflicts
[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
ffda963f 868 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 869
87d92707 870 if (my $snapname = $param->{snapshot}) {
b14477e7 871 my $snapshot = $conf->{snapshots}->{$snapname};
87d92707
TL
872 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
873
874 $snapshot->{digest} = $conf->{digest}; # keep file digest for API
b14477e7 875
b14477e7
RV
876 $conf = $snapshot;
877 }
878
22c377f0 879 delete $conf->{snapshots};
6d89b548
DM
880
881 if (!$param->{current}) {
025e1d90 882 foreach my $opt (keys %{$conf->{pending}}) {
6d89b548
DM
883 next if $opt eq 'delete';
884 my $value = $conf->{pending}->{$opt};
885 next if ref($value); # just to be sure
886 $conf->{$opt} = $value;
887 }
1bc483f6
WB
888 my $pending_delete_hash = PVE::QemuServer::split_flagged_list($conf->{pending}->{delete});
889 foreach my $opt (keys %$pending_delete_hash) {
6d89b548
DM
890 delete $conf->{$opt} if $conf->{$opt};
891 }
892 }
893
1e7f2726 894 delete $conf->{pending};
22c377f0 895
2254ffcf
DC
896 # hide cloudinit password
897 if ($conf->{cipassword}) {
898 $conf->{cipassword} = '**********';
899 }
900
1e3baf05
DM
901 return $conf;
902 }});
903
1e7f2726
DM
904__PACKAGE__->register_method({
905 name => 'vm_pending',
906 path => '{vmid}/pending',
907 method => 'GET',
908 proxyto => 'node',
909 description => "Get virtual machine configuration, including pending changes.",
910 permissions => {
911 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
912 },
913 parameters => {
914 additionalProperties => 0,
915 properties => {
916 node => get_standard_option('pve-node'),
335af808 917 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e7f2726
DM
918 },
919 },
920 returns => {
921 type => "array",
922 items => {
923 type => "object",
924 properties => {
925 key => {
926 description => "Configuration option name.",
927 type => 'string',
928 },
929 value => {
930 description => "Current value.",
931 type => 'string',
932 optional => 1,
933 },
934 pending => {
935 description => "Pending value.",
936 type => 'string',
937 optional => 1,
938 },
939 delete => {
1bc483f6
WB
940 description => "Indicates a pending delete request if present and not 0. " .
941 "The value 2 indicates a force-delete request.",
942 type => 'integer',
943 minimum => 0,
944 maximum => 2,
1e7f2726
DM
945 optional => 1,
946 },
947 },
948 },
949 },
950 code => sub {
951 my ($param) = @_;
952
ffda963f 953 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e7f2726 954
1bc483f6 955 my $pending_delete_hash = PVE::QemuServer::split_flagged_list($conf->{pending}->{delete});
1e7f2726
DM
956
957 my $res = [];
958
025e1d90 959 foreach my $opt (keys %$conf) {
1e7f2726
DM
960 next if ref($conf->{$opt});
961 my $item = { key => $opt };
962 $item->{value} = $conf->{$opt} if defined($conf->{$opt});
963 $item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt});
1bc483f6 964 $item->{delete} = ($pending_delete_hash->{$opt} ? 2 : 1) if exists $pending_delete_hash->{$opt};
2254ffcf
DC
965
966 # hide cloudinit password
967 if ($opt eq 'cipassword') {
968 $item->{value} = '**********' if defined($item->{value});
969 # the trailing space so that the pending string is different
970 $item->{pending} = '********** ' if defined($item->{pending});
971 }
1e7f2726
DM
972 push @$res, $item;
973 }
974
025e1d90 975 foreach my $opt (keys %{$conf->{pending}}) {
1e7f2726
DM
976 next if $opt eq 'delete';
977 next if ref($conf->{pending}->{$opt}); # just to be sure
0e54e1c8 978 next if defined($conf->{$opt});
1e7f2726
DM
979 my $item = { key => $opt };
980 $item->{pending} = $conf->{pending}->{$opt};
2254ffcf
DC
981
982 # hide cloudinit password
983 if ($opt eq 'cipassword') {
984 $item->{pending} = '**********' if defined($item->{pending});
985 }
1e7f2726
DM
986 push @$res, $item;
987 }
988
1bc483f6 989 while (my ($opt, $force) = each %$pending_delete_hash) {
1e7f2726
DM
990 next if $conf->{pending}->{$opt}; # just to be sure
991 next if $conf->{$opt};
1bc483f6 992 my $item = { key => $opt, delete => ($force ? 2 : 1)};
1e7f2726
DM
993 push @$res, $item;
994 }
995
996 return $res;
997 }});
998
5555edea
DM
999# POST/PUT {vmid}/config implementation
1000#
1001# The original API used PUT (idempotent) an we assumed that all operations
1002# are fast. But it turned out that almost any configuration change can
1003# involve hot-plug actions, or disk alloc/free. Such actions can take long
1004# time to complete and have side effects (not idempotent).
1005#
7043d946 1006# The new implementation uses POST and forks a worker process. We added
5555edea 1007# a new option 'background_delay'. If specified we wait up to
7043d946 1008# 'background_delay' second for the worker task to complete. It returns null
5555edea 1009# if the task is finished within that time, else we return the UPID.
7043d946 1010
5555edea
DM
1011my $update_vm_api = sub {
1012 my ($param, $sync) = @_;
a0d1b1a2 1013
5555edea 1014 my $rpcenv = PVE::RPCEnvironment::get();
1e3baf05 1015
5555edea 1016 my $authuser = $rpcenv->get_user();
1e3baf05 1017
5555edea 1018 my $node = extract_param($param, 'node');
1e3baf05 1019
5555edea 1020 my $vmid = extract_param($param, 'vmid');
1e3baf05 1021
5555edea 1022 my $digest = extract_param($param, 'digest');
1e3baf05 1023
5555edea 1024 my $background_delay = extract_param($param, 'background_delay');
1e3baf05 1025
cefb41fa
WB
1026 if (defined(my $cipassword = $param->{cipassword})) {
1027 # Same logic as in cloud-init (but with the regex fixed...)
1028 $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
1029 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1030 }
0c9a7596 1031
5555edea 1032 my @paramarr = (); # used for log message
edd48c32 1033 foreach my $key (sort keys %$param) {
cefb41fa
WB
1034 my $value = $key eq 'cipassword' ? '<hidden>' : $param->{$key};
1035 push @paramarr, "-$key", $value;
5555edea 1036 }
0532bc63 1037
5555edea
DM
1038 my $skiplock = extract_param($param, 'skiplock');
1039 raise_param_exc({ skiplock => "Only root may use this option." })
1040 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1041
5555edea 1042 my $delete_str = extract_param($param, 'delete');
0532bc63 1043
d3df8cf3
DM
1044 my $revert_str = extract_param($param, 'revert');
1045
5555edea 1046 my $force = extract_param($param, 'force');
1e68cb19 1047
0c9a7596 1048 if (defined(my $ssh_keys = $param->{sshkeys})) {
231f824b
WB
1049 $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
1050 PVE::Tools::validate_ssh_public_keys($ssh_keys);
0c9a7596
AD
1051 }
1052
d3df8cf3 1053 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
7bfdeb5f 1054
5555edea 1055 my $storecfg = PVE::Storage::config();
1e68cb19 1056
5555edea 1057 my $defaults = PVE::QemuServer::load_defaults();
1e68cb19 1058
5555edea 1059 &$resolve_cdrom_alias($param);
0532bc63 1060
5555edea 1061 # now try to verify all parameters
ae57f6b3 1062
d3df8cf3
DM
1063 my $revert = {};
1064 foreach my $opt (PVE::Tools::split_list($revert_str)) {
1065 if (!PVE::QemuServer::option_exists($opt)) {
1066 raise_param_exc({ revert => "unknown option '$opt'" });
1067 }
1068
1069 raise_param_exc({ delete => "you can't use '-$opt' and " .
1070 "-revert $opt' at the same time" })
1071 if defined($param->{$opt});
1072
1073 $revert->{$opt} = 1;
1074 }
1075
5555edea
DM
1076 my @delete = ();
1077 foreach my $opt (PVE::Tools::split_list($delete_str)) {
1078 $opt = 'ide2' if $opt eq 'cdrom';
d3df8cf3 1079
5555edea
DM
1080 raise_param_exc({ delete => "you can't use '-$opt' and " .
1081 "-delete $opt' at the same time" })
1082 if defined($param->{$opt});
7043d946 1083
d3df8cf3
DM
1084 raise_param_exc({ revert => "you can't use '-delete $opt' and " .
1085 "-revert $opt' at the same time" })
1086 if $revert->{$opt};
1087
5555edea
DM
1088 if (!PVE::QemuServer::option_exists($opt)) {
1089 raise_param_exc({ delete => "unknown option '$opt'" });
0532bc63 1090 }
1e3baf05 1091
5555edea
DM
1092 push @delete, $opt;
1093 }
1094
17677004
WB
1095 my $repl_conf = PVE::ReplicationConfig->new();
1096 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1097 my $check_replication = sub {
1098 my ($drive) = @_;
1099 return if !$is_replicated;
1100 my $volid = $drive->{file};
1101 return if !$volid || !($drive->{replicate}//1);
1102 return if PVE::QemuServer::drive_is_cdrom($drive);
1103 my ($storeid, $format);
1104 if ($volid =~ $NEW_DISK_RE) {
1105 $storeid = $2;
1106 $format = $drive->{format} || PVE::Storage::storage_default_format($storecfg, $storeid);
1107 } else {
1108 ($storeid, undef) = PVE::Storage::parse_volume_id($volid, 1);
1109 $format = (PVE::Storage::parse_volname($storecfg, $volid))[6];
1110 }
1111 return if PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
9b1396ed
WB
1112 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
1113 return if $scfg->{shared};
17677004
WB
1114 die "cannot add non-replicatable volume to a replicated VM\n";
1115 };
1116
5555edea 1117 foreach my $opt (keys %$param) {
74479ee9 1118 if (PVE::QemuServer::is_valid_drivename($opt)) {
5555edea
DM
1119 # cleanup drive path
1120 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
f9091201 1121 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
5555edea 1122 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
17677004 1123 $check_replication->($drive);
5555edea
DM
1124 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
1125 } elsif ($opt =~ m/^net(\d+)$/) {
1126 # add macaddr
1127 my $net = PVE::QemuServer::parse_net($param->{$opt});
1128 $param->{$opt} = PVE::QemuServer::print_net($net);
6ee499ff
DC
1129 } elsif ($opt eq 'vmgenid') {
1130 if ($param->{$opt} eq '1') {
1131 $param->{$opt} = PVE::QemuServer::generate_uuid();
1132 }
9e784b11
DC
1133 } elsif ($opt eq 'hookscript') {
1134 eval { PVE::GuestHelpers::check_hookscript($param->{$opt}, $storecfg); };
1135 raise_param_exc({ $opt => $@ }) if $@;
1e68cb19 1136 }
5555edea 1137 }
1e3baf05 1138
5555edea 1139 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
ae57f6b3 1140
e30f75c5 1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
ae57f6b3 1142
5555edea 1143 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1e3baf05 1144
5555edea 1145 my $updatefn = sub {
1e3baf05 1146
ffda963f 1147 my $conf = PVE::QemuConfig->load_config($vmid);
1e3baf05 1148
5555edea
DM
1149 die "checksum missmatch (file change by other user?)\n"
1150 if $digest && $digest ne $conf->{digest};
1151
ffda963f 1152 PVE::QemuConfig->check_lock($conf) if !$skiplock;
7043d946 1153
d3df8cf3
DM
1154 foreach my $opt (keys %$revert) {
1155 if (defined($conf->{$opt})) {
1156 $param->{$opt} = $conf->{$opt};
1157 } elsif (defined($conf->{pending}->{$opt})) {
1158 push @delete, $opt;
1159 }
1160 }
1161
5555edea 1162 if ($param->{memory} || defined($param->{balloon})) {
6ca8b698
DM
1163 my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
1164 my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
7043d946 1165
5555edea
DM
1166 die "balloon value too large (must be smaller than assigned memory)\n"
1167 if $balloon && $balloon > $maxmem;
1168 }
1e3baf05 1169
5555edea 1170 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1e3baf05 1171
5555edea 1172 my $worker = sub {
7bfdeb5f 1173
5555edea 1174 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
c2a64aa7 1175
202d1f45
DM
1176 # write updates to pending section
1177
3a11fadb
DM
1178 my $modified = {}; # record what $option we modify
1179
202d1f45 1180 foreach my $opt (@delete) {
3a11fadb 1181 $modified->{$opt} = 1;
ffda963f 1182 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
af6d2db4 1183 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
d2c6bf93
FG
1184 warn "cannot delete '$opt' - not set in current configuration!\n";
1185 $modified->{$opt} = 0;
1186 next;
1187 }
1188
202d1f45 1189 if ($opt =~ m/^unused/) {
202d1f45 1190 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
ffda963f 1191 PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'");
4d8d55f1 1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3dc38fbb
WB
1193 if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1194 delete $conf->{$opt};
ffda963f 1195 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1196 }
74479ee9 1197 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
ffda963f 1198 PVE::QemuConfig->check_protection($conf, "can't remove drive '$opt'");
202d1f45 1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
055d554d 1200 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45 1201 if defined($conf->{pending}->{$opt});
3dc38fbb 1202 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
ffda963f 1203 PVE::QemuConfig->write_config($vmid, $conf);
e30f75c5 1204 } elsif ($opt =~ m/^serial\d+$/) {
e5453043
DC
1205 if ($conf->{$opt} eq 'socket') {
1206 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1207 } elsif ($authuser ne 'root@pam') {
1208 die "only root can delete '$opt' config for real devices\n";
1209 }
1210 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
1211 PVE::QemuConfig->write_config($vmid, $conf);
165be267
DC
1212 } elsif ($opt =~ m/^usb\d+$/) {
1213 if ($conf->{$opt} =~ m/spice/) {
1214 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1215 } elsif ($authuser ne 'root@pam') {
1216 die "only root can delete '$opt' config for real devices\n";
1217 }
1218 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
1219 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1220 } else {
3dc38fbb 1221 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
ffda963f 1222 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1223 }
5d39a182 1224 }
1e3baf05 1225
202d1f45 1226 foreach my $opt (keys %$param) { # add/change
3a11fadb 1227 $modified->{$opt} = 1;
ffda963f 1228 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
202d1f45
DM
1229 next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
1230
96ed3574
WB
1231 my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
1232
74479ee9 1233 if (PVE::QemuServer::is_valid_drivename($opt)) {
202d1f45 1234 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
9ed7a77c 1235 # FIXME: cloudinit: CDROM or Disk?
202d1f45
DM
1236 if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
1237 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1238 } else {
1239 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1240 }
055d554d 1241 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45
DM
1242 if defined($conf->{pending}->{$opt});
1243
96ed3574 1244 &$create_disks($rpcenv, $authuser, $conf->{pending}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
e30f75c5
DC
1245 } elsif ($opt =~ m/^serial\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1250 }
1251 $conf->{pending}->{$opt} = $param->{$opt};
165be267
DC
1252 } elsif ($opt =~ m/^usb\d+/) {
1253 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1254 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1255 } elsif ($authuser ne 'root@pam') {
1256 die "only root can modify '$opt' config for real devices\n";
1257 }
1258 $conf->{pending}->{$opt} = $param->{$opt};
202d1f45
DM
1259 } else {
1260 $conf->{pending}->{$opt} = $param->{$opt};
1261 }
055d554d 1262 PVE::QemuServer::vmconfig_undelete_pending_option($conf, $opt);
ffda963f 1263 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45
DM
1264 }
1265
1266 # remove pending changes when nothing changed
ffda963f 1267 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
c750e90a 1268 my $changes = PVE::QemuServer::vmconfig_cleanup_pending($conf);
ffda963f 1269 PVE::QemuConfig->write_config($vmid, $conf) if $changes;
202d1f45
DM
1270
1271 return if !scalar(keys %{$conf->{pending}});
1272
7bfdeb5f 1273 my $running = PVE::QemuServer::check_running($vmid);
39001640
DM
1274
1275 # apply pending changes
1276
ffda963f 1277 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
39001640 1278
3a11fadb
DM
1279 if ($running) {
1280 my $errors = {};
1281 PVE::QemuServer::vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors);
1282 raise_param_exc($errors) if scalar(keys %$errors);
1283 } else {
1284 PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running);
1285 }
1e68cb19 1286
915d3481 1287 return;
5d39a182
DM
1288 };
1289
5555edea
DM
1290 if ($sync) {
1291 &$worker();
1292 return undef;
1293 } else {
1294 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
fcdb0117 1295
5555edea
DM
1296 if ($background_delay) {
1297
1298 # Note: It would be better to do that in the Event based HTTPServer
7043d946 1299 # to avoid blocking call to sleep.
5555edea
DM
1300
1301 my $end_time = time() + $background_delay;
1302
1303 my $task = PVE::Tools::upid_decode($upid);
1304
1305 my $running = 1;
1306 while (time() < $end_time) {
1307 $running = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
1308 last if !$running;
1309 sleep(1); # this gets interrupted when child process ends
1310 }
1311
1312 if (!$running) {
1313 my $status = PVE::Tools::upid_read_status($upid);
1314 return undef if $status eq 'OK';
1315 die $status;
1316 }
7043d946 1317 }
5555edea
DM
1318
1319 return $upid;
1320 }
1321 };
1322
ffda963f 1323 return PVE::QemuConfig->lock_config($vmid, $updatefn);
5555edea
DM
1324};
1325
1326my $vm_config_perm_list = [
1327 'VM.Config.Disk',
1328 'VM.Config.CDROM',
1329 'VM.Config.CPU',
1330 'VM.Config.Memory',
1331 'VM.Config.Network',
1332 'VM.Config.HWType',
1333 'VM.Config.Options',
1334 ];
1335
1336__PACKAGE__->register_method({
1337 name => 'update_vm_async',
1338 path => '{vmid}/config',
1339 method => 'POST',
1340 protected => 1,
1341 proxyto => 'node',
1342 description => "Set virtual machine options (asynchrounous API).",
1343 permissions => {
1344 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1345 },
1346 parameters => {
1347 additionalProperties => 0,
1348 properties => PVE::QemuServer::json_config_properties(
1349 {
1350 node => get_standard_option('pve-node'),
1351 vmid => get_standard_option('pve-vmid'),
1352 skiplock => get_standard_option('skiplock'),
1353 delete => {
1354 type => 'string', format => 'pve-configid-list',
1355 description => "A list of settings you want to delete.",
1356 optional => 1,
1357 },
4c8365fa
DM
1358 revert => {
1359 type => 'string', format => 'pve-configid-list',
1360 description => "Revert a pending change.",
1361 optional => 1,
1362 },
5555edea
DM
1363 force => {
1364 type => 'boolean',
1365 description => $opt_force_description,
1366 optional => 1,
1367 requires => 'delete',
1368 },
1369 digest => {
1370 type => 'string',
1371 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1372 maxLength => 40,
1373 optional => 1,
1374 },
1375 background_delay => {
1376 type => 'integer',
1377 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1378 minimum => 1,
1379 maximum => 30,
1380 optional => 1,
1381 },
1382 }),
1383 },
1384 returns => {
1385 type => 'string',
1386 optional => 1,
1387 },
1388 code => $update_vm_api,
1389});
1390
1391__PACKAGE__->register_method({
1392 name => 'update_vm',
1393 path => '{vmid}/config',
1394 method => 'PUT',
1395 protected => 1,
1396 proxyto => 'node',
1397 description => "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1398 permissions => {
1399 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1400 },
1401 parameters => {
1402 additionalProperties => 0,
1403 properties => PVE::QemuServer::json_config_properties(
1404 {
1405 node => get_standard_option('pve-node'),
335af808 1406 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
5555edea
DM
1407 skiplock => get_standard_option('skiplock'),
1408 delete => {
1409 type => 'string', format => 'pve-configid-list',
1410 description => "A list of settings you want to delete.",
1411 optional => 1,
1412 },
4c8365fa
DM
1413 revert => {
1414 type => 'string', format => 'pve-configid-list',
1415 description => "Revert a pending change.",
1416 optional => 1,
1417 },
5555edea
DM
1418 force => {
1419 type => 'boolean',
1420 description => $opt_force_description,
1421 optional => 1,
1422 requires => 'delete',
1423 },
1424 digest => {
1425 type => 'string',
1426 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1427 maxLength => 40,
1428 optional => 1,
1429 },
1430 }),
1431 },
1432 returns => { type => 'null' },
1433 code => sub {
1434 my ($param) = @_;
1435 &$update_vm_api($param, 1);
1e3baf05 1436 return undef;
5555edea
DM
1437 }
1438});
1e3baf05 1439
1e3baf05 1440__PACKAGE__->register_method({
afdb31d5
DM
1441 name => 'destroy_vm',
1442 path => '{vmid}',
1e3baf05
DM
1443 method => 'DELETE',
1444 protected => 1,
1445 proxyto => 'node',
1446 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
1447 permissions => {
1448 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1449 },
1e3baf05
DM
1450 parameters => {
1451 additionalProperties => 0,
1452 properties => {
1453 node => get_standard_option('pve-node'),
335af808 1454 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60 1455 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
1456 },
1457 },
afdb31d5 1458 returns => {
5fdbe4f0
DM
1459 type => 'string',
1460 },
1e3baf05
DM
1461 code => sub {
1462 my ($param) = @_;
1463
1464 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 1465 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1466 my $vmid = $param->{vmid};
1467
1468 my $skiplock = $param->{skiplock};
afdb31d5 1469 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1470 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1471
5fdbe4f0 1472 # test if VM exists
ffda963f 1473 my $conf = PVE::QemuConfig->load_config($vmid);
afdb31d5 1474 my $storecfg = PVE::Storage::config();
ffda963f 1475 PVE::QemuConfig->check_protection($conf, "can't remove VM $vmid");
952e3ac3
DM
1476 die "unable to remove VM $vmid - used in HA resources\n"
1477 if PVE::HA::Config::vm_is_ha_managed($vmid);
e9f2f8e5 1478
628bb7f2
DM
1479 # do not allow destroy if there are replication jobs
1480 my $repl_conf = PVE::ReplicationConfig->new();
1481 $repl_conf->check_for_existing_jobs($vmid);
1482
db593da2
DM
1483 # early tests (repeat after locking)
1484 die "VM $vmid is running - destroy failed\n"
1485 if PVE::QemuServer::check_running($vmid);
1486
5fdbe4f0 1487 my $realcmd = sub {
ff1a2432
DM
1488 my $upid = shift;
1489
1490 syslog('info', "destroy VM $vmid: $upid\n");
5fdbe4f0 1491 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
37f43805 1492 PVE::AccessControl::remove_vm_access($vmid);
e9abcde6 1493 PVE::Firewall::remove_vmfw_conf($vmid);
5fdbe4f0 1494 };
1e3baf05 1495
a0d1b1a2 1496 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
1497 }});
1498
1499__PACKAGE__->register_method({
afdb31d5
DM
1500 name => 'unlink',
1501 path => '{vmid}/unlink',
1e3baf05
DM
1502 method => 'PUT',
1503 protected => 1,
1504 proxyto => 'node',
1505 description => "Unlink/delete disk images.",
a0d1b1a2
DM
1506 permissions => {
1507 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1508 },
1e3baf05
DM
1509 parameters => {
1510 additionalProperties => 0,
1511 properties => {
1512 node => get_standard_option('pve-node'),
335af808 1513 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e3baf05
DM
1514 idlist => {
1515 type => 'string', format => 'pve-configid-list',
1516 description => "A list of disk IDs you want to delete.",
1517 },
1518 force => {
1519 type => 'boolean',
1520 description => $opt_force_description,
1521 optional => 1,
1522 },
1523 },
1524 },
1525 returns => { type => 'null'},
1526 code => sub {
1527 my ($param) = @_;
1528
1529 $param->{delete} = extract_param($param, 'idlist');
1530
1531 __PACKAGE__->update_vm($param);
1532
1533 return undef;
1534 }});
1535
1536my $sslcert;
1537
1538__PACKAGE__->register_method({
afdb31d5
DM
1539 name => 'vncproxy',
1540 path => '{vmid}/vncproxy',
1e3baf05
DM
1541 method => 'POST',
1542 protected => 1,
1543 permissions => {
378b359e 1544 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
1545 },
1546 description => "Creates a TCP VNC proxy connections.",
1547 parameters => {
1548 additionalProperties => 0,
1549 properties => {
1550 node => get_standard_option('pve-node'),
1551 vmid => get_standard_option('pve-vmid'),
b4d5c000
SP
1552 websocket => {
1553 optional => 1,
1554 type => 'boolean',
1555 description => "starts websockify instead of vncproxy",
1556 },
1e3baf05
DM
1557 },
1558 },
afdb31d5 1559 returns => {
1e3baf05
DM
1560 additionalProperties => 0,
1561 properties => {
1562 user => { type => 'string' },
1563 ticket => { type => 'string' },
1564 cert => { type => 'string' },
1565 port => { type => 'integer' },
1566 upid => { type => 'string' },
1567 },
1568 },
1569 code => sub {
1570 my ($param) = @_;
1571
1572 my $rpcenv = PVE::RPCEnvironment::get();
1573
a0d1b1a2 1574 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1575
1576 my $vmid = $param->{vmid};
1577 my $node = $param->{node};
983d4582 1578 my $websocket = $param->{websocket};
1e3baf05 1579
ffda963f 1580 my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
414b42d8 1581 my $use_serial = ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/));
ef5e2be2 1582
b6f39da2
DM
1583 my $authpath = "/vms/$vmid";
1584
a0d1b1a2 1585 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 1586
1e3baf05
DM
1587 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1588 if !$sslcert;
1589
414b42d8 1590 my $family;
ef5e2be2 1591 my $remcmd = [];
afdb31d5 1592
4f1be36c 1593 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
414b42d8
DC
1594 (undef, $family) = PVE::Cluster::remote_node_ip($node);
1595 my $sshinfo = PVE::Cluster::get_ssh_info($node);
b4d5c000 1596 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
8fef2bdb 1597 $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, $use_serial ? '-t' : '-T');
af0eba7e
WB
1598 } else {
1599 $family = PVE::Tools::get_host_address_family($node);
1e3baf05
DM
1600 }
1601
af0eba7e
WB
1602 my $port = PVE::Tools::next_vnc_port($family);
1603
afdb31d5 1604 my $timeout = 10;
1e3baf05
DM
1605
1606 my $realcmd = sub {
1607 my $upid = shift;
1608
1609 syslog('info', "starting vnc proxy $upid\n");
1610
ef5e2be2 1611 my $cmd;
1e3baf05 1612
414b42d8 1613 if ($use_serial) {
b4d5c000 1614
ccb88f45 1615 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga}, '-escape', '0' ];
9e6d6e97 1616
ef5e2be2 1617 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
fa8ea931 1618 '-timeout', $timeout, '-authpath', $authpath,
9e6d6e97
DC
1619 '-perm', 'Sys.Console'];
1620
1621 if ($param->{websocket}) {
1622 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
1623 push @$cmd, '-notls', '-listen', 'localhost';
1624 }
1625
1626 push @$cmd, '-c', @$remcmd, @$termcmd;
1627
655d7462 1628 PVE::Tools::run_command($cmd);
9e6d6e97 1629
ef5e2be2 1630 } else {
1e3baf05 1631
3e7567e0
DM
1632 $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy"
1633
655d7462
WB
1634 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1635
1636 my $sock = IO::Socket::IP->new(
dd32a466 1637 ReuseAddr => 1,
655d7462
WB
1638 Listen => 1,
1639 LocalPort => $port,
1640 Proto => 'tcp',
1641 GetAddrInfoFlags => 0,
1642 ) or die "failed to create socket: $!\n";
1643 # Inside the worker we shouldn't have any previous alarms
1644 # running anyway...:
1645 alarm(0);
1646 local $SIG{ALRM} = sub { die "connection timed out\n" };
1647 alarm $timeout;
1648 accept(my $cli, $sock) or die "connection failed: $!\n";
058ff55b 1649 alarm(0);
655d7462
WB
1650 close($sock);
1651 if (PVE::Tools::run_command($cmd,
1652 output => '>&'.fileno($cli),
1653 input => '<&'.fileno($cli),
1654 noerr => 1) != 0)
1655 {
1656 die "Failed to run vncproxy.\n";
1657 }
ef5e2be2 1658 }
1e3baf05 1659
1e3baf05
DM
1660 return;
1661 };
1662
2c7fc947 1663 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1e3baf05 1664
3da85107
DM
1665 PVE::Tools::wait_for_vnc_port($port);
1666
1e3baf05 1667 return {
a0d1b1a2 1668 user => $authuser,
1e3baf05 1669 ticket => $ticket,
afdb31d5
DM
1670 port => $port,
1671 upid => $upid,
1672 cert => $sslcert,
1e3baf05
DM
1673 };
1674 }});
1675
87302002
DC
1676__PACKAGE__->register_method({
1677 name => 'termproxy',
1678 path => '{vmid}/termproxy',
1679 method => 'POST',
1680 protected => 1,
1681 permissions => {
1682 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1683 },
1684 description => "Creates a TCP proxy connections.",
1685 parameters => {
1686 additionalProperties => 0,
1687 properties => {
1688 node => get_standard_option('pve-node'),
1689 vmid => get_standard_option('pve-vmid'),
1690 serial=> {
1691 optional => 1,
1692 type => 'string',
1693 enum => [qw(serial0 serial1 serial2 serial3)],
1694 description => "opens a serial terminal (defaults to display)",
1695 },
1696 },
1697 },
1698 returns => {
1699 additionalProperties => 0,
1700 properties => {
1701 user => { type => 'string' },
1702 ticket => { type => 'string' },
1703 port => { type => 'integer' },
1704 upid => { type => 'string' },
1705 },
1706 },
1707 code => sub {
1708 my ($param) = @_;
1709
1710 my $rpcenv = PVE::RPCEnvironment::get();
1711
1712 my $authuser = $rpcenv->get_user();
1713
1714 my $vmid = $param->{vmid};
1715 my $node = $param->{node};
1716 my $serial = $param->{serial};
1717
1718 my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
1719
1720 if (!defined($serial)) {
1721 if ($conf->{vga} && $conf->{vga} =~ m/^serial\d+$/) {
1722 $serial = $conf->{vga};
1723 }
1724 }
1725
1726 my $authpath = "/vms/$vmid";
1727
1728 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
1729
414b42d8
DC
1730 my $family;
1731 my $remcmd = [];
87302002
DC
1732
1733 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
414b42d8
DC
1734 (undef, $family) = PVE::Cluster::remote_node_ip($node);
1735 my $sshinfo = PVE::Cluster::get_ssh_info($node);
1736 $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, '-t');
1737 push @$remcmd, '--';
87302002
DC
1738 } else {
1739 $family = PVE::Tools::get_host_address_family($node);
1740 }
1741
1742 my $port = PVE::Tools::next_vnc_port($family);
1743
ccb88f45 1744 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
87302002
DC
1745 push @$termcmd, '-iface', $serial if $serial;
1746
1747 my $realcmd = sub {
1748 my $upid = shift;
1749
1750 syslog('info', "starting qemu termproxy $upid\n");
1751
1752 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1753 '--perm', 'VM.Console', '--'];
1754 push @$cmd, @$remcmd, @$termcmd;
1755
1756 PVE::Tools::run_command($cmd);
1757 };
1758
1759 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1760
1761 PVE::Tools::wait_for_vnc_port($port);
1762
1763 return {
1764 user => $authuser,
1765 ticket => $ticket,
1766 port => $port,
1767 upid => $upid,
1768 };
1769 }});
1770
3e7567e0
DM
1771__PACKAGE__->register_method({
1772 name => 'vncwebsocket',
1773 path => '{vmid}/vncwebsocket',
1774 method => 'GET',
3e7567e0 1775 permissions => {
c422ce93 1776 description => "You also need to pass a valid ticket (vncticket).",
3e7567e0
DM
1777 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1778 },
4d00f52f 1779 description => "Opens a weksocket for VNC traffic.",
3e7567e0
DM
1780 parameters => {
1781 additionalProperties => 0,
1782 properties => {
1783 node => get_standard_option('pve-node'),
1784 vmid => get_standard_option('pve-vmid'),
c422ce93
DM
1785 vncticket => {
1786 description => "Ticket from previous call to vncproxy.",
1787 type => 'string',
1788 maxLength => 512,
1789 },
3e7567e0
DM
1790 port => {
1791 description => "Port number returned by previous vncproxy call.",
1792 type => 'integer',
1793 minimum => 5900,
1794 maximum => 5999,
1795 },
1796 },
1797 },
1798 returns => {
1799 type => "object",
1800 properties => {
1801 port => { type => 'string' },
1802 },
1803 },
1804 code => sub {
1805 my ($param) = @_;
1806
1807 my $rpcenv = PVE::RPCEnvironment::get();
1808
1809 my $authuser = $rpcenv->get_user();
1810
1811 my $vmid = $param->{vmid};
1812 my $node = $param->{node};
1813
c422ce93
DM
1814 my $authpath = "/vms/$vmid";
1815
1816 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1817
ffda963f 1818 my $conf = PVE::QemuConfig->load_config($vmid, $node); # VM exists ?
3e7567e0
DM
1819
1820 # Note: VNC ports are acessible from outside, so we do not gain any
1821 # security if we verify that $param->{port} belongs to VM $vmid. This
1822 # check is done by verifying the VNC ticket (inside VNC protocol).
1823
1824 my $port = $param->{port};
f34ebd52 1825
3e7567e0
DM
1826 return { port => $port };
1827 }});
1828
288eeea8
DM
1829__PACKAGE__->register_method({
1830 name => 'spiceproxy',
1831 path => '{vmid}/spiceproxy',
78252ce7 1832 method => 'POST',
288eeea8 1833 protected => 1,
78252ce7 1834 proxyto => 'node',
288eeea8
DM
1835 permissions => {
1836 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1837 },
1838 description => "Returns a SPICE configuration to connect to the VM.",
1839 parameters => {
1840 additionalProperties => 0,
1841 properties => {
1842 node => get_standard_option('pve-node'),
1843 vmid => get_standard_option('pve-vmid'),
dd25eecf 1844 proxy => get_standard_option('spice-proxy', { optional => 1 }),
288eeea8
DM
1845 },
1846 },
dd25eecf 1847 returns => get_standard_option('remote-viewer-config'),
288eeea8
DM
1848 code => sub {
1849 my ($param) = @_;
1850
1851 my $rpcenv = PVE::RPCEnvironment::get();
1852
1853 my $authuser = $rpcenv->get_user();
1854
1855 my $vmid = $param->{vmid};
1856 my $node = $param->{node};
fb6c7260 1857 my $proxy = $param->{proxy};
288eeea8 1858
ffda963f 1859 my $conf = PVE::QemuConfig->load_config($vmid, $node);
7f9e28e4
TL
1860 my $title = "VM $vmid";
1861 $title .= " - ". $conf->{name} if $conf->{name};
288eeea8 1862
943340a6 1863 my $port = PVE::QemuServer::spice_port($vmid);
dd25eecf 1864
f34ebd52 1865 my ($ticket, undef, $remote_viewer_config) =
dd25eecf 1866 PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
f34ebd52 1867
288eeea8
DM
1868 PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
1869 PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
f34ebd52 1870
dd25eecf 1871 return $remote_viewer_config;
288eeea8
DM
1872 }});
1873
5fdbe4f0
DM
1874__PACKAGE__->register_method({
1875 name => 'vmcmdidx',
afdb31d5 1876 path => '{vmid}/status',
5fdbe4f0
DM
1877 method => 'GET',
1878 proxyto => 'node',
1879 description => "Directory index",
a0d1b1a2
DM
1880 permissions => {
1881 user => 'all',
1882 },
5fdbe4f0
DM
1883 parameters => {
1884 additionalProperties => 0,
1885 properties => {
1886 node => get_standard_option('pve-node'),
1887 vmid => get_standard_option('pve-vmid'),
1888 },
1889 },
1890 returns => {
1891 type => 'array',
1892 items => {
1893 type => "object",
1894 properties => {
1895 subdir => { type => 'string' },
1896 },
1897 },
1898 links => [ { rel => 'child', href => "{subdir}" } ],
1899 },
1900 code => sub {
1901 my ($param) = @_;
1902
1903 # test if VM exists
ffda963f 1904 my $conf = PVE::QemuConfig->load_config($param->{vmid});
5fdbe4f0
DM
1905
1906 my $res = [
1907 { subdir => 'current' },
1908 { subdir => 'start' },
1909 { subdir => 'stop' },
1910 ];
afdb31d5 1911
5fdbe4f0
DM
1912 return $res;
1913 }});
1914
1e3baf05 1915__PACKAGE__->register_method({
afdb31d5 1916 name => 'vm_status',
5fdbe4f0 1917 path => '{vmid}/status/current',
1e3baf05
DM
1918 method => 'GET',
1919 proxyto => 'node',
1920 protected => 1, # qemu pid files are only readable by root
1921 description => "Get virtual machine status.",
a0d1b1a2
DM
1922 permissions => {
1923 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1924 },
1e3baf05
DM
1925 parameters => {
1926 additionalProperties => 0,
1927 properties => {
1928 node => get_standard_option('pve-node'),
1929 vmid => get_standard_option('pve-vmid'),
1930 },
1931 },
b1a70cab
DM
1932 returns => {
1933 type => 'object',
1934 properties => {
1935 %$PVE::QemuServer::vmstatus_return_properties,
1936 ha => {
1937 description => "HA manager service status.",
1938 type => 'object',
1939 },
1940 spice => {
1941 description => "Qemu VGA configuration supports spice.",
1942 type => 'boolean',
1943 optional => 1,
1944 },
1945 agent => {
1946 description => "Qemu GuestAgent enabled in config.",
1947 type => 'boolean',
1948 optional => 1,
1949 },
1950 },
1951 },
1e3baf05
DM
1952 code => sub {
1953 my ($param) = @_;
1954
1955 # test if VM exists
ffda963f 1956 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 1957
03a33f30 1958 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1959 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1960
4d2a734e 1961 $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
8610701a 1962
86b8228b 1963 $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
9d66b397 1964 $status->{agent} = 1 if (PVE::QemuServer::parse_guest_agent($conf)->{enabled});
c9a074b8 1965
8610701a 1966 return $status;
1e3baf05
DM
1967 }});
1968
1969__PACKAGE__->register_method({
afdb31d5 1970 name => 'vm_start',
5fdbe4f0
DM
1971 path => '{vmid}/status/start',
1972 method => 'POST',
1e3baf05
DM
1973 protected => 1,
1974 proxyto => 'node',
5fdbe4f0 1975 description => "Start virtual machine.",
a0d1b1a2
DM
1976 permissions => {
1977 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1978 },
1e3baf05
DM
1979 parameters => {
1980 additionalProperties => 0,
1981 properties => {
1982 node => get_standard_option('pve-node'),
ab5904f7
TL
1983 vmid => get_standard_option('pve-vmid',
1984 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1985 skiplock => get_standard_option('skiplock'),
1986 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1987 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1988 migration_type => {
1989 type => 'string',
1990 enum => ['secure', 'insecure'],
1991 description => "Migration traffic is encrypted using an SSH " .
1992 "tunnel by default. On secure, completely private networks " .
1993 "this can be disabled to increase performance.",
1994 optional => 1,
1995 },
1996 migration_network => {
29ddbe70 1997 type => 'string', format => 'CIDR',
2de2d6f7
TL
1998 description => "CIDR of the (sub) network that is used for migration.",
1999 optional => 1,
2000 },
952958bc 2001 machine => get_standard_option('pve-qm-machine'),
2189246c 2002 targetstorage => {
bd2d5fe6 2003 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
2004 type => 'string',
2005 optional => 1
2006 }
1e3baf05
DM
2007 },
2008 },
afdb31d5 2009 returns => {
5fdbe4f0
DM
2010 type => 'string',
2011 },
1e3baf05
DM
2012 code => sub {
2013 my ($param) = @_;
2014
2015 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2016 my $authuser = $rpcenv->get_user();
1e3baf05
DM
2017
2018 my $node = extract_param($param, 'node');
1e3baf05
DM
2019 my $vmid = extract_param($param, 'vmid');
2020
952958bc
DM
2021 my $machine = extract_param($param, 'machine');
2022
736c92f6
TL
2023 my $get_root_param = sub {
2024 my $value = extract_param($param, $_[0]);
2025 raise_param_exc({ "$_[0]" => "Only root may use this option." })
2026 if $value && $authuser ne 'root@pam';
2027 return $value;
2028 };
2de2d6f7 2029
736c92f6
TL
2030 my $stateuri = $get_root_param->('stateuri');
2031 my $skiplock = $get_root_param->('skiplock');
2032 my $migratedfrom = $get_root_param->('migratedfrom');
2033 my $migration_type = $get_root_param->('migration_type');
2034 my $migration_network = $get_root_param->('migration_network');
2035 my $targetstorage = $get_root_param->('targetstorage');
2189246c
AD
2036
2037 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
2038 if $targetstorage && !$migratedfrom;
2039
7c14dcae
DM
2040 # read spice ticket from STDIN
2041 my $spice_ticket;
ccab68c2 2042 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
e5caa02e 2043 if (defined(my $line = <STDIN>)) {
760fb3c8
DM
2044 chomp $line;
2045 $spice_ticket = $line;
2046 }
7c14dcae
DM
2047 }
2048
98cbd0f4
WB
2049 PVE::Cluster::check_cfs_quorum();
2050
afdb31d5 2051 my $storecfg = PVE::Storage::config();
5fdbe4f0 2052
a4262553 2053 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri && $rpcenv->{type} ne 'ha') {
88fc87b4
DM
2054 my $hacmd = sub {
2055 my $upid = shift;
5fdbe4f0 2056
02765844 2057 print "Requesting HA start for VM $vmid\n";
88fc87b4 2058
a4262553 2059 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
88fc87b4 2060 PVE::Tools::run_command($cmd);
88fc87b4
DM
2061 return;
2062 };
2063
2064 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2065
2066 } else {
2067
2068 my $realcmd = sub {
2069 my $upid = shift;
2070
2071 syslog('info', "start VM $vmid: $upid\n");
2072
fa8ea931 2073 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 2074 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
2075 return;
2076 };
5fdbe4f0 2077
88fc87b4
DM
2078 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2079 }
5fdbe4f0
DM
2080 }});
2081
2082__PACKAGE__->register_method({
afdb31d5 2083 name => 'vm_stop',
5fdbe4f0
DM
2084 path => '{vmid}/status/stop',
2085 method => 'POST',
2086 protected => 1,
2087 proxyto => 'node',
346130b2 2088 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 2089 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
2090 permissions => {
2091 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2092 },
5fdbe4f0
DM
2093 parameters => {
2094 additionalProperties => 0,
2095 properties => {
2096 node => get_standard_option('pve-node'),
ab5904f7
TL
2097 vmid => get_standard_option('pve-vmid',
2098 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2099 skiplock => get_standard_option('skiplock'),
debe8882 2100 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
2101 timeout => {
2102 description => "Wait maximal timeout seconds.",
2103 type => 'integer',
2104 minimum => 0,
2105 optional => 1,
254575e9
DM
2106 },
2107 keepActive => {
94a17e1d 2108 description => "Do not deactivate storage volumes.",
254575e9
DM
2109 type => 'boolean',
2110 optional => 1,
2111 default => 0,
c6bb9502 2112 }
5fdbe4f0
DM
2113 },
2114 },
afdb31d5 2115 returns => {
5fdbe4f0
DM
2116 type => 'string',
2117 },
2118 code => sub {
2119 my ($param) = @_;
2120
2121 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2122 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2123
2124 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2125 my $vmid = extract_param($param, 'vmid');
2126
2127 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2128 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2129 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2130
254575e9 2131 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2132 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2133 if $keepActive && $authuser ne 'root@pam';
254575e9 2134
af30308f
DM
2135 my $migratedfrom = extract_param($param, 'migratedfrom');
2136 raise_param_exc({ migratedfrom => "Only root may use this option." })
2137 if $migratedfrom && $authuser ne 'root@pam';
2138
2139
ff1a2432
DM
2140 my $storecfg = PVE::Storage::config();
2141
2003f0f8 2142 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 2143
88fc87b4
DM
2144 my $hacmd = sub {
2145 my $upid = shift;
5fdbe4f0 2146
02765844 2147 print "Requesting HA stop for VM $vmid\n";
88fc87b4 2148
a4262553 2149 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
88fc87b4 2150 PVE::Tools::run_command($cmd);
88fc87b4
DM
2151 return;
2152 };
2153
2154 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2155
2156 } else {
2157 my $realcmd = sub {
2158 my $upid = shift;
2159
2160 syslog('info', "stop VM $vmid: $upid\n");
2161
2162 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 2163 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
2164 return;
2165 };
2166
2167 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2168 }
5fdbe4f0
DM
2169 }});
2170
2171__PACKAGE__->register_method({
afdb31d5 2172 name => 'vm_reset',
5fdbe4f0
DM
2173 path => '{vmid}/status/reset',
2174 method => 'POST',
2175 protected => 1,
2176 proxyto => 'node',
2177 description => "Reset virtual machine.",
a0d1b1a2
DM
2178 permissions => {
2179 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2180 },
5fdbe4f0
DM
2181 parameters => {
2182 additionalProperties => 0,
2183 properties => {
2184 node => get_standard_option('pve-node'),
ab5904f7
TL
2185 vmid => get_standard_option('pve-vmid',
2186 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2187 skiplock => get_standard_option('skiplock'),
2188 },
2189 },
afdb31d5 2190 returns => {
5fdbe4f0
DM
2191 type => 'string',
2192 },
2193 code => sub {
2194 my ($param) = @_;
2195
2196 my $rpcenv = PVE::RPCEnvironment::get();
2197
a0d1b1a2 2198 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2199
2200 my $node = extract_param($param, 'node');
2201
2202 my $vmid = extract_param($param, 'vmid');
2203
2204 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2205 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2206 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2207
ff1a2432
DM
2208 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2209
5fdbe4f0
DM
2210 my $realcmd = sub {
2211 my $upid = shift;
2212
1e3baf05 2213 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
2214
2215 return;
2216 };
2217
a0d1b1a2 2218 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2219 }});
2220
2221__PACKAGE__->register_method({
afdb31d5 2222 name => 'vm_shutdown',
5fdbe4f0
DM
2223 path => '{vmid}/status/shutdown',
2224 method => 'POST',
2225 protected => 1,
2226 proxyto => 'node',
d6c747ff
EK
2227 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2228 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
2229 permissions => {
2230 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2231 },
5fdbe4f0
DM
2232 parameters => {
2233 additionalProperties => 0,
2234 properties => {
2235 node => get_standard_option('pve-node'),
ab5904f7
TL
2236 vmid => get_standard_option('pve-vmid',
2237 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2238 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
2239 timeout => {
2240 description => "Wait maximal timeout seconds.",
2241 type => 'integer',
2242 minimum => 0,
2243 optional => 1,
9269013a
DM
2244 },
2245 forceStop => {
2246 description => "Make sure the VM stops.",
2247 type => 'boolean',
2248 optional => 1,
2249 default => 0,
254575e9
DM
2250 },
2251 keepActive => {
94a17e1d 2252 description => "Do not deactivate storage volumes.",
254575e9
DM
2253 type => 'boolean',
2254 optional => 1,
2255 default => 0,
c6bb9502 2256 }
5fdbe4f0
DM
2257 },
2258 },
afdb31d5 2259 returns => {
5fdbe4f0
DM
2260 type => 'string',
2261 },
2262 code => sub {
2263 my ($param) = @_;
2264
2265 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2266 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2267
2268 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2269 my $vmid = extract_param($param, 'vmid');
2270
2271 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2272 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2273 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2274
254575e9 2275 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2276 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2277 if $keepActive && $authuser ne 'root@pam';
254575e9 2278
02d07cf5
DM
2279 my $storecfg = PVE::Storage::config();
2280
89897367
DC
2281 my $shutdown = 1;
2282
2283 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2284 # otherwise, we will infer a shutdown command, but run into the timeout,
2285 # then when the vm is resumed, it will instantly shutdown
2286 #
2287 # checking the qmp status here to get feedback to the gui/cli/api
2288 # and the status query should not take too long
a4262553
TL
2289 my $qmpstatus = eval {
2290 PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
89897367
DC
2291 };
2292 my $err = $@ if $@;
2293
2294 if (!$err && $qmpstatus->{status} eq "paused") {
2295 if ($param->{forceStop}) {
2296 warn "VM is paused - stop instead of shutdown\n";
2297 $shutdown = 0;
2298 } else {
2299 die "VM is paused - cannot shutdown\n";
2300 }
2301 }
2302
a4262553 2303 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
5fdbe4f0 2304
ae849692
DM
2305 my $hacmd = sub {
2306 my $upid = shift;
5fdbe4f0 2307
02765844 2308 print "Requesting HA stop for VM $vmid\n";
ae849692 2309
a4262553 2310 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
ae849692 2311 PVE::Tools::run_command($cmd);
ae849692
DM
2312 return;
2313 };
2314
2315 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2316
2317 } else {
2318
2319 my $realcmd = sub {
2320 my $upid = shift;
2321
2322 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2323
ae849692
DM
2324 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2325 $shutdown, $param->{forceStop}, $keepActive);
ae849692
DM
2326 return;
2327 };
2328
2329 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2330 }
5fdbe4f0
DM
2331 }});
2332
2333__PACKAGE__->register_method({
afdb31d5 2334 name => 'vm_suspend',
5fdbe4f0
DM
2335 path => '{vmid}/status/suspend',
2336 method => 'POST',
2337 protected => 1,
2338 proxyto => 'node',
2339 description => "Suspend virtual machine.",
a0d1b1a2
DM
2340 permissions => {
2341 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2342 },
5fdbe4f0
DM
2343 parameters => {
2344 additionalProperties => 0,
2345 properties => {
2346 node => get_standard_option('pve-node'),
ab5904f7
TL
2347 vmid => get_standard_option('pve-vmid',
2348 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2349 skiplock => get_standard_option('skiplock'),
22371fe0
DC
2350 todisk => {
2351 type => 'boolean',
2352 default => 0,
2353 optional => 1,
2354 description => 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2355 },
48b4cdc2
DC
2356 statestorage => get_standard_option('pve-storage-id', {
2357 description => "The storage for the VM state",
2358 requires => 'todisk',
2359 optional => 1,
2360 completion => \&PVE::Storage::complete_storage_enabled,
2361 }),
5fdbe4f0
DM
2362 },
2363 },
afdb31d5 2364 returns => {
5fdbe4f0
DM
2365 type => 'string',
2366 },
2367 code => sub {
2368 my ($param) = @_;
2369
2370 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2371 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2372
2373 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2374 my $vmid = extract_param($param, 'vmid');
2375
22371fe0
DC
2376 my $todisk = extract_param($param, 'todisk') // 0;
2377
48b4cdc2
DC
2378 my $statestorage = extract_param($param, 'statestorage');
2379
5fdbe4f0 2380 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2381 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2382 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2383
ff1a2432
DM
2384 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2385
22371fe0
DC
2386 die "Cannot suspend HA managed VM to disk\n"
2387 if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);
2388
5fdbe4f0
DM
2389 my $realcmd = sub {
2390 my $upid = shift;
2391
2392 syslog('info', "suspend VM $vmid: $upid\n");
2393
48b4cdc2 2394 PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
5fdbe4f0
DM
2395
2396 return;
2397 };
2398
a4262553 2399 my $taskname = $todisk ? 'qmsuspend' : 'qmpause';
f17fb184 2400 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2401 }});
2402
2403__PACKAGE__->register_method({
afdb31d5 2404 name => 'vm_resume',
5fdbe4f0
DM
2405 path => '{vmid}/status/resume',
2406 method => 'POST',
2407 protected => 1,
2408 proxyto => 'node',
2409 description => "Resume virtual machine.",
a0d1b1a2
DM
2410 permissions => {
2411 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2412 },
5fdbe4f0
DM
2413 parameters => {
2414 additionalProperties => 0,
2415 properties => {
2416 node => get_standard_option('pve-node'),
ab5904f7
TL
2417 vmid => get_standard_option('pve-vmid',
2418 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2419 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2420 nocheck => { type => 'boolean', optional => 1 },
2421
5fdbe4f0
DM
2422 },
2423 },
afdb31d5 2424 returns => {
5fdbe4f0
DM
2425 type => 'string',
2426 },
2427 code => sub {
2428 my ($param) = @_;
2429
2430 my $rpcenv = PVE::RPCEnvironment::get();
2431
a0d1b1a2 2432 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2433
2434 my $node = extract_param($param, 'node');
2435
2436 my $vmid = extract_param($param, 'vmid');
2437
2438 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2439 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2440 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2441
289e0b85
AD
2442 my $nocheck = extract_param($param, 'nocheck');
2443
cd9a035b
TL
2444 my $to_disk_suspended;
2445 eval {
2446 PVE::QemuConfig->lock_config($vmid, sub {
2447 my $conf = PVE::QemuConfig->load_config($vmid);
2448 $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
2449 });
2450 };
2451
2452 die "VM $vmid not running\n"
2453 if !$to_disk_suspended && !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2454
5fdbe4f0
DM
2455 my $realcmd = sub {
2456 my $upid = shift;
2457
2458 syslog('info', "resume VM $vmid: $upid\n");
2459
cd9a035b
TL
2460 if (!$to_disk_suspended) {
2461 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
2462 } else {
2463 my $storecfg = PVE::Storage::config();
2464 PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
2465 }
1e3baf05 2466
5fdbe4f0
DM
2467 return;
2468 };
2469
a0d1b1a2 2470 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2471 }});
2472
2473__PACKAGE__->register_method({
afdb31d5 2474 name => 'vm_sendkey',
5fdbe4f0
DM
2475 path => '{vmid}/sendkey',
2476 method => 'PUT',
2477 protected => 1,
2478 proxyto => 'node',
2479 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2480 permissions => {
2481 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2482 },
5fdbe4f0
DM
2483 parameters => {
2484 additionalProperties => 0,
2485 properties => {
2486 node => get_standard_option('pve-node'),
ab5904f7
TL
2487 vmid => get_standard_option('pve-vmid',
2488 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2489 skiplock => get_standard_option('skiplock'),
2490 key => {
2491 description => "The key (qemu monitor encoding).",
2492 type => 'string'
2493 }
2494 },
2495 },
2496 returns => { type => 'null'},
2497 code => sub {
2498 my ($param) = @_;
2499
2500 my $rpcenv = PVE::RPCEnvironment::get();
2501
a0d1b1a2 2502 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2503
2504 my $node = extract_param($param, 'node');
2505
2506 my $vmid = extract_param($param, 'vmid');
2507
2508 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2509 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2510 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2511
2512 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2513
2514 return;
1e3baf05
DM
2515 }});
2516
1ac0d2ee
AD
2517__PACKAGE__->register_method({
2518 name => 'vm_feature',
2519 path => '{vmid}/feature',
2520 method => 'GET',
2521 proxyto => 'node',
75466c4f 2522 protected => 1,
1ac0d2ee
AD
2523 description => "Check if feature for virtual machine is available.",
2524 permissions => {
2525 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2526 },
2527 parameters => {
2528 additionalProperties => 0,
2529 properties => {
2530 node => get_standard_option('pve-node'),
2531 vmid => get_standard_option('pve-vmid'),
2532 feature => {
2533 description => "Feature to check.",
2534 type => 'string',
7758ce86 2535 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2536 },
2537 snapname => get_standard_option('pve-snapshot-name', {
2538 optional => 1,
2539 }),
2540 },
1ac0d2ee
AD
2541 },
2542 returns => {
719893a9
DM
2543 type => "object",
2544 properties => {
2545 hasFeature => { type => 'boolean' },
7043d946 2546 nodes => {
719893a9
DM
2547 type => 'array',
2548 items => { type => 'string' },
2549 }
2550 },
1ac0d2ee
AD
2551 },
2552 code => sub {
2553 my ($param) = @_;
2554
2555 my $node = extract_param($param, 'node');
2556
2557 my $vmid = extract_param($param, 'vmid');
2558
2559 my $snapname = extract_param($param, 'snapname');
2560
2561 my $feature = extract_param($param, 'feature');
2562
2563 my $running = PVE::QemuServer::check_running($vmid);
2564
ffda963f 2565 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2566
2567 if($snapname){
2568 my $snap = $conf->{snapshots}->{$snapname};
2569 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2570 $conf = $snap;
2571 }
2572 my $storecfg = PVE::Storage::config();
2573
719893a9 2574 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2575 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2576
719893a9
DM
2577 return {
2578 hasFeature => $hasFeature,
2579 nodes => [ keys %$nodelist ],
7043d946 2580 };
1ac0d2ee
AD
2581 }});
2582
6116f729 2583__PACKAGE__->register_method({
9418baad
DM
2584 name => 'clone_vm',
2585 path => '{vmid}/clone',
6116f729
DM
2586 method => 'POST',
2587 protected => 1,
2588 proxyto => 'node',
37329185 2589 description => "Create a copy of virtual machine/template.",
6116f729 2590 permissions => {
9418baad 2591 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2592 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2593 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2594 check =>
2595 [ 'and',
9418baad 2596 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2597 [ 'or',
6116f729
DM
2598 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2599 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2600 ],
2601 ]
2602 },
2603 parameters => {
2604 additionalProperties => 0,
2605 properties => {
6116f729 2606 node => get_standard_option('pve-node'),
335af808 2607 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1ae43f8c
DM
2608 newid => get_standard_option('pve-vmid', {
2609 completion => \&PVE::Cluster::complete_next_vmid,
2610 description => 'VMID for the clone.' }),
a60ab1a6
DM
2611 name => {
2612 optional => 1,
2613 type => 'string', format => 'dns-name',
2614 description => "Set a name for the new VM.",
2615 },
2616 description => {
2617 optional => 1,
2618 type => 'string',
2619 description => "Description for the new VM.",
2620 },
75466c4f 2621 pool => {
6116f729
DM
2622 optional => 1,
2623 type => 'string', format => 'pve-poolid',
2624 description => "Add the new VM to the specified pool.",
2625 },
9076d880 2626 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2627 optional => 1,
2628 }),
81f043eb 2629 storage => get_standard_option('pve-storage-id', {
9418baad 2630 description => "Target storage for full clone.",
81f043eb
AD
2631 optional => 1,
2632 }),
55173c6b 2633 'format' => {
fd13b1d0 2634 description => "Target format for file storage. Only valid for full clone.",
42a19c87
AD
2635 type => 'string',
2636 optional => 1,
55173c6b 2637 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2638 },
6116f729
DM
2639 full => {
2640 optional => 1,
55173c6b 2641 type => 'boolean',
fd13b1d0 2642 description => "Create a full copy of all disks. This is always done when " .
9418baad 2643 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729 2644 },
75466c4f 2645 target => get_standard_option('pve-node', {
55173c6b
DM
2646 description => "Target node. Only allowed if the original VM is on shared storage.",
2647 optional => 1,
2648 }),
0aab5a16
SI
2649 bwlimit => {
2650 description => "Override I/O bandwidth limit (in KiB/s).",
2651 optional => 1,
2652 type => 'integer',
2653 minimum => '0',
41756a3b 2654 default => 'clone limit from datacenter or storage config',
0aab5a16 2655 },
55173c6b 2656 },
6116f729
DM
2657 },
2658 returns => {
2659 type => 'string',
2660 },
2661 code => sub {
2662 my ($param) = @_;
2663
2664 my $rpcenv = PVE::RPCEnvironment::get();
2665
55173c6b 2666 my $authuser = $rpcenv->get_user();
6116f729
DM
2667
2668 my $node = extract_param($param, 'node');
2669
2670 my $vmid = extract_param($param, 'vmid');
2671
2672 my $newid = extract_param($param, 'newid');
2673
6116f729
DM
2674 my $pool = extract_param($param, 'pool');
2675
2676 if (defined($pool)) {
2677 $rpcenv->check_pool_exist($pool);
2678 }
2679
55173c6b 2680 my $snapname = extract_param($param, 'snapname');
9076d880 2681
81f043eb
AD
2682 my $storage = extract_param($param, 'storage');
2683
42a19c87
AD
2684 my $format = extract_param($param, 'format');
2685
55173c6b
DM
2686 my $target = extract_param($param, 'target');
2687
2688 my $localnode = PVE::INotify::nodename();
2689
751cc556 2690 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2691
2692 PVE::Cluster::check_node_exists($target) if $target;
2693
6116f729
DM
2694 my $storecfg = PVE::Storage::config();
2695
4a5a2590
DM
2696 if ($storage) {
2697 # check if storage is enabled on local node
2698 PVE::Storage::storage_check_enabled($storecfg, $storage);
2699 if ($target) {
2700 # check if storage is available on target node
2701 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2702 # clone only works if target storage is shared
2703 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2704 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2705 }
2706 }
2707
55173c6b 2708 PVE::Cluster::check_cfs_quorum();
6116f729 2709
4e4f83fe
DM
2710 my $running = PVE::QemuServer::check_running($vmid) || 0;
2711
4e4f83fe
DM
2712 # exclusive lock if VM is running - else shared lock is enough;
2713 my $shared_lock = $running ? 0 : 1;
2714
9418baad 2715 my $clonefn = sub {
6116f729 2716
829967a9
DM
2717 # do all tests after lock
2718 # we also try to do all tests before we fork the worker
2719
ffda963f 2720 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2721
ffda963f 2722 PVE::QemuConfig->check_lock($conf);
6116f729 2723
4e4f83fe 2724 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2725
4e4f83fe 2726 die "unexpected state change\n" if $verify_running != $running;
6116f729 2727
75466c4f
DM
2728 die "snapshot '$snapname' does not exist\n"
2729 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2730
fd13b1d0
DM
2731 my $full = extract_param($param, 'full');
2732 if (!defined($full)) {
2733 $full = !PVE::QemuConfig->is_template($conf);
2734 }
2735
2736 die "parameter 'storage' not allowed for linked clones\n"
2737 if defined($storage) && !$full;
2738
2739 die "parameter 'format' not allowed for linked clones\n"
2740 if defined($format) && !$full;
2741
75466c4f 2742 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2743
9418baad 2744 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2745
9418baad 2746 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2747
ffda963f 2748 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2749
2750 die "unable to create VM $newid: config file already exists\n"
2751 if -f $conffile;
2752
9418baad 2753 my $newconf = { lock => 'clone' };
829967a9 2754 my $drives = {};
34456bf0 2755 my $fullclone = {};
829967a9
DM
2756 my $vollist = [];
2757
2758 foreach my $opt (keys %$oldconf) {
2759 my $value = $oldconf->{$opt};
2760
2761 # do not copy snapshot related info
2762 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2763 $opt eq 'vmstate' || $opt eq 'snapstate';
2764
a78ea5df
WL
2765 # no need to copy unused images, because VMID(owner) changes anyways
2766 next if $opt =~ m/^unused\d+$/;
2767
829967a9
DM
2768 # always change MAC! address
2769 if ($opt =~ m/^net(\d+)$/) {
2770 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2771 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2772 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2773 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2774 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2775 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2776 die "unable to parse drive options for '$opt'\n" if !$drive;
931432bd 2777 if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
829967a9
DM
2778 $newconf->{$opt} = $value; # simply copy configuration
2779 } else {
fd13b1d0 2780 if ($full || PVE::QemuServer::drive_is_cloudinit($drive)) {
6318daca 2781 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2782 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2783 $fullclone->{$opt} = 1;
64ff6fe4
SP
2784 } else {
2785 # not full means clone instead of copy
6318daca 2786 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2787 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2788 }
829967a9
DM
2789 $drives->{$opt} = $drive;
2790 push @$vollist, $drive->{file};
2791 }
2792 } else {
2793 # copy everything else
2794 $newconf->{$opt} = $value;
2795 }
2796 }
2797
cd11416f 2798 # auto generate a new uuid
cd11416f 2799 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
6ee499ff 2800 $smbios1->{uuid} = PVE::QemuServer::generate_uuid();
cd11416f
DM
2801 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2802
6ee499ff
DC
2803 # auto generate a new vmgenid if the option was set
2804 if ($newconf->{vmgenid}) {
2805 $newconf->{vmgenid} = PVE::QemuServer::generate_uuid();
2806 }
2807
829967a9
DM
2808 delete $newconf->{template};
2809
2810 if ($param->{name}) {
2811 $newconf->{name} = $param->{name};
2812 } else {
c55fee03
DM
2813 if ($oldconf->{name}) {
2814 $newconf->{name} = "Copy-of-$oldconf->{name}";
2815 } else {
2816 $newconf->{name} = "Copy-of-VM-$vmid";
2817 }
829967a9 2818 }
2dd53043 2819
829967a9
DM
2820 if ($param->{description}) {
2821 $newconf->{description} = $param->{description};
2822 }
2823
6116f729 2824 # create empty/temp config - this fails if VM already exists on other node
9418baad 2825 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2826
2827 my $realcmd = sub {
2828 my $upid = shift;
2829
b83e0181 2830 my $newvollist = [];
c6fdd002 2831 my $jobs = {};
6116f729 2832
b83e0181 2833 eval {
eaae66be
TL
2834 local $SIG{INT} =
2835 local $SIG{TERM} =
2836 local $SIG{QUIT} =
2837 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2838
eb15b9f0 2839 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2840
0aab5a16
SI
2841 my $bwlimit = extract_param($param, 'bwlimit');
2842
c6fdd002
AD
2843 my $total_jobs = scalar(keys %{$drives});
2844 my $i = 1;
c6fdd002 2845
829967a9
DM
2846 foreach my $opt (keys %$drives) {
2847 my $drive = $drives->{$opt};
3b4cf0f0 2848 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2849
0aab5a16 2850 my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
f0dbdb68
TL
2851 my $storage_list = [ $src_sid ];
2852 push @$storage_list, $storage if defined($storage);
ee43cd48 2853 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
0aab5a16 2854
152fe752 2855 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0 2856 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
0aab5a16 2857 $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
00b095ca 2858
152fe752 2859 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2860
ffda963f 2861 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2862 $i++;
829967a9 2863 }
b83e0181
DM
2864
2865 delete $newconf->{lock};
68e46b84
DC
2866
2867 # do not write pending changes
c725dd5f
DC
2868 if (my @changes = keys %{$newconf->{pending}}) {
2869 my $pending = join(',', @changes);
2870 warn "found pending changes for '$pending', discarding for clone\n";
68e46b84
DC
2871 delete $newconf->{pending};
2872 }
2873
ffda963f 2874 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2875
2876 if ($target) {
baca276d 2877 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2878 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2879 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2880
ffda963f 2881 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2882 die "Failed to move config to node '$target' - rename failed: $!\n"
2883 if !rename($conffile, $newconffile);
2884 }
d703d4c0 2885
be517049 2886 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2887 };
75466c4f 2888 if (my $err = $@) {
6116f729
DM
2889 unlink $conffile;
2890
c6fdd002
AD
2891 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2892
b83e0181
DM
2893 sleep 1; # some storage like rbd need to wait before release volume - really?
2894
2895 foreach my $volid (@$newvollist) {
2896 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2897 warn $@ if $@;
2898 }
9418baad 2899 die "clone failed: $err";
6116f729
DM
2900 }
2901
2902 return;
2903 };
2904
457010cc
AG
2905 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2906
9418baad 2907 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2908 };
2909
ffda963f 2910 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2911 # Aquire exclusive lock lock for $newid
ffda963f 2912 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2913 });
2914
2915 }});
2916
586bfa78 2917__PACKAGE__->register_method({
43bc02a9
DM
2918 name => 'move_vm_disk',
2919 path => '{vmid}/move_disk',
e2cd75fa 2920 method => 'POST',
586bfa78
AD
2921 protected => 1,
2922 proxyto => 'node',
2923 description => "Move volume to different storage.",
2924 permissions => {
c07a9e3d
DM
2925 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2926 check => [ 'and',
2927 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2928 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2929 ],
586bfa78
AD
2930 },
2931 parameters => {
2932 additionalProperties => 0,
c07a9e3d 2933 properties => {
586bfa78 2934 node => get_standard_option('pve-node'),
335af808 2935 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2936 disk => {
2937 type => 'string',
2938 description => "The disk you want to move.",
74479ee9 2939 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2940 },
335af808
DM
2941 storage => get_standard_option('pve-storage-id', {
2942 description => "Target storage.",
2943 completion => \&PVE::QemuServer::complete_storage,
2944 }),
635c3c44 2945 'format' => {
586bfa78
AD
2946 type => 'string',
2947 description => "Target Format.",
2948 enum => [ 'raw', 'qcow2', 'vmdk' ],
2949 optional => 1,
2950 },
70d45e33
DM
2951 delete => {
2952 type => 'boolean',
2953 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2954 optional => 1,
2955 default => 0,
2956 },
586bfa78
AD
2957 digest => {
2958 type => 'string',
2959 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2960 maxLength => 40,
2961 optional => 1,
2962 },
0aab5a16
SI
2963 bwlimit => {
2964 description => "Override I/O bandwidth limit (in KiB/s).",
2965 optional => 1,
2966 type => 'integer',
2967 minimum => '0',
41756a3b 2968 default => 'move limit from datacenter or storage config',
0aab5a16 2969 },
586bfa78
AD
2970 },
2971 },
e2cd75fa
DM
2972 returns => {
2973 type => 'string',
2974 description => "the task ID.",
2975 },
586bfa78
AD
2976 code => sub {
2977 my ($param) = @_;
2978
2979 my $rpcenv = PVE::RPCEnvironment::get();
2980
2981 my $authuser = $rpcenv->get_user();
2982
2983 my $node = extract_param($param, 'node');
2984
2985 my $vmid = extract_param($param, 'vmid');
2986
2987 my $digest = extract_param($param, 'digest');
2988
2989 my $disk = extract_param($param, 'disk');
2990
2991 my $storeid = extract_param($param, 'storage');
2992
2993 my $format = extract_param($param, 'format');
2994
586bfa78
AD
2995 my $storecfg = PVE::Storage::config();
2996
2997 my $updatefn = sub {
2998
ffda963f 2999 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 3000
dcce9b46
FG
3001 PVE::QemuConfig->check_lock($conf);
3002
586bfa78
AD
3003 die "checksum missmatch (file change by other user?)\n"
3004 if $digest && $digest ne $conf->{digest};
586bfa78
AD
3005
3006 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3007
3008 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3009
70d45e33 3010 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78 3011
931432bd 3012 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
586bfa78 3013
e2cd75fa 3014 my $oldfmt;
70d45e33 3015 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
3016 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3017 $oldfmt = $1;
3018 }
3019
7043d946 3020 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 3021 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 3022
9dbf9b54
FG
3023 # this only checks snapshots because $disk is passed!
3024 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
3025 die "you can't move a disk with snapshots and delete the source\n"
3026 if $snapshotted && $param->{delete};
3027
586bfa78
AD
3028 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3029
3030 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
3031
3032 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
3033
586bfa78
AD
3034 my $realcmd = sub {
3035
3036 my $newvollist = [];
3037
3038 eval {
6cb0144a
EK
3039 local $SIG{INT} =
3040 local $SIG{TERM} =
3041 local $SIG{QUIT} =
3042 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 3043
9dbf9b54
FG
3044 warn "moving disk with snapshots, snapshots will not be moved!\n"
3045 if $snapshotted;
3046
0aab5a16
SI
3047 my $bwlimit = extract_param($param, 'bwlimit');
3048 my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
3049
e2cd75fa 3050 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
0aab5a16 3051 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
e2cd75fa
DM
3052
3053 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
3054
8793d495 3055 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 3056
fbd7dcce
FG
3057 # convert moved disk to base if part of template
3058 PVE::QemuServer::template_create($vmid, $conf, $disk)
3059 if PVE::QemuConfig->is_template($conf);
3060
ffda963f 3061 PVE::QemuConfig->write_config($vmid, $conf);
73272365 3062
ca662131
SI
3063 if ($running && PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks} && PVE::QemuServer::qga_check_running($vmid)) {
3064 eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fstrim"); };
3065 }
3066
f34ebd52 3067 eval {
73272365 3068 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 3069 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
3070 if !$running;
3071 };
3072 warn $@ if $@;
586bfa78
AD
3073 };
3074 if (my $err = $@) {
3075
3076 foreach my $volid (@$newvollist) {
3077 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
3078 warn $@ if $@;
3079 }
3080 die "storage migration failed: $err";
3081 }
70d45e33
DM
3082
3083 if ($param->{delete}) {
a3d0bafb
FG
3084 eval {
3085 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
3086 PVE::Storage::vdisk_free($storecfg, $old_volid);
3087 };
3088 warn $@ if $@;
70d45e33 3089 }
586bfa78
AD
3090 };
3091
3092 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3093 };
e2cd75fa 3094
ffda963f 3095 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
3096 }});
3097
71fc647f
TM
3098my $check_vm_disks_local = sub {
3099 my ($storecfg, $vmconf, $vmid) = @_;
3100
3101 my $local_disks = {};
3102
3103 # add some more information to the disks e.g. cdrom
3104 PVE::QemuServer::foreach_volid($vmconf, sub {
3105 my ($volid, $attr) = @_;
3106
3107 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
3108 if ($storeid) {
3109 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
3110 return if $scfg->{shared};
3111 }
3112 # The shared attr here is just a special case where the vdisk
3113 # is marked as shared manually
3114 return if $attr->{shared};
3115 return if $attr->{cdrom} and $volid eq "none";
3116
3117 if (exists $local_disks->{$volid}) {
3118 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3119 } else {
3120 $local_disks->{$volid} = $attr;
3121 # ensure volid is present in case it's needed
3122 $local_disks->{$volid}->{volid} = $volid;
3123 }
3124 });
3125
3126 return $local_disks;
3127};
3128
3129__PACKAGE__->register_method({
3130 name => 'migrate_vm_precondition',
3131 path => '{vmid}/migrate',
3132 method => 'GET',
3133 protected => 1,
3134 proxyto => 'node',
3135 description => "Get preconditions for migration.",
3136 permissions => {
3137 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3138 },
3139 parameters => {
3140 additionalProperties => 0,
3141 properties => {
3142 node => get_standard_option('pve-node'),
3143 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
3144 target => get_standard_option('pve-node', {
3145 description => "Target node.",
3146 completion => \&PVE::Cluster::complete_migration_target,
3147 optional => 1,
3148 }),
3149 },
3150 },
3151 returns => {
3152 type => "object",
3153 properties => {
3154 running => { type => 'boolean' },
3155 allowed_nodes => {
3156 type => 'array',
3157 optional => 1,
f25852c2
TM
3158 description => "List nodes allowed for offline migration, only passed if VM is offline"
3159 },
3160 not_allowed_nodes => {
3161 type => 'object',
3162 optional => 1,
3163 description => "List not allowed nodes with additional informations, only passed if VM is offline"
71fc647f
TM
3164 },
3165 local_disks => {
3166 type => 'array',
3167 description => "List local disks including CD-Rom, unsused and not referenced disks"
3168 },
3169 local_resources => {
3170 type => 'array',
3171 description => "List local resources e.g. pci, usb"
3172 }
3173 },
3174 },
3175 code => sub {
3176 my ($param) = @_;
3177
3178 my $rpcenv = PVE::RPCEnvironment::get();
3179
3180 my $authuser = $rpcenv->get_user();
3181
3182 PVE::Cluster::check_cfs_quorum();
3183
3184 my $res = {};
3185
3186 my $vmid = extract_param($param, 'vmid');
3187 my $target = extract_param($param, 'target');
3188 my $localnode = PVE::INotify::nodename();
3189
3190
3191 # test if VM exists
3192 my $vmconf = PVE::QemuConfig->load_config($vmid);
3193 my $storecfg = PVE::Storage::config();
3194
3195
3196 # try to detect errors early
3197 PVE::QemuConfig->check_lock($vmconf);
3198
3199 $res->{running} = PVE::QemuServer::check_running($vmid) ? 1:0;
3200
3201 # if vm is not running, return target nodes where local storage is available
3202 # for offline migration
3203 if (!$res->{running}) {
f25852c2
TM
3204 $res->{allowed_nodes} = [];
3205 my $checked_nodes = PVE::QemuServer::check_local_storage_availability($vmconf, $storecfg);
32075a2c 3206 delete $checked_nodes->{$localnode};
f25852c2 3207
f25852c2 3208 foreach my $node (keys %$checked_nodes) {
32075a2c 3209 if (!defined $checked_nodes->{$node}->{unavailable_storages}) {
f25852c2 3210 push @{$res->{allowed_nodes}}, $node;
f25852c2 3211 }
71fc647f 3212
f25852c2
TM
3213 }
3214 $res->{not_allowed_nodes} = $checked_nodes;
71fc647f
TM
3215 }
3216
3217
3218 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3219 $res->{local_disks} = [ values %$local_disks ];;
3220
3221 my $local_resources = PVE::QemuServer::check_local_resources($vmconf, 1);
3222
3223 $res->{local_resources} = $local_resources;
3224
3225 return $res;
3226
3227
3228 }});
3229
3ea94c60 3230__PACKAGE__->register_method({
afdb31d5 3231 name => 'migrate_vm',
3ea94c60
DM
3232 path => '{vmid}/migrate',
3233 method => 'POST',
3234 protected => 1,
3235 proxyto => 'node',
3236 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
3237 permissions => {
3238 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3239 },
3ea94c60
DM
3240 parameters => {
3241 additionalProperties => 0,
3242 properties => {
3243 node => get_standard_option('pve-node'),
335af808 3244 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
c2ed338e 3245 target => get_standard_option('pve-node', {
335af808
DM
3246 description => "Target node.",
3247 completion => \&PVE::Cluster::complete_migration_target,
3248 }),
3ea94c60
DM
3249 online => {
3250 type => 'boolean',
3251 description => "Use online/live migration.",
3252 optional => 1,
3253 },
3254 force => {
3255 type => 'boolean',
3256 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
3257 optional => 1,
3258 },
2de2d6f7
TL
3259 migration_type => {
3260 type => 'string',
3261 enum => ['secure', 'insecure'],
c07a9e3d 3262 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
3263 optional => 1,
3264 },
3265 migration_network => {
c07a9e3d 3266 type => 'string', format => 'CIDR',
2de2d6f7
TL
3267 description => "CIDR of the (sub) network that is used for migration.",
3268 optional => 1,
3269 },
56af7146
AD
3270 "with-local-disks" => {
3271 type => 'boolean',
3272 description => "Enable live storage migration for local disk",
b74cad8a 3273 optional => 1,
56af7146
AD
3274 },
3275 targetstorage => get_standard_option('pve-storage-id', {
3276 description => "Default target storage.",
3277 optional => 1,
3278 completion => \&PVE::QemuServer::complete_storage,
3279 }),
0aab5a16
SI
3280 bwlimit => {
3281 description => "Override I/O bandwidth limit (in KiB/s).",
3282 optional => 1,
3283 type => 'integer',
3284 minimum => '0',
41756a3b 3285 default => 'migrate limit from datacenter or storage config',
0aab5a16 3286 },
3ea94c60
DM
3287 },
3288 },
afdb31d5 3289 returns => {
3ea94c60
DM
3290 type => 'string',
3291 description => "the task ID.",
3292 },
3293 code => sub {
3294 my ($param) = @_;
3295
3296 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 3297 my $authuser = $rpcenv->get_user();
3ea94c60
DM
3298
3299 my $target = extract_param($param, 'target');
3300
3301 my $localnode = PVE::INotify::nodename();
3302 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
3303
3304 PVE::Cluster::check_cfs_quorum();
3305
3306 PVE::Cluster::check_node_exists($target);
3307
3308 my $targetip = PVE::Cluster::remote_node_ip($target);
3309
3310 my $vmid = extract_param($param, 'vmid');
3311
bd2d5fe6 3312 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
3313 if !$param->{online} && $param->{targetstorage};
3314
afdb31d5 3315 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 3316 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 3317
2de2d6f7
TL
3318 raise_param_exc({ migration_type => "Only root may use this option." })
3319 if $param->{migration_type} && $authuser ne 'root@pam';
3320
3321 # allow root only until better network permissions are available
3322 raise_param_exc({ migration_network => "Only root may use this option." })
3323 if $param->{migration_network} && $authuser ne 'root@pam';
3324
3ea94c60 3325 # test if VM exists
ffda963f 3326 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
3327
3328 # try to detect errors early
a5ed42d3 3329
ffda963f 3330 PVE::QemuConfig->check_lock($conf);
a5ed42d3 3331
3ea94c60 3332 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 3333 die "cant migrate running VM without --online\n"
3ea94c60
DM
3334 if !$param->{online};
3335 }
3336
47152e2e 3337 my $storecfg = PVE::Storage::config();
d80ad67f
AD
3338
3339 if( $param->{targetstorage}) {
3340 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
3341 } else {
3342 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
3343 }
47152e2e 3344
2003f0f8 3345 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 3346
88fc87b4
DM
3347 my $hacmd = sub {
3348 my $upid = shift;
3ea94c60 3349
02765844 3350 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4 3351
a4262553 3352 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
88fc87b4 3353 PVE::Tools::run_command($cmd);
88fc87b4
DM
3354 return;
3355 };
3356
3357 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3358
3359 } else {
3360
f53c6ad8 3361 my $realcmd = sub {
f53c6ad8
DM
3362 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3363 };
88fc87b4 3364
f53c6ad8
DM
3365 my $worker = sub {
3366 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
3367 };
3368
f53c6ad8 3369 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 3370 }
3ea94c60 3371
3ea94c60 3372 }});
1e3baf05 3373
91c94f0a 3374__PACKAGE__->register_method({
afdb31d5
DM
3375 name => 'monitor',
3376 path => '{vmid}/monitor',
91c94f0a
DM
3377 method => 'POST',
3378 protected => 1,
3379 proxyto => 'node',
3380 description => "Execute Qemu monitor commands.",
a0d1b1a2 3381 permissions => {
a8f2f427 3382 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 3383 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 3384 },
91c94f0a
DM
3385 parameters => {
3386 additionalProperties => 0,
3387 properties => {
3388 node => get_standard_option('pve-node'),
3389 vmid => get_standard_option('pve-vmid'),
3390 command => {
3391 type => 'string',
3392 description => "The monitor command.",
3393 }
3394 },
3395 },
3396 returns => { type => 'string'},
3397 code => sub {
3398 my ($param) = @_;
3399
a8f2f427
FG
3400 my $rpcenv = PVE::RPCEnvironment::get();
3401 my $authuser = $rpcenv->get_user();
3402
3403 my $is_ro = sub {
3404 my $command = shift;
3405 return $command =~ m/^\s*info(\s+|$)/
3406 || $command =~ m/^\s*help\s*$/;
3407 };
3408
3409 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3410 if !&$is_ro($param->{command});
3411
91c94f0a
DM
3412 my $vmid = $param->{vmid};
3413
ffda963f 3414 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
3415
3416 my $res = '';
3417 eval {
7b7c6d1b 3418 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
3419 };
3420 $res = "ERROR: $@" if $@;
3421
3422 return $res;
3423 }});
3424
0d02881c
AD
3425__PACKAGE__->register_method({
3426 name => 'resize_vm',
614e3941 3427 path => '{vmid}/resize',
0d02881c
AD
3428 method => 'PUT',
3429 protected => 1,
3430 proxyto => 'node',
2f48a4f5 3431 description => "Extend volume size.",
0d02881c 3432 permissions => {
3b2773f6 3433 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3434 },
3435 parameters => {
3436 additionalProperties => 0,
2f48a4f5
DM
3437 properties => {
3438 node => get_standard_option('pve-node'),
335af808 3439 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3440 skiplock => get_standard_option('skiplock'),
3441 disk => {
3442 type => 'string',
3443 description => "The disk you want to resize.",
74479ee9 3444 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3445 },
3446 size => {
3447 type => 'string',
f91b2e45 3448 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3449 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
3450 },
3451 digest => {
3452 type => 'string',
3453 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3454 maxLength => 40,
3455 optional => 1,
3456 },
3457 },
0d02881c
AD
3458 },
3459 returns => { type => 'null'},
3460 code => sub {
3461 my ($param) = @_;
3462
3463 my $rpcenv = PVE::RPCEnvironment::get();
3464
3465 my $authuser = $rpcenv->get_user();
3466
3467 my $node = extract_param($param, 'node');
3468
3469 my $vmid = extract_param($param, 'vmid');
3470
3471 my $digest = extract_param($param, 'digest');
3472
2f48a4f5 3473 my $disk = extract_param($param, 'disk');
75466c4f 3474
2f48a4f5 3475 my $sizestr = extract_param($param, 'size');
0d02881c 3476
f91b2e45 3477 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3478 raise_param_exc({ skiplock => "Only root may use this option." })
3479 if $skiplock && $authuser ne 'root@pam';
3480
0d02881c
AD
3481 my $storecfg = PVE::Storage::config();
3482
0d02881c
AD
3483 my $updatefn = sub {
3484
ffda963f 3485 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3486
3487 die "checksum missmatch (file change by other user?)\n"
3488 if $digest && $digest ne $conf->{digest};
ffda963f 3489 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3490
f91b2e45
DM
3491 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3492
3493 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3494
d662790a
WL
3495 my (undef, undef, undef, undef, undef, undef, $format) =
3496 PVE::Storage::parse_volname($storecfg, $drive->{file});
3497
c2ed338e 3498 die "can't resize volume: $disk if snapshot exists\n"
d662790a
WL
3499 if %{$conf->{snapshots}} && $format eq 'qcow2';
3500
f91b2e45
DM
3501 my $volid = $drive->{file};
3502
3503 die "disk '$disk' has no associated volume\n" if !$volid;
3504
3505 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3506
3507 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3508
3509 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3510
b572a606 3511 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3512 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3513
3514 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3515 my ($ext, $newsize, $unit) = ($1, $2, $4);
3516 if ($unit) {
3517 if ($unit eq 'K') {
3518 $newsize = $newsize * 1024;
3519 } elsif ($unit eq 'M') {
3520 $newsize = $newsize * 1024 * 1024;
3521 } elsif ($unit eq 'G') {
3522 $newsize = $newsize * 1024 * 1024 * 1024;
3523 } elsif ($unit eq 'T') {
3524 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3525 }
3526 }
3527 $newsize += $size if $ext;
3528 $newsize = int($newsize);
3529
9a478b17 3530 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3531
3532 return if $size == $newsize;
3533
2f48a4f5 3534 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3535
f91b2e45 3536 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3537
f91b2e45
DM
3538 $drive->{size} = $newsize;
3539 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3540
ffda963f 3541 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3542 };
0d02881c 3543
ffda963f 3544 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3545 return undef;
3546 }});
3547
9dbd1ee4 3548__PACKAGE__->register_method({
7e7d7b61 3549 name => 'snapshot_list',
9dbd1ee4 3550 path => '{vmid}/snapshot',
7e7d7b61
DM
3551 method => 'GET',
3552 description => "List all snapshots.",
3553 permissions => {
3554 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3555 },
3556 proxyto => 'node',
3557 protected => 1, # qemu pid files are only readable by root
3558 parameters => {
3559 additionalProperties => 0,
3560 properties => {
e261de40 3561 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3562 node => get_standard_option('pve-node'),
3563 },
3564 },
3565 returns => {
3566 type => 'array',
3567 items => {
3568 type => "object",
ce9b0a38
DM
3569 properties => {
3570 name => {
3571 description => "Snapshot identifier. Value 'current' identifies the current VM.",
3572 type => 'string',
3573 },
3574 vmstate => {
3575 description => "Snapshot includes RAM.",
3576 type => 'boolean',
3577 optional => 1,
3578 },
3579 description => {
3580 description => "Snapshot description.",
3581 type => 'string',
3582 },
3583 snaptime => {
3584 description => "Snapshot creation time",
3585 type => 'integer',
3586 renderer => 'timestamp',
3587 optional => 1,
3588 },
3589 parent => {
3590 description => "Parent snapshot identifier.",
3591 type => 'string',
3592 optional => 1,
3593 },
3594 },
7e7d7b61
DM
3595 },
3596 links => [ { rel => 'child', href => "{name}" } ],
3597 },
3598 code => sub {
3599 my ($param) = @_;
3600
6aa4651b
DM
3601 my $vmid = $param->{vmid};
3602
ffda963f 3603 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3604 my $snaphash = $conf->{snapshots} || {};
3605
3606 my $res = [];
3607
3608 foreach my $name (keys %$snaphash) {
0ea6bc69 3609 my $d = $snaphash->{$name};
75466c4f
DM
3610 my $item = {
3611 name => $name,
3612 snaptime => $d->{snaptime} || 0,
6aa4651b 3613 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3614 description => $d->{description} || '',
3615 };
0ea6bc69 3616 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3617 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3618 push @$res, $item;
3619 }
3620
6aa4651b 3621 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
ce9b0a38
DM
3622 my $current = {
3623 name => 'current',
3624 digest => $conf->{digest},
3625 running => $running,
3626 description => "You are here!",
3627 };
d1914468
DM
3628 $current->{parent} = $conf->{parent} if $conf->{parent};
3629
3630 push @$res, $current;
7e7d7b61
DM
3631
3632 return $res;
3633 }});
3634
3635__PACKAGE__->register_method({
3636 name => 'snapshot',
3637 path => '{vmid}/snapshot',
3638 method => 'POST',
9dbd1ee4
AD
3639 protected => 1,
3640 proxyto => 'node',
3641 description => "Snapshot a VM.",
3642 permissions => {
f1baf1df 3643 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3644 },
3645 parameters => {
3646 additionalProperties => 0,
3647 properties => {
3648 node => get_standard_option('pve-node'),
335af808 3649 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3650 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3651 vmstate => {
3652 optional => 1,
3653 type => 'boolean',
3654 description => "Save the vmstate",
3655 },
782f4f75
DM
3656 description => {
3657 optional => 1,
3658 type => 'string',
3659 description => "A textual description or comment.",
3660 },
9dbd1ee4
AD
3661 },
3662 },
7e7d7b61
DM
3663 returns => {
3664 type => 'string',
3665 description => "the task ID.",
3666 },
9dbd1ee4
AD
3667 code => sub {
3668 my ($param) = @_;
3669
3670 my $rpcenv = PVE::RPCEnvironment::get();
3671
3672 my $authuser = $rpcenv->get_user();
3673
3674 my $node = extract_param($param, 'node');
3675
3676 my $vmid = extract_param($param, 'vmid');
3677
9dbd1ee4
AD
3678 my $snapname = extract_param($param, 'snapname');
3679
d1914468
DM
3680 die "unable to use snapshot name 'current' (reserved name)\n"
3681 if $snapname eq 'current';
3682
7e7d7b61 3683 my $realcmd = sub {
22c377f0 3684 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
c2ed338e 3685 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3686 $param->{description});
7e7d7b61
DM
3687 };
3688
3689 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3690 }});
3691
154ccdcd
DM
3692__PACKAGE__->register_method({
3693 name => 'snapshot_cmd_idx',
3694 path => '{vmid}/snapshot/{snapname}',
3695 description => '',
3696 method => 'GET',
3697 permissions => {
3698 user => 'all',
3699 },
3700 parameters => {
3701 additionalProperties => 0,
3702 properties => {
3703 vmid => get_standard_option('pve-vmid'),
3704 node => get_standard_option('pve-node'),
8abd398b 3705 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3706 },
3707 },
3708 returns => {
3709 type => 'array',
3710 items => {
3711 type => "object",
3712 properties => {},
3713 },
3714 links => [ { rel => 'child', href => "{cmd}" } ],
3715 },
3716 code => sub {
3717 my ($param) = @_;
3718
3719 my $res = [];
3720
3721 push @$res, { cmd => 'rollback' };
d788cea6 3722 push @$res, { cmd => 'config' };
154ccdcd
DM
3723
3724 return $res;
3725 }});
3726
d788cea6
DM
3727__PACKAGE__->register_method({
3728 name => 'update_snapshot_config',
3729 path => '{vmid}/snapshot/{snapname}/config',
3730 method => 'PUT',
3731 protected => 1,
3732 proxyto => 'node',
3733 description => "Update snapshot metadata.",
3734 permissions => {
3735 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3736 },
3737 parameters => {
3738 additionalProperties => 0,
3739 properties => {
3740 node => get_standard_option('pve-node'),
3741 vmid => get_standard_option('pve-vmid'),
3742 snapname => get_standard_option('pve-snapshot-name'),
3743 description => {
3744 optional => 1,
3745 type => 'string',
3746 description => "A textual description or comment.",
3747 },
3748 },
3749 },
3750 returns => { type => 'null' },
3751 code => sub {
3752 my ($param) = @_;
3753
3754 my $rpcenv = PVE::RPCEnvironment::get();
3755
3756 my $authuser = $rpcenv->get_user();
3757
3758 my $vmid = extract_param($param, 'vmid');
3759
3760 my $snapname = extract_param($param, 'snapname');
3761
3762 return undef if !defined($param->{description});
3763
3764 my $updatefn = sub {
3765
ffda963f 3766 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3767
ffda963f 3768 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3769
3770 my $snap = $conf->{snapshots}->{$snapname};
3771
75466c4f
DM
3772 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3773
d788cea6
DM
3774 $snap->{description} = $param->{description} if defined($param->{description});
3775
ffda963f 3776 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3777 };
3778
ffda963f 3779 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3780
3781 return undef;
3782 }});
3783
3784__PACKAGE__->register_method({
3785 name => 'get_snapshot_config',
3786 path => '{vmid}/snapshot/{snapname}/config',
3787 method => 'GET',
3788 proxyto => 'node',
3789 description => "Get snapshot configuration",
3790 permissions => {
c268337d 3791 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3792 },
3793 parameters => {
3794 additionalProperties => 0,
3795 properties => {
3796 node => get_standard_option('pve-node'),
3797 vmid => get_standard_option('pve-vmid'),
3798 snapname => get_standard_option('pve-snapshot-name'),
3799 },
3800 },
3801 returns => { type => "object" },
3802 code => sub {
3803 my ($param) = @_;
3804
3805 my $rpcenv = PVE::RPCEnvironment::get();
3806
3807 my $authuser = $rpcenv->get_user();
3808
3809 my $vmid = extract_param($param, 'vmid');
3810
3811 my $snapname = extract_param($param, 'snapname');
3812
ffda963f 3813 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3814
3815 my $snap = $conf->{snapshots}->{$snapname};
3816
75466c4f
DM
3817 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3818
d788cea6
DM
3819 return $snap;
3820 }});
3821
7e7d7b61
DM
3822__PACKAGE__->register_method({
3823 name => 'rollback',
154ccdcd 3824 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3825 method => 'POST',
3826 protected => 1,
3827 proxyto => 'node',
3828 description => "Rollback VM state to specified snapshot.",
3829 permissions => {
c268337d 3830 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3831 },
3832 parameters => {
3833 additionalProperties => 0,
3834 properties => {
3835 node => get_standard_option('pve-node'),
335af808 3836 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3837 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3838 },
3839 },
3840 returns => {
3841 type => 'string',
3842 description => "the task ID.",
3843 },
3844 code => sub {
3845 my ($param) = @_;
3846
3847 my $rpcenv = PVE::RPCEnvironment::get();
3848
3849 my $authuser = $rpcenv->get_user();
3850
3851 my $node = extract_param($param, 'node');
3852
3853 my $vmid = extract_param($param, 'vmid');
3854
3855 my $snapname = extract_param($param, 'snapname');
3856
7e7d7b61 3857 my $realcmd = sub {
22c377f0 3858 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3859 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3860 };
3861
c068c1c3
WL
3862 my $worker = sub {
3863 # hold migration lock, this makes sure that nobody create replication snapshots
3864 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3865 };
3866
3867 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3868 }});
3869
3870__PACKAGE__->register_method({
3871 name => 'delsnapshot',
3872 path => '{vmid}/snapshot/{snapname}',
3873 method => 'DELETE',
3874 protected => 1,
3875 proxyto => 'node',
3876 description => "Delete a VM snapshot.",
3877 permissions => {
f1baf1df 3878 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3879 },
3880 parameters => {
3881 additionalProperties => 0,
3882 properties => {
3883 node => get_standard_option('pve-node'),
335af808 3884 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3885 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3886 force => {
3887 optional => 1,
3888 type => 'boolean',
3889 description => "For removal from config file, even if removing disk snapshots fails.",
3890 },
7e7d7b61
DM
3891 },
3892 },
3893 returns => {
3894 type => 'string',
3895 description => "the task ID.",
3896 },
3897 code => sub {
3898 my ($param) = @_;
3899
3900 my $rpcenv = PVE::RPCEnvironment::get();
3901
3902 my $authuser = $rpcenv->get_user();
3903
3904 my $node = extract_param($param, 'node');
3905
3906 my $vmid = extract_param($param, 'vmid');
3907
3908 my $snapname = extract_param($param, 'snapname');
3909
7e7d7b61 3910 my $realcmd = sub {
22c377f0 3911 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3912 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3913 };
9dbd1ee4 3914
7b2257a8 3915 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3916 }});
3917
04a69bb4
AD
3918__PACKAGE__->register_method({
3919 name => 'template',
3920 path => '{vmid}/template',
3921 method => 'POST',
3922 protected => 1,
3923 proxyto => 'node',
3924 description => "Create a Template.",
b02691d8 3925 permissions => {
7af0a6c8
DM
3926 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3927 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3928 },
04a69bb4
AD
3929 parameters => {
3930 additionalProperties => 0,
3931 properties => {
3932 node => get_standard_option('pve-node'),
335af808 3933 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3934 disk => {
3935 optional => 1,
3936 type => 'string',
3937 description => "If you want to convert only 1 disk to base image.",
74479ee9 3938 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3939 },
3940
3941 },
3942 },
3943 returns => { type => 'null'},
3944 code => sub {
3945 my ($param) = @_;
3946
3947 my $rpcenv = PVE::RPCEnvironment::get();
3948
3949 my $authuser = $rpcenv->get_user();
3950
3951 my $node = extract_param($param, 'node');
3952
3953 my $vmid = extract_param($param, 'vmid');
3954
3955 my $disk = extract_param($param, 'disk');
3956
3957 my $updatefn = sub {
3958
ffda963f 3959 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3960
ffda963f 3961 PVE::QemuConfig->check_lock($conf);
04a69bb4 3962
75466c4f 3963 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3964 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3965
75466c4f 3966 die "you can't convert a template to a template\n"
ffda963f 3967 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3968
75466c4f 3969 die "you can't convert a VM to template if VM is running\n"
218cab9a 3970 if PVE::QemuServer::check_running($vmid);
35c5fdef 3971
04a69bb4
AD
3972 my $realcmd = sub {
3973 PVE::QemuServer::template_create($vmid, $conf, $disk);
3974 };
04a69bb4 3975
75e7e997 3976 $conf->{template} = 1;
ffda963f 3977 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3978
3979 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3980 };
3981
ffda963f 3982 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3983 return undef;
3984 }});
3985
73709749
ML
3986__PACKAGE__->register_method({
3987 name => 'cloudinit_generated_config_dump',
3988 path => '{vmid}/cloudinit/dump',
3989 method => 'GET',
3990 proxyto => 'node',
3991 description => "Get automatically generated cloudinit config.",
3992 permissions => {
3993 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3994 },
3995 parameters => {
3996 additionalProperties => 0,
3997 properties => {
3998 node => get_standard_option('pve-node'),
3999 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
4000 type => {
4001 description => 'Config type.',
4002 type => 'string',
4003 enum => ['user', 'network', 'meta'],
4004 },
4005 },
4006 },
4007 returns => {
4008 type => 'string',
4009 },
4010 code => sub {
4011 my ($param) = @_;
4012
4013 my $conf = PVE::QemuConfig->load_config($param->{vmid});
4014
4015 return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type});
4016 }});
4017
1e3baf05 40181;