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