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