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