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