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