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