]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
refactor vm_stop locked code
[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' },
58f9db6a
DC
1910 { subdir => 'reset' },
1911 { subdir => 'shutdown' },
1912 { subdir => 'suspend' },
5fdbe4f0 1913 ];
afdb31d5 1914
5fdbe4f0
DM
1915 return $res;
1916 }});
1917
1e3baf05 1918__PACKAGE__->register_method({
afdb31d5 1919 name => 'vm_status',
5fdbe4f0 1920 path => '{vmid}/status/current',
1e3baf05
DM
1921 method => 'GET',
1922 proxyto => 'node',
1923 protected => 1, # qemu pid files are only readable by root
1924 description => "Get virtual machine status.",
a0d1b1a2
DM
1925 permissions => {
1926 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1927 },
1e3baf05
DM
1928 parameters => {
1929 additionalProperties => 0,
1930 properties => {
1931 node => get_standard_option('pve-node'),
1932 vmid => get_standard_option('pve-vmid'),
1933 },
1934 },
b1a70cab
DM
1935 returns => {
1936 type => 'object',
1937 properties => {
1938 %$PVE::QemuServer::vmstatus_return_properties,
1939 ha => {
1940 description => "HA manager service status.",
1941 type => 'object',
1942 },
1943 spice => {
1944 description => "Qemu VGA configuration supports spice.",
1945 type => 'boolean',
1946 optional => 1,
1947 },
1948 agent => {
1949 description => "Qemu GuestAgent enabled in config.",
1950 type => 'boolean',
1951 optional => 1,
1952 },
1953 },
1954 },
1e3baf05
DM
1955 code => sub {
1956 my ($param) = @_;
1957
1958 # test if VM exists
ffda963f 1959 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 1960
03a33f30 1961 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1962 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1963
4d2a734e 1964 $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
8610701a 1965
86b8228b 1966 $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
9d66b397 1967 $status->{agent} = 1 if (PVE::QemuServer::parse_guest_agent($conf)->{enabled});
c9a074b8 1968
8610701a 1969 return $status;
1e3baf05
DM
1970 }});
1971
1972__PACKAGE__->register_method({
afdb31d5 1973 name => 'vm_start',
5fdbe4f0
DM
1974 path => '{vmid}/status/start',
1975 method => 'POST',
1e3baf05
DM
1976 protected => 1,
1977 proxyto => 'node',
5fdbe4f0 1978 description => "Start virtual machine.",
a0d1b1a2
DM
1979 permissions => {
1980 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1981 },
1e3baf05
DM
1982 parameters => {
1983 additionalProperties => 0,
1984 properties => {
1985 node => get_standard_option('pve-node'),
ab5904f7
TL
1986 vmid => get_standard_option('pve-vmid',
1987 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1988 skiplock => get_standard_option('skiplock'),
1989 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1990 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1991 migration_type => {
1992 type => 'string',
1993 enum => ['secure', 'insecure'],
1994 description => "Migration traffic is encrypted using an SSH " .
1995 "tunnel by default. On secure, completely private networks " .
1996 "this can be disabled to increase performance.",
1997 optional => 1,
1998 },
1999 migration_network => {
29ddbe70 2000 type => 'string', format => 'CIDR',
2de2d6f7
TL
2001 description => "CIDR of the (sub) network that is used for migration.",
2002 optional => 1,
2003 },
952958bc 2004 machine => get_standard_option('pve-qm-machine'),
2189246c 2005 targetstorage => {
bd2d5fe6 2006 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
2007 type => 'string',
2008 optional => 1
2009 }
1e3baf05
DM
2010 },
2011 },
afdb31d5 2012 returns => {
5fdbe4f0
DM
2013 type => 'string',
2014 },
1e3baf05
DM
2015 code => sub {
2016 my ($param) = @_;
2017
2018 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2019 my $authuser = $rpcenv->get_user();
1e3baf05
DM
2020
2021 my $node = extract_param($param, 'node');
1e3baf05
DM
2022 my $vmid = extract_param($param, 'vmid');
2023
952958bc
DM
2024 my $machine = extract_param($param, 'machine');
2025
736c92f6
TL
2026 my $get_root_param = sub {
2027 my $value = extract_param($param, $_[0]);
2028 raise_param_exc({ "$_[0]" => "Only root may use this option." })
2029 if $value && $authuser ne 'root@pam';
2030 return $value;
2031 };
2de2d6f7 2032
736c92f6
TL
2033 my $stateuri = $get_root_param->('stateuri');
2034 my $skiplock = $get_root_param->('skiplock');
2035 my $migratedfrom = $get_root_param->('migratedfrom');
2036 my $migration_type = $get_root_param->('migration_type');
2037 my $migration_network = $get_root_param->('migration_network');
2038 my $targetstorage = $get_root_param->('targetstorage');
2189246c
AD
2039
2040 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
2041 if $targetstorage && !$migratedfrom;
2042
7c14dcae
DM
2043 # read spice ticket from STDIN
2044 my $spice_ticket;
ccab68c2 2045 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
e5caa02e 2046 if (defined(my $line = <STDIN>)) {
760fb3c8
DM
2047 chomp $line;
2048 $spice_ticket = $line;
2049 }
7c14dcae
DM
2050 }
2051
98cbd0f4
WB
2052 PVE::Cluster::check_cfs_quorum();
2053
afdb31d5 2054 my $storecfg = PVE::Storage::config();
5fdbe4f0 2055
a4262553 2056 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri && $rpcenv->{type} ne 'ha') {
88fc87b4
DM
2057 my $hacmd = sub {
2058 my $upid = shift;
5fdbe4f0 2059
02765844 2060 print "Requesting HA start for VM $vmid\n";
88fc87b4 2061
a4262553 2062 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
88fc87b4 2063 PVE::Tools::run_command($cmd);
88fc87b4
DM
2064 return;
2065 };
2066
2067 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2068
2069 } else {
2070
2071 my $realcmd = sub {
2072 my $upid = shift;
2073
2074 syslog('info', "start VM $vmid: $upid\n");
2075
fa8ea931 2076 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 2077 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
2078 return;
2079 };
5fdbe4f0 2080
88fc87b4
DM
2081 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2082 }
5fdbe4f0
DM
2083 }});
2084
2085__PACKAGE__->register_method({
afdb31d5 2086 name => 'vm_stop',
5fdbe4f0
DM
2087 path => '{vmid}/status/stop',
2088 method => 'POST',
2089 protected => 1,
2090 proxyto => 'node',
346130b2 2091 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 2092 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
2093 permissions => {
2094 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2095 },
5fdbe4f0
DM
2096 parameters => {
2097 additionalProperties => 0,
2098 properties => {
2099 node => get_standard_option('pve-node'),
ab5904f7
TL
2100 vmid => get_standard_option('pve-vmid',
2101 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2102 skiplock => get_standard_option('skiplock'),
debe8882 2103 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
2104 timeout => {
2105 description => "Wait maximal timeout seconds.",
2106 type => 'integer',
2107 minimum => 0,
2108 optional => 1,
254575e9
DM
2109 },
2110 keepActive => {
94a17e1d 2111 description => "Do not deactivate storage volumes.",
254575e9
DM
2112 type => 'boolean',
2113 optional => 1,
2114 default => 0,
c6bb9502 2115 }
5fdbe4f0
DM
2116 },
2117 },
afdb31d5 2118 returns => {
5fdbe4f0
DM
2119 type => 'string',
2120 },
2121 code => sub {
2122 my ($param) = @_;
2123
2124 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2125 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2126
2127 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2128 my $vmid = extract_param($param, 'vmid');
2129
2130 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2131 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2132 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2133
254575e9 2134 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2135 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2136 if $keepActive && $authuser ne 'root@pam';
254575e9 2137
af30308f
DM
2138 my $migratedfrom = extract_param($param, 'migratedfrom');
2139 raise_param_exc({ migratedfrom => "Only root may use this option." })
2140 if $migratedfrom && $authuser ne 'root@pam';
2141
2142
ff1a2432
DM
2143 my $storecfg = PVE::Storage::config();
2144
2003f0f8 2145 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 2146
88fc87b4
DM
2147 my $hacmd = sub {
2148 my $upid = shift;
5fdbe4f0 2149
02765844 2150 print "Requesting HA stop for VM $vmid\n";
88fc87b4 2151
a4262553 2152 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
88fc87b4 2153 PVE::Tools::run_command($cmd);
88fc87b4
DM
2154 return;
2155 };
2156
2157 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2158
2159 } else {
2160 my $realcmd = sub {
2161 my $upid = shift;
2162
2163 syslog('info', "stop VM $vmid: $upid\n");
2164
2165 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 2166 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
2167 return;
2168 };
2169
2170 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2171 }
5fdbe4f0
DM
2172 }});
2173
2174__PACKAGE__->register_method({
afdb31d5 2175 name => 'vm_reset',
5fdbe4f0
DM
2176 path => '{vmid}/status/reset',
2177 method => 'POST',
2178 protected => 1,
2179 proxyto => 'node',
2180 description => "Reset virtual machine.",
a0d1b1a2
DM
2181 permissions => {
2182 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2183 },
5fdbe4f0
DM
2184 parameters => {
2185 additionalProperties => 0,
2186 properties => {
2187 node => get_standard_option('pve-node'),
ab5904f7
TL
2188 vmid => get_standard_option('pve-vmid',
2189 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2190 skiplock => get_standard_option('skiplock'),
2191 },
2192 },
afdb31d5 2193 returns => {
5fdbe4f0
DM
2194 type => 'string',
2195 },
2196 code => sub {
2197 my ($param) = @_;
2198
2199 my $rpcenv = PVE::RPCEnvironment::get();
2200
a0d1b1a2 2201 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2202
2203 my $node = extract_param($param, 'node');
2204
2205 my $vmid = extract_param($param, 'vmid');
2206
2207 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2208 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2209 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2210
ff1a2432
DM
2211 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2212
5fdbe4f0
DM
2213 my $realcmd = sub {
2214 my $upid = shift;
2215
1e3baf05 2216 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
2217
2218 return;
2219 };
2220
a0d1b1a2 2221 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2222 }});
2223
2224__PACKAGE__->register_method({
afdb31d5 2225 name => 'vm_shutdown',
5fdbe4f0
DM
2226 path => '{vmid}/status/shutdown',
2227 method => 'POST',
2228 protected => 1,
2229 proxyto => 'node',
d6c747ff
EK
2230 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2231 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
2232 permissions => {
2233 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2234 },
5fdbe4f0
DM
2235 parameters => {
2236 additionalProperties => 0,
2237 properties => {
2238 node => get_standard_option('pve-node'),
ab5904f7
TL
2239 vmid => get_standard_option('pve-vmid',
2240 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2241 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
2242 timeout => {
2243 description => "Wait maximal timeout seconds.",
2244 type => 'integer',
2245 minimum => 0,
2246 optional => 1,
9269013a
DM
2247 },
2248 forceStop => {
2249 description => "Make sure the VM stops.",
2250 type => 'boolean',
2251 optional => 1,
2252 default => 0,
254575e9
DM
2253 },
2254 keepActive => {
94a17e1d 2255 description => "Do not deactivate storage volumes.",
254575e9
DM
2256 type => 'boolean',
2257 optional => 1,
2258 default => 0,
c6bb9502 2259 }
5fdbe4f0
DM
2260 },
2261 },
afdb31d5 2262 returns => {
5fdbe4f0
DM
2263 type => 'string',
2264 },
2265 code => sub {
2266 my ($param) = @_;
2267
2268 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2269 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2270
2271 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2272 my $vmid = extract_param($param, 'vmid');
2273
2274 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2275 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2276 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2277
254575e9 2278 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2279 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2280 if $keepActive && $authuser ne 'root@pam';
254575e9 2281
02d07cf5
DM
2282 my $storecfg = PVE::Storage::config();
2283
89897367
DC
2284 my $shutdown = 1;
2285
2286 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2287 # otherwise, we will infer a shutdown command, but run into the timeout,
2288 # then when the vm is resumed, it will instantly shutdown
2289 #
2290 # checking the qmp status here to get feedback to the gui/cli/api
2291 # and the status query should not take too long
a4262553
TL
2292 my $qmpstatus = eval {
2293 PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
89897367
DC
2294 };
2295 my $err = $@ if $@;
2296
2297 if (!$err && $qmpstatus->{status} eq "paused") {
2298 if ($param->{forceStop}) {
2299 warn "VM is paused - stop instead of shutdown\n";
2300 $shutdown = 0;
2301 } else {
2302 die "VM is paused - cannot shutdown\n";
2303 }
2304 }
2305
a4262553 2306 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
5fdbe4f0 2307
ae849692
DM
2308 my $hacmd = sub {
2309 my $upid = shift;
5fdbe4f0 2310
02765844 2311 print "Requesting HA stop for VM $vmid\n";
ae849692 2312
a4262553 2313 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped'];
ae849692 2314 PVE::Tools::run_command($cmd);
ae849692
DM
2315 return;
2316 };
2317
2318 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2319
2320 } else {
2321
2322 my $realcmd = sub {
2323 my $upid = shift;
2324
2325 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2326
ae849692
DM
2327 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2328 $shutdown, $param->{forceStop}, $keepActive);
ae849692
DM
2329 return;
2330 };
2331
2332 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2333 }
5fdbe4f0
DM
2334 }});
2335
2336__PACKAGE__->register_method({
afdb31d5 2337 name => 'vm_suspend',
5fdbe4f0
DM
2338 path => '{vmid}/status/suspend',
2339 method => 'POST',
2340 protected => 1,
2341 proxyto => 'node',
2342 description => "Suspend virtual machine.",
a0d1b1a2
DM
2343 permissions => {
2344 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2345 },
5fdbe4f0
DM
2346 parameters => {
2347 additionalProperties => 0,
2348 properties => {
2349 node => get_standard_option('pve-node'),
ab5904f7
TL
2350 vmid => get_standard_option('pve-vmid',
2351 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2352 skiplock => get_standard_option('skiplock'),
22371fe0
DC
2353 todisk => {
2354 type => 'boolean',
2355 default => 0,
2356 optional => 1,
2357 description => 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2358 },
48b4cdc2
DC
2359 statestorage => get_standard_option('pve-storage-id', {
2360 description => "The storage for the VM state",
2361 requires => 'todisk',
2362 optional => 1,
2363 completion => \&PVE::Storage::complete_storage_enabled,
2364 }),
5fdbe4f0
DM
2365 },
2366 },
afdb31d5 2367 returns => {
5fdbe4f0
DM
2368 type => 'string',
2369 },
2370 code => sub {
2371 my ($param) = @_;
2372
2373 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2374 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2375
2376 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2377 my $vmid = extract_param($param, 'vmid');
2378
22371fe0
DC
2379 my $todisk = extract_param($param, 'todisk') // 0;
2380
48b4cdc2
DC
2381 my $statestorage = extract_param($param, 'statestorage');
2382
5fdbe4f0 2383 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2384 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2385 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2386
ff1a2432
DM
2387 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2388
22371fe0
DC
2389 die "Cannot suspend HA managed VM to disk\n"
2390 if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);
2391
5fdbe4f0
DM
2392 my $realcmd = sub {
2393 my $upid = shift;
2394
2395 syslog('info', "suspend VM $vmid: $upid\n");
2396
48b4cdc2 2397 PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
5fdbe4f0
DM
2398
2399 return;
2400 };
2401
a4262553 2402 my $taskname = $todisk ? 'qmsuspend' : 'qmpause';
f17fb184 2403 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2404 }});
2405
2406__PACKAGE__->register_method({
afdb31d5 2407 name => 'vm_resume',
5fdbe4f0
DM
2408 path => '{vmid}/status/resume',
2409 method => 'POST',
2410 protected => 1,
2411 proxyto => 'node',
2412 description => "Resume virtual machine.",
a0d1b1a2
DM
2413 permissions => {
2414 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2415 },
5fdbe4f0
DM
2416 parameters => {
2417 additionalProperties => 0,
2418 properties => {
2419 node => get_standard_option('pve-node'),
ab5904f7
TL
2420 vmid => get_standard_option('pve-vmid',
2421 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2422 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2423 nocheck => { type => 'boolean', optional => 1 },
2424
5fdbe4f0
DM
2425 },
2426 },
afdb31d5 2427 returns => {
5fdbe4f0
DM
2428 type => 'string',
2429 },
2430 code => sub {
2431 my ($param) = @_;
2432
2433 my $rpcenv = PVE::RPCEnvironment::get();
2434
a0d1b1a2 2435 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2436
2437 my $node = extract_param($param, 'node');
2438
2439 my $vmid = extract_param($param, 'vmid');
2440
2441 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2442 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2443 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2444
289e0b85
AD
2445 my $nocheck = extract_param($param, 'nocheck');
2446
cd9a035b
TL
2447 my $to_disk_suspended;
2448 eval {
2449 PVE::QemuConfig->lock_config($vmid, sub {
2450 my $conf = PVE::QemuConfig->load_config($vmid);
2451 $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
2452 });
2453 };
2454
2455 die "VM $vmid not running\n"
2456 if !$to_disk_suspended && !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2457
5fdbe4f0
DM
2458 my $realcmd = sub {
2459 my $upid = shift;
2460
2461 syslog('info', "resume VM $vmid: $upid\n");
2462
cd9a035b
TL
2463 if (!$to_disk_suspended) {
2464 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
2465 } else {
2466 my $storecfg = PVE::Storage::config();
2467 PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
2468 }
1e3baf05 2469
5fdbe4f0
DM
2470 return;
2471 };
2472
a0d1b1a2 2473 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2474 }});
2475
2476__PACKAGE__->register_method({
afdb31d5 2477 name => 'vm_sendkey',
5fdbe4f0
DM
2478 path => '{vmid}/sendkey',
2479 method => 'PUT',
2480 protected => 1,
2481 proxyto => 'node',
2482 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2483 permissions => {
2484 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2485 },
5fdbe4f0
DM
2486 parameters => {
2487 additionalProperties => 0,
2488 properties => {
2489 node => get_standard_option('pve-node'),
ab5904f7
TL
2490 vmid => get_standard_option('pve-vmid',
2491 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2492 skiplock => get_standard_option('skiplock'),
2493 key => {
2494 description => "The key (qemu monitor encoding).",
2495 type => 'string'
2496 }
2497 },
2498 },
2499 returns => { type => 'null'},
2500 code => sub {
2501 my ($param) = @_;
2502
2503 my $rpcenv = PVE::RPCEnvironment::get();
2504
a0d1b1a2 2505 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2506
2507 my $node = extract_param($param, 'node');
2508
2509 my $vmid = extract_param($param, 'vmid');
2510
2511 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2512 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2513 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2514
2515 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2516
2517 return;
1e3baf05
DM
2518 }});
2519
1ac0d2ee
AD
2520__PACKAGE__->register_method({
2521 name => 'vm_feature',
2522 path => '{vmid}/feature',
2523 method => 'GET',
2524 proxyto => 'node',
75466c4f 2525 protected => 1,
1ac0d2ee
AD
2526 description => "Check if feature for virtual machine is available.",
2527 permissions => {
2528 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2529 },
2530 parameters => {
2531 additionalProperties => 0,
2532 properties => {
2533 node => get_standard_option('pve-node'),
2534 vmid => get_standard_option('pve-vmid'),
2535 feature => {
2536 description => "Feature to check.",
2537 type => 'string',
7758ce86 2538 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2539 },
2540 snapname => get_standard_option('pve-snapshot-name', {
2541 optional => 1,
2542 }),
2543 },
1ac0d2ee
AD
2544 },
2545 returns => {
719893a9
DM
2546 type => "object",
2547 properties => {
2548 hasFeature => { type => 'boolean' },
7043d946 2549 nodes => {
719893a9
DM
2550 type => 'array',
2551 items => { type => 'string' },
2552 }
2553 },
1ac0d2ee
AD
2554 },
2555 code => sub {
2556 my ($param) = @_;
2557
2558 my $node = extract_param($param, 'node');
2559
2560 my $vmid = extract_param($param, 'vmid');
2561
2562 my $snapname = extract_param($param, 'snapname');
2563
2564 my $feature = extract_param($param, 'feature');
2565
2566 my $running = PVE::QemuServer::check_running($vmid);
2567
ffda963f 2568 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2569
2570 if($snapname){
2571 my $snap = $conf->{snapshots}->{$snapname};
2572 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2573 $conf = $snap;
2574 }
2575 my $storecfg = PVE::Storage::config();
2576
719893a9 2577 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2578 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2579
719893a9
DM
2580 return {
2581 hasFeature => $hasFeature,
2582 nodes => [ keys %$nodelist ],
7043d946 2583 };
1ac0d2ee
AD
2584 }});
2585
6116f729 2586__PACKAGE__->register_method({
9418baad
DM
2587 name => 'clone_vm',
2588 path => '{vmid}/clone',
6116f729
DM
2589 method => 'POST',
2590 protected => 1,
2591 proxyto => 'node',
37329185 2592 description => "Create a copy of virtual machine/template.",
6116f729 2593 permissions => {
9418baad 2594 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2595 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2596 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2597 check =>
2598 [ 'and',
9418baad 2599 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2600 [ 'or',
6116f729
DM
2601 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2602 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2603 ],
2604 ]
2605 },
2606 parameters => {
2607 additionalProperties => 0,
2608 properties => {
6116f729 2609 node => get_standard_option('pve-node'),
335af808 2610 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1ae43f8c
DM
2611 newid => get_standard_option('pve-vmid', {
2612 completion => \&PVE::Cluster::complete_next_vmid,
2613 description => 'VMID for the clone.' }),
a60ab1a6
DM
2614 name => {
2615 optional => 1,
2616 type => 'string', format => 'dns-name',
2617 description => "Set a name for the new VM.",
2618 },
2619 description => {
2620 optional => 1,
2621 type => 'string',
2622 description => "Description for the new VM.",
2623 },
75466c4f 2624 pool => {
6116f729
DM
2625 optional => 1,
2626 type => 'string', format => 'pve-poolid',
2627 description => "Add the new VM to the specified pool.",
2628 },
9076d880 2629 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2630 optional => 1,
2631 }),
81f043eb 2632 storage => get_standard_option('pve-storage-id', {
9418baad 2633 description => "Target storage for full clone.",
81f043eb
AD
2634 optional => 1,
2635 }),
55173c6b 2636 'format' => {
fd13b1d0 2637 description => "Target format for file storage. Only valid for full clone.",
42a19c87
AD
2638 type => 'string',
2639 optional => 1,
55173c6b 2640 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2641 },
6116f729
DM
2642 full => {
2643 optional => 1,
55173c6b 2644 type => 'boolean',
fd13b1d0 2645 description => "Create a full copy of all disks. This is always done when " .
9418baad 2646 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729 2647 },
75466c4f 2648 target => get_standard_option('pve-node', {
55173c6b
DM
2649 description => "Target node. Only allowed if the original VM is on shared storage.",
2650 optional => 1,
2651 }),
0aab5a16
SI
2652 bwlimit => {
2653 description => "Override I/O bandwidth limit (in KiB/s).",
2654 optional => 1,
2655 type => 'integer',
2656 minimum => '0',
41756a3b 2657 default => 'clone limit from datacenter or storage config',
0aab5a16 2658 },
55173c6b 2659 },
6116f729
DM
2660 },
2661 returns => {
2662 type => 'string',
2663 },
2664 code => sub {
2665 my ($param) = @_;
2666
2667 my $rpcenv = PVE::RPCEnvironment::get();
2668
55173c6b 2669 my $authuser = $rpcenv->get_user();
6116f729
DM
2670
2671 my $node = extract_param($param, 'node');
2672
2673 my $vmid = extract_param($param, 'vmid');
2674
2675 my $newid = extract_param($param, 'newid');
2676
6116f729
DM
2677 my $pool = extract_param($param, 'pool');
2678
2679 if (defined($pool)) {
2680 $rpcenv->check_pool_exist($pool);
2681 }
2682
55173c6b 2683 my $snapname = extract_param($param, 'snapname');
9076d880 2684
81f043eb
AD
2685 my $storage = extract_param($param, 'storage');
2686
42a19c87
AD
2687 my $format = extract_param($param, 'format');
2688
55173c6b
DM
2689 my $target = extract_param($param, 'target');
2690
2691 my $localnode = PVE::INotify::nodename();
2692
751cc556 2693 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2694
2695 PVE::Cluster::check_node_exists($target) if $target;
2696
6116f729
DM
2697 my $storecfg = PVE::Storage::config();
2698
4a5a2590
DM
2699 if ($storage) {
2700 # check if storage is enabled on local node
2701 PVE::Storage::storage_check_enabled($storecfg, $storage);
2702 if ($target) {
2703 # check if storage is available on target node
2704 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2705 # clone only works if target storage is shared
2706 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2707 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2708 }
2709 }
2710
55173c6b 2711 PVE::Cluster::check_cfs_quorum();
6116f729 2712
4e4f83fe
DM
2713 my $running = PVE::QemuServer::check_running($vmid) || 0;
2714
4e4f83fe
DM
2715 # exclusive lock if VM is running - else shared lock is enough;
2716 my $shared_lock = $running ? 0 : 1;
2717
9418baad 2718 my $clonefn = sub {
6116f729 2719
829967a9
DM
2720 # do all tests after lock
2721 # we also try to do all tests before we fork the worker
2722
ffda963f 2723 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2724
ffda963f 2725 PVE::QemuConfig->check_lock($conf);
6116f729 2726
4e4f83fe 2727 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2728
4e4f83fe 2729 die "unexpected state change\n" if $verify_running != $running;
6116f729 2730
75466c4f
DM
2731 die "snapshot '$snapname' does not exist\n"
2732 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2733
fd13b1d0
DM
2734 my $full = extract_param($param, 'full');
2735 if (!defined($full)) {
2736 $full = !PVE::QemuConfig->is_template($conf);
2737 }
2738
2739 die "parameter 'storage' not allowed for linked clones\n"
2740 if defined($storage) && !$full;
2741
2742 die "parameter 'format' not allowed for linked clones\n"
2743 if defined($format) && !$full;
2744
75466c4f 2745 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2746
9418baad 2747 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2748
9418baad 2749 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2750
ffda963f 2751 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2752
2753 die "unable to create VM $newid: config file already exists\n"
2754 if -f $conffile;
2755
9418baad 2756 my $newconf = { lock => 'clone' };
829967a9 2757 my $drives = {};
34456bf0 2758 my $fullclone = {};
829967a9
DM
2759 my $vollist = [];
2760
2761 foreach my $opt (keys %$oldconf) {
2762 my $value = $oldconf->{$opt};
2763
2764 # do not copy snapshot related info
2765 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2766 $opt eq 'vmstate' || $opt eq 'snapstate';
2767
a78ea5df
WL
2768 # no need to copy unused images, because VMID(owner) changes anyways
2769 next if $opt =~ m/^unused\d+$/;
2770
829967a9
DM
2771 # always change MAC! address
2772 if ($opt =~ m/^net(\d+)$/) {
2773 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2774 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2775 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2776 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2777 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2778 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2779 die "unable to parse drive options for '$opt'\n" if !$drive;
931432bd 2780 if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
829967a9
DM
2781 $newconf->{$opt} = $value; # simply copy configuration
2782 } else {
fd13b1d0 2783 if ($full || PVE::QemuServer::drive_is_cloudinit($drive)) {
6318daca 2784 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2785 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2786 $fullclone->{$opt} = 1;
64ff6fe4
SP
2787 } else {
2788 # not full means clone instead of copy
6318daca 2789 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2790 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2791 }
829967a9
DM
2792 $drives->{$opt} = $drive;
2793 push @$vollist, $drive->{file};
2794 }
2795 } else {
2796 # copy everything else
2797 $newconf->{$opt} = $value;
2798 }
2799 }
2800
cd11416f 2801 # auto generate a new uuid
cd11416f 2802 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
6ee499ff 2803 $smbios1->{uuid} = PVE::QemuServer::generate_uuid();
cd11416f
DM
2804 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2805
6ee499ff
DC
2806 # auto generate a new vmgenid if the option was set
2807 if ($newconf->{vmgenid}) {
2808 $newconf->{vmgenid} = PVE::QemuServer::generate_uuid();
2809 }
2810
829967a9
DM
2811 delete $newconf->{template};
2812
2813 if ($param->{name}) {
2814 $newconf->{name} = $param->{name};
2815 } else {
c55fee03
DM
2816 if ($oldconf->{name}) {
2817 $newconf->{name} = "Copy-of-$oldconf->{name}";
2818 } else {
2819 $newconf->{name} = "Copy-of-VM-$vmid";
2820 }
829967a9 2821 }
2dd53043 2822
829967a9
DM
2823 if ($param->{description}) {
2824 $newconf->{description} = $param->{description};
2825 }
2826
6116f729 2827 # create empty/temp config - this fails if VM already exists on other node
9418baad 2828 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2829
2830 my $realcmd = sub {
2831 my $upid = shift;
2832
b83e0181 2833 my $newvollist = [];
c6fdd002 2834 my $jobs = {};
6116f729 2835
b83e0181 2836 eval {
eaae66be
TL
2837 local $SIG{INT} =
2838 local $SIG{TERM} =
2839 local $SIG{QUIT} =
2840 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2841
eb15b9f0 2842 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2843
0aab5a16
SI
2844 my $bwlimit = extract_param($param, 'bwlimit');
2845
c6fdd002
AD
2846 my $total_jobs = scalar(keys %{$drives});
2847 my $i = 1;
c6fdd002 2848
829967a9
DM
2849 foreach my $opt (keys %$drives) {
2850 my $drive = $drives->{$opt};
3b4cf0f0 2851 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2852
0aab5a16 2853 my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
f0dbdb68
TL
2854 my $storage_list = [ $src_sid ];
2855 push @$storage_list, $storage if defined($storage);
ee43cd48 2856 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
0aab5a16 2857
152fe752 2858 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0 2859 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
0aab5a16 2860 $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
00b095ca 2861
152fe752 2862 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2863
ffda963f 2864 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2865 $i++;
829967a9 2866 }
b83e0181
DM
2867
2868 delete $newconf->{lock};
68e46b84
DC
2869
2870 # do not write pending changes
c725dd5f
DC
2871 if (my @changes = keys %{$newconf->{pending}}) {
2872 my $pending = join(',', @changes);
2873 warn "found pending changes for '$pending', discarding for clone\n";
68e46b84
DC
2874 delete $newconf->{pending};
2875 }
2876
ffda963f 2877 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2878
2879 if ($target) {
baca276d 2880 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2881 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2882 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2883
ffda963f 2884 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2885 die "Failed to move config to node '$target' - rename failed: $!\n"
2886 if !rename($conffile, $newconffile);
2887 }
d703d4c0 2888
be517049 2889 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2890 };
75466c4f 2891 if (my $err = $@) {
6116f729
DM
2892 unlink $conffile;
2893
c6fdd002
AD
2894 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2895
b83e0181
DM
2896 sleep 1; # some storage like rbd need to wait before release volume - really?
2897
2898 foreach my $volid (@$newvollist) {
2899 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2900 warn $@ if $@;
2901 }
9418baad 2902 die "clone failed: $err";
6116f729
DM
2903 }
2904
2905 return;
2906 };
2907
457010cc
AG
2908 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2909
9418baad 2910 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2911 };
2912
ffda963f 2913 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2914 # Aquire exclusive lock lock for $newid
ffda963f 2915 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2916 });
2917
2918 }});
2919
586bfa78 2920__PACKAGE__->register_method({
43bc02a9
DM
2921 name => 'move_vm_disk',
2922 path => '{vmid}/move_disk',
e2cd75fa 2923 method => 'POST',
586bfa78
AD
2924 protected => 1,
2925 proxyto => 'node',
2926 description => "Move volume to different storage.",
2927 permissions => {
c07a9e3d
DM
2928 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2929 check => [ 'and',
2930 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2931 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2932 ],
586bfa78
AD
2933 },
2934 parameters => {
2935 additionalProperties => 0,
c07a9e3d 2936 properties => {
586bfa78 2937 node => get_standard_option('pve-node'),
335af808 2938 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2939 disk => {
2940 type => 'string',
2941 description => "The disk you want to move.",
74479ee9 2942 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2943 },
335af808
DM
2944 storage => get_standard_option('pve-storage-id', {
2945 description => "Target storage.",
2946 completion => \&PVE::QemuServer::complete_storage,
2947 }),
635c3c44 2948 'format' => {
586bfa78
AD
2949 type => 'string',
2950 description => "Target Format.",
2951 enum => [ 'raw', 'qcow2', 'vmdk' ],
2952 optional => 1,
2953 },
70d45e33
DM
2954 delete => {
2955 type => 'boolean',
2956 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2957 optional => 1,
2958 default => 0,
2959 },
586bfa78
AD
2960 digest => {
2961 type => 'string',
2962 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2963 maxLength => 40,
2964 optional => 1,
2965 },
0aab5a16
SI
2966 bwlimit => {
2967 description => "Override I/O bandwidth limit (in KiB/s).",
2968 optional => 1,
2969 type => 'integer',
2970 minimum => '0',
41756a3b 2971 default => 'move limit from datacenter or storage config',
0aab5a16 2972 },
586bfa78
AD
2973 },
2974 },
e2cd75fa
DM
2975 returns => {
2976 type => 'string',
2977 description => "the task ID.",
2978 },
586bfa78
AD
2979 code => sub {
2980 my ($param) = @_;
2981
2982 my $rpcenv = PVE::RPCEnvironment::get();
2983
2984 my $authuser = $rpcenv->get_user();
2985
2986 my $node = extract_param($param, 'node');
2987
2988 my $vmid = extract_param($param, 'vmid');
2989
2990 my $digest = extract_param($param, 'digest');
2991
2992 my $disk = extract_param($param, 'disk');
2993
2994 my $storeid = extract_param($param, 'storage');
2995
2996 my $format = extract_param($param, 'format');
2997
586bfa78
AD
2998 my $storecfg = PVE::Storage::config();
2999
3000 my $updatefn = sub {
3001
ffda963f 3002 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 3003
dcce9b46
FG
3004 PVE::QemuConfig->check_lock($conf);
3005
586bfa78
AD
3006 die "checksum missmatch (file change by other user?)\n"
3007 if $digest && $digest ne $conf->{digest};
586bfa78
AD
3008
3009 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3010
3011 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3012
70d45e33 3013 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78 3014
931432bd 3015 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
586bfa78 3016
e2cd75fa 3017 my $oldfmt;
70d45e33 3018 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
3019 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3020 $oldfmt = $1;
3021 }
3022
7043d946 3023 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 3024 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 3025
9dbf9b54
FG
3026 # this only checks snapshots because $disk is passed!
3027 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
3028 die "you can't move a disk with snapshots and delete the source\n"
3029 if $snapshotted && $param->{delete};
3030
586bfa78
AD
3031 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3032
3033 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
3034
3035 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
3036
586bfa78
AD
3037 my $realcmd = sub {
3038
3039 my $newvollist = [];
3040
3041 eval {
6cb0144a
EK
3042 local $SIG{INT} =
3043 local $SIG{TERM} =
3044 local $SIG{QUIT} =
3045 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 3046
9dbf9b54
FG
3047 warn "moving disk with snapshots, snapshots will not be moved!\n"
3048 if $snapshotted;
3049
0aab5a16
SI
3050 my $bwlimit = extract_param($param, 'bwlimit');
3051 my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
3052
e2cd75fa 3053 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
0aab5a16 3054 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
e2cd75fa
DM
3055
3056 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
3057
8793d495 3058 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 3059
fbd7dcce
FG
3060 # convert moved disk to base if part of template
3061 PVE::QemuServer::template_create($vmid, $conf, $disk)
3062 if PVE::QemuConfig->is_template($conf);
3063
ffda963f 3064 PVE::QemuConfig->write_config($vmid, $conf);
73272365 3065
ca662131
SI
3066 if ($running && PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks} && PVE::QemuServer::qga_check_running($vmid)) {
3067 eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fstrim"); };
3068 }
3069
f34ebd52 3070 eval {
73272365 3071 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 3072 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
3073 if !$running;
3074 };
3075 warn $@ if $@;
586bfa78
AD
3076 };
3077 if (my $err = $@) {
3078
3079 foreach my $volid (@$newvollist) {
3080 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
3081 warn $@ if $@;
3082 }
3083 die "storage migration failed: $err";
3084 }
70d45e33
DM
3085
3086 if ($param->{delete}) {
a3d0bafb
FG
3087 eval {
3088 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
3089 PVE::Storage::vdisk_free($storecfg, $old_volid);
3090 };
3091 warn $@ if $@;
70d45e33 3092 }
586bfa78
AD
3093 };
3094
3095 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3096 };
e2cd75fa 3097
ffda963f 3098 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
3099 }});
3100
71fc647f
TM
3101my $check_vm_disks_local = sub {
3102 my ($storecfg, $vmconf, $vmid) = @_;
3103
3104 my $local_disks = {};
3105
3106 # add some more information to the disks e.g. cdrom
3107 PVE::QemuServer::foreach_volid($vmconf, sub {
3108 my ($volid, $attr) = @_;
3109
3110 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
3111 if ($storeid) {
3112 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
3113 return if $scfg->{shared};
3114 }
3115 # The shared attr here is just a special case where the vdisk
3116 # is marked as shared manually
3117 return if $attr->{shared};
3118 return if $attr->{cdrom} and $volid eq "none";
3119
3120 if (exists $local_disks->{$volid}) {
3121 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3122 } else {
3123 $local_disks->{$volid} = $attr;
3124 # ensure volid is present in case it's needed
3125 $local_disks->{$volid}->{volid} = $volid;
3126 }
3127 });
3128
3129 return $local_disks;
3130};
3131
3132__PACKAGE__->register_method({
3133 name => 'migrate_vm_precondition',
3134 path => '{vmid}/migrate',
3135 method => 'GET',
3136 protected => 1,
3137 proxyto => 'node',
3138 description => "Get preconditions for migration.",
3139 permissions => {
3140 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3141 },
3142 parameters => {
3143 additionalProperties => 0,
3144 properties => {
3145 node => get_standard_option('pve-node'),
3146 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
3147 target => get_standard_option('pve-node', {
3148 description => "Target node.",
3149 completion => \&PVE::Cluster::complete_migration_target,
3150 optional => 1,
3151 }),
3152 },
3153 },
3154 returns => {
3155 type => "object",
3156 properties => {
3157 running => { type => 'boolean' },
3158 allowed_nodes => {
3159 type => 'array',
3160 optional => 1,
f25852c2
TM
3161 description => "List nodes allowed for offline migration, only passed if VM is offline"
3162 },
3163 not_allowed_nodes => {
3164 type => 'object',
3165 optional => 1,
3166 description => "List not allowed nodes with additional informations, only passed if VM is offline"
71fc647f
TM
3167 },
3168 local_disks => {
3169 type => 'array',
3170 description => "List local disks including CD-Rom, unsused and not referenced disks"
3171 },
3172 local_resources => {
3173 type => 'array',
3174 description => "List local resources e.g. pci, usb"
3175 }
3176 },
3177 },
3178 code => sub {
3179 my ($param) = @_;
3180
3181 my $rpcenv = PVE::RPCEnvironment::get();
3182
3183 my $authuser = $rpcenv->get_user();
3184
3185 PVE::Cluster::check_cfs_quorum();
3186
3187 my $res = {};
3188
3189 my $vmid = extract_param($param, 'vmid');
3190 my $target = extract_param($param, 'target');
3191 my $localnode = PVE::INotify::nodename();
3192
3193
3194 # test if VM exists
3195 my $vmconf = PVE::QemuConfig->load_config($vmid);
3196 my $storecfg = PVE::Storage::config();
3197
3198
3199 # try to detect errors early
3200 PVE::QemuConfig->check_lock($vmconf);
3201
3202 $res->{running} = PVE::QemuServer::check_running($vmid) ? 1:0;
3203
3204 # if vm is not running, return target nodes where local storage is available
3205 # for offline migration
3206 if (!$res->{running}) {
f25852c2
TM
3207 $res->{allowed_nodes} = [];
3208 my $checked_nodes = PVE::QemuServer::check_local_storage_availability($vmconf, $storecfg);
32075a2c 3209 delete $checked_nodes->{$localnode};
f25852c2 3210
f25852c2 3211 foreach my $node (keys %$checked_nodes) {
32075a2c 3212 if (!defined $checked_nodes->{$node}->{unavailable_storages}) {
f25852c2 3213 push @{$res->{allowed_nodes}}, $node;
f25852c2 3214 }
71fc647f 3215
f25852c2
TM
3216 }
3217 $res->{not_allowed_nodes} = $checked_nodes;
71fc647f
TM
3218 }
3219
3220
3221 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3222 $res->{local_disks} = [ values %$local_disks ];;
3223
3224 my $local_resources = PVE::QemuServer::check_local_resources($vmconf, 1);
3225
3226 $res->{local_resources} = $local_resources;
3227
3228 return $res;
3229
3230
3231 }});
3232
3ea94c60 3233__PACKAGE__->register_method({
afdb31d5 3234 name => 'migrate_vm',
3ea94c60
DM
3235 path => '{vmid}/migrate',
3236 method => 'POST',
3237 protected => 1,
3238 proxyto => 'node',
3239 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
3240 permissions => {
3241 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3242 },
3ea94c60
DM
3243 parameters => {
3244 additionalProperties => 0,
3245 properties => {
3246 node => get_standard_option('pve-node'),
335af808 3247 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
c2ed338e 3248 target => get_standard_option('pve-node', {
335af808
DM
3249 description => "Target node.",
3250 completion => \&PVE::Cluster::complete_migration_target,
3251 }),
3ea94c60
DM
3252 online => {
3253 type => 'boolean',
3254 description => "Use online/live migration.",
3255 optional => 1,
3256 },
3257 force => {
3258 type => 'boolean',
3259 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
3260 optional => 1,
3261 },
2de2d6f7
TL
3262 migration_type => {
3263 type => 'string',
3264 enum => ['secure', 'insecure'],
c07a9e3d 3265 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
3266 optional => 1,
3267 },
3268 migration_network => {
c07a9e3d 3269 type => 'string', format => 'CIDR',
2de2d6f7
TL
3270 description => "CIDR of the (sub) network that is used for migration.",
3271 optional => 1,
3272 },
56af7146
AD
3273 "with-local-disks" => {
3274 type => 'boolean',
3275 description => "Enable live storage migration for local disk",
b74cad8a 3276 optional => 1,
56af7146
AD
3277 },
3278 targetstorage => get_standard_option('pve-storage-id', {
3279 description => "Default target storage.",
3280 optional => 1,
3281 completion => \&PVE::QemuServer::complete_storage,
3282 }),
0aab5a16
SI
3283 bwlimit => {
3284 description => "Override I/O bandwidth limit (in KiB/s).",
3285 optional => 1,
3286 type => 'integer',
3287 minimum => '0',
41756a3b 3288 default => 'migrate limit from datacenter or storage config',
0aab5a16 3289 },
3ea94c60
DM
3290 },
3291 },
afdb31d5 3292 returns => {
3ea94c60
DM
3293 type => 'string',
3294 description => "the task ID.",
3295 },
3296 code => sub {
3297 my ($param) = @_;
3298
3299 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 3300 my $authuser = $rpcenv->get_user();
3ea94c60
DM
3301
3302 my $target = extract_param($param, 'target');
3303
3304 my $localnode = PVE::INotify::nodename();
3305 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
3306
3307 PVE::Cluster::check_cfs_quorum();
3308
3309 PVE::Cluster::check_node_exists($target);
3310
3311 my $targetip = PVE::Cluster::remote_node_ip($target);
3312
3313 my $vmid = extract_param($param, 'vmid');
3314
bd2d5fe6 3315 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
3316 if !$param->{online} && $param->{targetstorage};
3317
afdb31d5 3318 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 3319 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 3320
2de2d6f7
TL
3321 raise_param_exc({ migration_type => "Only root may use this option." })
3322 if $param->{migration_type} && $authuser ne 'root@pam';
3323
3324 # allow root only until better network permissions are available
3325 raise_param_exc({ migration_network => "Only root may use this option." })
3326 if $param->{migration_network} && $authuser ne 'root@pam';
3327
3ea94c60 3328 # test if VM exists
ffda963f 3329 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
3330
3331 # try to detect errors early
a5ed42d3 3332
ffda963f 3333 PVE::QemuConfig->check_lock($conf);
a5ed42d3 3334
3ea94c60 3335 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 3336 die "cant migrate running VM without --online\n"
3ea94c60
DM
3337 if !$param->{online};
3338 }
3339
47152e2e 3340 my $storecfg = PVE::Storage::config();
d80ad67f
AD
3341
3342 if( $param->{targetstorage}) {
3343 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
3344 } else {
3345 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
3346 }
47152e2e 3347
2003f0f8 3348 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 3349
88fc87b4
DM
3350 my $hacmd = sub {
3351 my $upid = shift;
3ea94c60 3352
02765844 3353 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4 3354
a4262553 3355 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
88fc87b4 3356 PVE::Tools::run_command($cmd);
88fc87b4
DM
3357 return;
3358 };
3359
3360 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3361
3362 } else {
3363
f53c6ad8 3364 my $realcmd = sub {
f53c6ad8
DM
3365 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3366 };
88fc87b4 3367
f53c6ad8
DM
3368 my $worker = sub {
3369 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
3370 };
3371
f53c6ad8 3372 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 3373 }
3ea94c60 3374
3ea94c60 3375 }});
1e3baf05 3376
91c94f0a 3377__PACKAGE__->register_method({
afdb31d5
DM
3378 name => 'monitor',
3379 path => '{vmid}/monitor',
91c94f0a
DM
3380 method => 'POST',
3381 protected => 1,
3382 proxyto => 'node',
3383 description => "Execute Qemu monitor commands.",
a0d1b1a2 3384 permissions => {
a8f2f427 3385 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 3386 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 3387 },
91c94f0a
DM
3388 parameters => {
3389 additionalProperties => 0,
3390 properties => {
3391 node => get_standard_option('pve-node'),
3392 vmid => get_standard_option('pve-vmid'),
3393 command => {
3394 type => 'string',
3395 description => "The monitor command.",
3396 }
3397 },
3398 },
3399 returns => { type => 'string'},
3400 code => sub {
3401 my ($param) = @_;
3402
a8f2f427
FG
3403 my $rpcenv = PVE::RPCEnvironment::get();
3404 my $authuser = $rpcenv->get_user();
3405
3406 my $is_ro = sub {
3407 my $command = shift;
3408 return $command =~ m/^\s*info(\s+|$)/
3409 || $command =~ m/^\s*help\s*$/;
3410 };
3411
3412 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3413 if !&$is_ro($param->{command});
3414
91c94f0a
DM
3415 my $vmid = $param->{vmid};
3416
ffda963f 3417 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
3418
3419 my $res = '';
3420 eval {
7b7c6d1b 3421 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
3422 };
3423 $res = "ERROR: $@" if $@;
3424
3425 return $res;
3426 }});
3427
0d02881c
AD
3428__PACKAGE__->register_method({
3429 name => 'resize_vm',
614e3941 3430 path => '{vmid}/resize',
0d02881c
AD
3431 method => 'PUT',
3432 protected => 1,
3433 proxyto => 'node',
2f48a4f5 3434 description => "Extend volume size.",
0d02881c 3435 permissions => {
3b2773f6 3436 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3437 },
3438 parameters => {
3439 additionalProperties => 0,
2f48a4f5
DM
3440 properties => {
3441 node => get_standard_option('pve-node'),
335af808 3442 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3443 skiplock => get_standard_option('skiplock'),
3444 disk => {
3445 type => 'string',
3446 description => "The disk you want to resize.",
74479ee9 3447 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3448 },
3449 size => {
3450 type => 'string',
f91b2e45 3451 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3452 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
3453 },
3454 digest => {
3455 type => 'string',
3456 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3457 maxLength => 40,
3458 optional => 1,
3459 },
3460 },
0d02881c
AD
3461 },
3462 returns => { type => 'null'},
3463 code => sub {
3464 my ($param) = @_;
3465
3466 my $rpcenv = PVE::RPCEnvironment::get();
3467
3468 my $authuser = $rpcenv->get_user();
3469
3470 my $node = extract_param($param, 'node');
3471
3472 my $vmid = extract_param($param, 'vmid');
3473
3474 my $digest = extract_param($param, 'digest');
3475
2f48a4f5 3476 my $disk = extract_param($param, 'disk');
75466c4f 3477
2f48a4f5 3478 my $sizestr = extract_param($param, 'size');
0d02881c 3479
f91b2e45 3480 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3481 raise_param_exc({ skiplock => "Only root may use this option." })
3482 if $skiplock && $authuser ne 'root@pam';
3483
0d02881c
AD
3484 my $storecfg = PVE::Storage::config();
3485
0d02881c
AD
3486 my $updatefn = sub {
3487
ffda963f 3488 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3489
3490 die "checksum missmatch (file change by other user?)\n"
3491 if $digest && $digest ne $conf->{digest};
ffda963f 3492 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3493
f91b2e45
DM
3494 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3495
3496 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3497
d662790a
WL
3498 my (undef, undef, undef, undef, undef, undef, $format) =
3499 PVE::Storage::parse_volname($storecfg, $drive->{file});
3500
c2ed338e 3501 die "can't resize volume: $disk if snapshot exists\n"
d662790a
WL
3502 if %{$conf->{snapshots}} && $format eq 'qcow2';
3503
f91b2e45
DM
3504 my $volid = $drive->{file};
3505
3506 die "disk '$disk' has no associated volume\n" if !$volid;
3507
3508 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3509
3510 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3511
3512 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3513
b572a606 3514 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3515 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3516
3517 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3518 my ($ext, $newsize, $unit) = ($1, $2, $4);
3519 if ($unit) {
3520 if ($unit eq 'K') {
3521 $newsize = $newsize * 1024;
3522 } elsif ($unit eq 'M') {
3523 $newsize = $newsize * 1024 * 1024;
3524 } elsif ($unit eq 'G') {
3525 $newsize = $newsize * 1024 * 1024 * 1024;
3526 } elsif ($unit eq 'T') {
3527 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3528 }
3529 }
3530 $newsize += $size if $ext;
3531 $newsize = int($newsize);
3532
9a478b17 3533 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3534
3535 return if $size == $newsize;
3536
2f48a4f5 3537 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3538
f91b2e45 3539 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3540
f91b2e45
DM
3541 $drive->{size} = $newsize;
3542 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3543
ffda963f 3544 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3545 };
0d02881c 3546
ffda963f 3547 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3548 return undef;
3549 }});
3550
9dbd1ee4 3551__PACKAGE__->register_method({
7e7d7b61 3552 name => 'snapshot_list',
9dbd1ee4 3553 path => '{vmid}/snapshot',
7e7d7b61
DM
3554 method => 'GET',
3555 description => "List all snapshots.",
3556 permissions => {
3557 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3558 },
3559 proxyto => 'node',
3560 protected => 1, # qemu pid files are only readable by root
3561 parameters => {
3562 additionalProperties => 0,
3563 properties => {
e261de40 3564 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3565 node => get_standard_option('pve-node'),
3566 },
3567 },
3568 returns => {
3569 type => 'array',
3570 items => {
3571 type => "object",
ce9b0a38
DM
3572 properties => {
3573 name => {
3574 description => "Snapshot identifier. Value 'current' identifies the current VM.",
3575 type => 'string',
3576 },
3577 vmstate => {
3578 description => "Snapshot includes RAM.",
3579 type => 'boolean',
3580 optional => 1,
3581 },
3582 description => {
3583 description => "Snapshot description.",
3584 type => 'string',
3585 },
3586 snaptime => {
3587 description => "Snapshot creation time",
3588 type => 'integer',
3589 renderer => 'timestamp',
3590 optional => 1,
3591 },
3592 parent => {
3593 description => "Parent snapshot identifier.",
3594 type => 'string',
3595 optional => 1,
3596 },
3597 },
7e7d7b61
DM
3598 },
3599 links => [ { rel => 'child', href => "{name}" } ],
3600 },
3601 code => sub {
3602 my ($param) = @_;
3603
6aa4651b
DM
3604 my $vmid = $param->{vmid};
3605
ffda963f 3606 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3607 my $snaphash = $conf->{snapshots} || {};
3608
3609 my $res = [];
3610
3611 foreach my $name (keys %$snaphash) {
0ea6bc69 3612 my $d = $snaphash->{$name};
75466c4f
DM
3613 my $item = {
3614 name => $name,
3615 snaptime => $d->{snaptime} || 0,
6aa4651b 3616 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3617 description => $d->{description} || '',
3618 };
0ea6bc69 3619 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3620 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3621 push @$res, $item;
3622 }
3623
6aa4651b 3624 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
ce9b0a38
DM
3625 my $current = {
3626 name => 'current',
3627 digest => $conf->{digest},
3628 running => $running,
3629 description => "You are here!",
3630 };
d1914468
DM
3631 $current->{parent} = $conf->{parent} if $conf->{parent};
3632
3633 push @$res, $current;
7e7d7b61
DM
3634
3635 return $res;
3636 }});
3637
3638__PACKAGE__->register_method({
3639 name => 'snapshot',
3640 path => '{vmid}/snapshot',
3641 method => 'POST',
9dbd1ee4
AD
3642 protected => 1,
3643 proxyto => 'node',
3644 description => "Snapshot a VM.",
3645 permissions => {
f1baf1df 3646 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3647 },
3648 parameters => {
3649 additionalProperties => 0,
3650 properties => {
3651 node => get_standard_option('pve-node'),
335af808 3652 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3653 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3654 vmstate => {
3655 optional => 1,
3656 type => 'boolean',
3657 description => "Save the vmstate",
3658 },
782f4f75
DM
3659 description => {
3660 optional => 1,
3661 type => 'string',
3662 description => "A textual description or comment.",
3663 },
9dbd1ee4
AD
3664 },
3665 },
7e7d7b61
DM
3666 returns => {
3667 type => 'string',
3668 description => "the task ID.",
3669 },
9dbd1ee4
AD
3670 code => sub {
3671 my ($param) = @_;
3672
3673 my $rpcenv = PVE::RPCEnvironment::get();
3674
3675 my $authuser = $rpcenv->get_user();
3676
3677 my $node = extract_param($param, 'node');
3678
3679 my $vmid = extract_param($param, 'vmid');
3680
9dbd1ee4
AD
3681 my $snapname = extract_param($param, 'snapname');
3682
d1914468
DM
3683 die "unable to use snapshot name 'current' (reserved name)\n"
3684 if $snapname eq 'current';
3685
7e7d7b61 3686 my $realcmd = sub {
22c377f0 3687 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
c2ed338e 3688 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3689 $param->{description});
7e7d7b61
DM
3690 };
3691
3692 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3693 }});
3694
154ccdcd
DM
3695__PACKAGE__->register_method({
3696 name => 'snapshot_cmd_idx',
3697 path => '{vmid}/snapshot/{snapname}',
3698 description => '',
3699 method => 'GET',
3700 permissions => {
3701 user => 'all',
3702 },
3703 parameters => {
3704 additionalProperties => 0,
3705 properties => {
3706 vmid => get_standard_option('pve-vmid'),
3707 node => get_standard_option('pve-node'),
8abd398b 3708 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3709 },
3710 },
3711 returns => {
3712 type => 'array',
3713 items => {
3714 type => "object",
3715 properties => {},
3716 },
3717 links => [ { rel => 'child', href => "{cmd}" } ],
3718 },
3719 code => sub {
3720 my ($param) = @_;
3721
3722 my $res = [];
3723
3724 push @$res, { cmd => 'rollback' };
d788cea6 3725 push @$res, { cmd => 'config' };
154ccdcd
DM
3726
3727 return $res;
3728 }});
3729
d788cea6
DM
3730__PACKAGE__->register_method({
3731 name => 'update_snapshot_config',
3732 path => '{vmid}/snapshot/{snapname}/config',
3733 method => 'PUT',
3734 protected => 1,
3735 proxyto => 'node',
3736 description => "Update snapshot metadata.",
3737 permissions => {
3738 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3739 },
3740 parameters => {
3741 additionalProperties => 0,
3742 properties => {
3743 node => get_standard_option('pve-node'),
3744 vmid => get_standard_option('pve-vmid'),
3745 snapname => get_standard_option('pve-snapshot-name'),
3746 description => {
3747 optional => 1,
3748 type => 'string',
3749 description => "A textual description or comment.",
3750 },
3751 },
3752 },
3753 returns => { type => 'null' },
3754 code => sub {
3755 my ($param) = @_;
3756
3757 my $rpcenv = PVE::RPCEnvironment::get();
3758
3759 my $authuser = $rpcenv->get_user();
3760
3761 my $vmid = extract_param($param, 'vmid');
3762
3763 my $snapname = extract_param($param, 'snapname');
3764
3765 return undef if !defined($param->{description});
3766
3767 my $updatefn = sub {
3768
ffda963f 3769 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3770
ffda963f 3771 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3772
3773 my $snap = $conf->{snapshots}->{$snapname};
3774
75466c4f
DM
3775 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3776
d788cea6
DM
3777 $snap->{description} = $param->{description} if defined($param->{description});
3778
ffda963f 3779 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3780 };
3781
ffda963f 3782 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3783
3784 return undef;
3785 }});
3786
3787__PACKAGE__->register_method({
3788 name => 'get_snapshot_config',
3789 path => '{vmid}/snapshot/{snapname}/config',
3790 method => 'GET',
3791 proxyto => 'node',
3792 description => "Get snapshot configuration",
3793 permissions => {
c268337d 3794 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3795 },
3796 parameters => {
3797 additionalProperties => 0,
3798 properties => {
3799 node => get_standard_option('pve-node'),
3800 vmid => get_standard_option('pve-vmid'),
3801 snapname => get_standard_option('pve-snapshot-name'),
3802 },
3803 },
3804 returns => { type => "object" },
3805 code => sub {
3806 my ($param) = @_;
3807
3808 my $rpcenv = PVE::RPCEnvironment::get();
3809
3810 my $authuser = $rpcenv->get_user();
3811
3812 my $vmid = extract_param($param, 'vmid');
3813
3814 my $snapname = extract_param($param, 'snapname');
3815
ffda963f 3816 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3817
3818 my $snap = $conf->{snapshots}->{$snapname};
3819
75466c4f
DM
3820 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3821
d788cea6
DM
3822 return $snap;
3823 }});
3824
7e7d7b61
DM
3825__PACKAGE__->register_method({
3826 name => 'rollback',
154ccdcd 3827 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3828 method => 'POST',
3829 protected => 1,
3830 proxyto => 'node',
3831 description => "Rollback VM state to specified snapshot.",
3832 permissions => {
c268337d 3833 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3834 },
3835 parameters => {
3836 additionalProperties => 0,
3837 properties => {
3838 node => get_standard_option('pve-node'),
335af808 3839 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3840 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3841 },
3842 },
3843 returns => {
3844 type => 'string',
3845 description => "the task ID.",
3846 },
3847 code => sub {
3848 my ($param) = @_;
3849
3850 my $rpcenv = PVE::RPCEnvironment::get();
3851
3852 my $authuser = $rpcenv->get_user();
3853
3854 my $node = extract_param($param, 'node');
3855
3856 my $vmid = extract_param($param, 'vmid');
3857
3858 my $snapname = extract_param($param, 'snapname');
3859
7e7d7b61 3860 my $realcmd = sub {
22c377f0 3861 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3862 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3863 };
3864
c068c1c3
WL
3865 my $worker = sub {
3866 # hold migration lock, this makes sure that nobody create replication snapshots
3867 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3868 };
3869
3870 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3871 }});
3872
3873__PACKAGE__->register_method({
3874 name => 'delsnapshot',
3875 path => '{vmid}/snapshot/{snapname}',
3876 method => 'DELETE',
3877 protected => 1,
3878 proxyto => 'node',
3879 description => "Delete a VM snapshot.",
3880 permissions => {
f1baf1df 3881 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3882 },
3883 parameters => {
3884 additionalProperties => 0,
3885 properties => {
3886 node => get_standard_option('pve-node'),
335af808 3887 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3888 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3889 force => {
3890 optional => 1,
3891 type => 'boolean',
3892 description => "For removal from config file, even if removing disk snapshots fails.",
3893 },
7e7d7b61
DM
3894 },
3895 },
3896 returns => {
3897 type => 'string',
3898 description => "the task ID.",
3899 },
3900 code => sub {
3901 my ($param) = @_;
3902
3903 my $rpcenv = PVE::RPCEnvironment::get();
3904
3905 my $authuser = $rpcenv->get_user();
3906
3907 my $node = extract_param($param, 'node');
3908
3909 my $vmid = extract_param($param, 'vmid');
3910
3911 my $snapname = extract_param($param, 'snapname');
3912
7e7d7b61 3913 my $realcmd = sub {
22c377f0 3914 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3915 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3916 };
9dbd1ee4 3917
7b2257a8 3918 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3919 }});
3920
04a69bb4
AD
3921__PACKAGE__->register_method({
3922 name => 'template',
3923 path => '{vmid}/template',
3924 method => 'POST',
3925 protected => 1,
3926 proxyto => 'node',
3927 description => "Create a Template.",
b02691d8 3928 permissions => {
7af0a6c8
DM
3929 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3930 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3931 },
04a69bb4
AD
3932 parameters => {
3933 additionalProperties => 0,
3934 properties => {
3935 node => get_standard_option('pve-node'),
335af808 3936 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3937 disk => {
3938 optional => 1,
3939 type => 'string',
3940 description => "If you want to convert only 1 disk to base image.",
74479ee9 3941 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3942 },
3943
3944 },
3945 },
3946 returns => { type => 'null'},
3947 code => sub {
3948 my ($param) = @_;
3949
3950 my $rpcenv = PVE::RPCEnvironment::get();
3951
3952 my $authuser = $rpcenv->get_user();
3953
3954 my $node = extract_param($param, 'node');
3955
3956 my $vmid = extract_param($param, 'vmid');
3957
3958 my $disk = extract_param($param, 'disk');
3959
3960 my $updatefn = sub {
3961
ffda963f 3962 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3963
ffda963f 3964 PVE::QemuConfig->check_lock($conf);
04a69bb4 3965
75466c4f 3966 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3967 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3968
75466c4f 3969 die "you can't convert a template to a template\n"
ffda963f 3970 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3971
75466c4f 3972 die "you can't convert a VM to template if VM is running\n"
218cab9a 3973 if PVE::QemuServer::check_running($vmid);
35c5fdef 3974
04a69bb4
AD
3975 my $realcmd = sub {
3976 PVE::QemuServer::template_create($vmid, $conf, $disk);
3977 };
04a69bb4 3978
75e7e997 3979 $conf->{template} = 1;
ffda963f 3980 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3981
3982 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3983 };
3984
ffda963f 3985 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3986 return undef;
3987 }});
3988
73709749
ML
3989__PACKAGE__->register_method({
3990 name => 'cloudinit_generated_config_dump',
3991 path => '{vmid}/cloudinit/dump',
3992 method => 'GET',
3993 proxyto => 'node',
3994 description => "Get automatically generated cloudinit config.",
3995 permissions => {
3996 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3997 },
3998 parameters => {
3999 additionalProperties => 0,
4000 properties => {
4001 node => get_standard_option('pve-node'),
4002 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
4003 type => {
4004 description => 'Config type.',
4005 type => 'string',
4006 enum => ['user', 'network', 'meta'],
4007 },
4008 },
4009 },
4010 returns => {
4011 type => 'string',
4012 },
4013 code => sub {
4014 my ($param) = @_;
4015
4016 my $conf = PVE::QemuConfig->load_config($param->{vmid});
4017
4018 return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type});
4019 }});
4020
1e3baf05 40211;