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