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