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