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